From 15b3394ae539c732c8f3304b40de59d17e92563b Mon Sep 17 00:00:00 2001 From: tzzzzzzzx <414850847@qq.com> Date: Tue, 20 Sep 2022 07:15:44 +0800 Subject: [PATCH] aaa0919 --- SafariChess_Gamev1.0.py | 111 ++++++++---- SafariChess_backend.py | 163 ++++++++++-------- .../SafariChess_Classes.cpython-39.pyc | Bin 836 -> 795 bytes .../SafariChess_backend.cpython-39.pyc | Bin 12075 -> 12416 bytes 4 files changed, 172 insertions(+), 102 deletions(-) diff --git a/SafariChess_Gamev1.0.py b/SafariChess_Gamev1.0.py index 8ea1d08..2a20cd4 100644 --- a/SafariChess_Gamev1.0.py +++ b/SafariChess_Gamev1.0.py @@ -11,6 +11,7 @@ it handle user input and GUI import colorsys import json +from multiprocessing import connection import pygame as pg import sys import socket @@ -34,10 +35,41 @@ SIZE = 64 IMAGES = {} bias_top = 100 #棋盘的上边距 bias_left = 100 #棋盘的左边距 - +#网络道具 +client = None +networkMsg = None +server = None +port = None +addr = None +connection = None #网络版更新需要注意多线程的问题? #尝试写一个函数分配一个新线程供监听 +def startNewThread(target): + thread = Thread(target=target) + thread.daemon =True + thread.start() +def listenFromServer(): + global networkMsg + try: + while True: + recvMsg = client.recvfrom(1024).decode('utf-8') + if len(recvMsg) != 0: + networkMsg = json.loads(recvMsg) + except socket.error as e: + print(e) + return e +def startNetworkServices(): + global client,server,port,addr,connection + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server = '127.0.0.1' + port = 50005 + addr = (server,port) + connection = client.connect(addr) + startNewThread(target=listenFromServer) + +def jsonAnalysis(networkMsg): + pass #等待敌人移动 def loadImages():#加载图片,a,b双方分别有象狮豹狼狐鹰鼠七个角色 @@ -200,7 +232,6 @@ def MainMenu(): button_3 = Button(bt3,(350,450)) button_4 = Button(bt4,(350,500)) mode = -1 - p1,p2 = 0,0 run = True while run: mainClock.tick(60) @@ -238,38 +269,26 @@ def MainMenu(): -def main(mode,p1,p2,network = None): - NewPlayerMessage = '' #来自对手的信息 - if mode == 2: - network=backend.Network() - #p1 = input("Enter your name: ") - p1 = 'player1' - myPlayerData = { - 'type': 0, # game state type ? - 'msg': { - 'name': player1 - } - } - network.send(myPlayerData) - print("Waiting for other player...") - NewPlayerMessage = network.receive() - print() - p2 = NewPlayerMessage['counterpart_name'] +def main(mode): + global networkMsg + networkMsg = None pg.init() screen = pg.display.set_mode((BOARD_WIDTH + MOVE_LOG_PANEL_WIDTH, BOARD_HEIGHT)) - pg.display.set_caption("Safafi Chess Game") + pg.display.set_caption("Safari Chess Game") clock = pg.time.Clock() screen.fill(pg.Color("white")) loadImages() drawBoard(screen) - isOnline = bool(network != None) - game_state=backend.GameState(isNet=isOnline,MySide=True if not isOnline else bool(NewPlayerMessage['side']),game_id=NewPlayerMessage['game_id'],client=network) + isOnline = bool(mode == 2) + game_state=backend.GameState() + #* cancelled args: MySide=True if not isOnline else bool(NewPlayerMessage['side']),game_id=NewPlayerMessage['game_id'] valid_moves=game_state.getAllMoves() running = True + other_joined = False mademove = False game_over = False - square_selected = ()#刚开始没有选择任何一个棋子 - click_queue = []#点击队列,记录第一次点击和第二次点击位置,方便移动棋子 + square_selected = () #刚开始没有选择任何一个棋子 + click_queue = [] #点击队列,记录第一次点击和第二次点击位置,方便移动棋子 pg.display.update() startGamePage(mode) @@ -319,7 +338,36 @@ def main(mode,p1,p2,network = None): clock.tick(60) pg.display.flip() elif mode == 2: + startNetworkServices() + myPlayerData = { + 'type': 0, # game state type ? + 'msg': { + 'name': 'myName' + } + } + login_packet = json.dumps(myPlayerData) + if (sys.version[:1] == '3'): + login_packet = login_packet.encode('utf-8') + lastNetworkMsg = networkMsg + client.send(login_packet) while running: + if lastNetworkMsg != networkMsg:#handle + print('get new msg: ',networkMsg) + lastNetworkMsg = networkMsg #networkMsg中保存当前字典 + if 'status' in networkMsg.keys(): + if networkMsg['status'] == 1: + print('Waiting for another player to connect') + + + elif 'src' and 'dst' in networkMsg.keys(): + if other_joined == False: + other_joined = True + print('Game start 2 play!') + else: + theMove = backend.Move([networkMsg['src']['x'],networkMsg['src']['y']],[networkMsg['dst']['x'],networkMsg['dst']['y']],game_state.board) + game_state.makeMove(theMove) + game_state.exchange() + for e in pg.event.get(): #接下来处理游戏中的事件 if e.type == pg.QUIT: @@ -358,24 +406,23 @@ def main(mode,p1,p2,network = None): #? 但是考虑到ui刷新问题,故仍尝试在main中写 #ShowGameState(screen,game_state,valid_moves,square_selected) if mademove: - mademove = False - game_state.updateEnemy()#思路变成:定时对敌方进行扫描,若收到更新相关包则进行局面更新。 + mademove = False + #思路变成:定时对敌方进行扫描,若收到更新相关包则进行局面更新。 + ShowGameState(screen,game_state,valid_moves,square_selected) if game_state.conquer(): showGameOverText(screen,"player "+game_state.win_person+" wins") game_over = True - clock.tick(60) + clock.tick(10) pg.display.flip() if __name__ == '__main__': print("Loading...") print("Initialing game...") - mode = MainMenu() - player1='player1' - network = None #前后端通信接口 - player2='player2' - main(mode,player1,player2,network) + #mode = MainMenu() + mode = 2 + main(mode) \ No newline at end of file diff --git a/SafariChess_backend.py b/SafariChess_backend.py index 33e2394..bcc9fbf 100644 --- a/SafariChess_backend.py +++ b/SafariChess_backend.py @@ -4,74 +4,134 @@ import requests import sys import json from threading import Thread +from queue import Queue #multithreading? + + +class Move: + def __init__(self,start_loc,end_loc,board): + self.start_row = start_loc[0] + self.start_col = start_loc[1] + self.end_row = end_loc[0] + self.end_col = end_loc[1] + self.nxt_piece = board[self.end_row][self.end_col] + self.cur_piece = board[self.start_row][self.start_col] + self.attack = self.nxt_piece != '00' + self.moveID = self.start_row + self.start_col*10 + self.end_row *100 +self.end_col * 1000 #Hash + def __eq__(self, __o: object) -> bool: + if isinstance(__o, Move): + return self.moveID == __o.moveID + return False def startNewThread(target): thread = Thread(target=target) thread.daemon = True thread.start() class Network: - def __init__(self): + def __init__(self,isNet=False): #注意:这里是否在传引用? self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server = "127.0.0.1" self.port = 50005 self.addr = (self.server, self.port) - self.msg = self.connect() - + #self.game_state = game_state #考虑将game_state接入Network以方便修改 + global msg_from_serv + thread = Thread(target = self.receive())#开线程始终监听 + thread.setDaemon(True) + thread.start() #专用数据接收线程 + + def getPos(self): return self.pos - + def connect(self): try: self.client.connect(self.addr) #return self.client.recv(2048).decode() except: pass - + def send(self, msg): try: packet = json.dumps(msg) if (sys.version[:1] == '3'): packet = packet.encode('utf-8') - #return self.client.recv(2048).encode() self.client.send(packet) print("send complete") + return self.client.recv(2048).encode() except socket.error as e: print(e) + return e def post(self, data): #using requests lib, data is json headers = {'Content-Type': 'application/json'} response = requests.post(url='http://localhost',data=data) return response - def receive(self): + def receive(self): #写成持续监听模式 try: - recvMsg = self.client.recv(2048).decode('utf-8') - msg = json.loads(recvMsg) - print('receiver got: ', msg) - return msg + while True: + recvMsg = self.client.recv(2048).decode('utf-8') + if len(recvMsg) != 0: + msg_from_serv = json.loads(recvMsg) + self.dataDealer(msg_from_serv) except socket.error as e: print(e) return e + + def dataDealer(self, msg_json): #根据相关数据对棋盘状态进行更新 + if 'status' in msg_json.keys(): + #处理相关特殊情况 + if msg_json['status'] == '1': + print('Waiting 4 the other player...') + + + elif 'src' and 'dst' in msg_json.keys(): #侦测到移动 + theMove = Move([msg_json['src']['x'],msg_json['src']['y']],[msg_json['dst']['x'],msg_json['dst']['y']],self.game_state.board) + self.game_state.makeMove(theMove,toUpload = False) + self.game_state.exchange() + + + def dataReceiver(self): + try: + while True: + msg = self.client.recv(2048).decode('utf-8') + if len(msg) != 0: self.dataDealer(json.loads(msg)) + + except socket.error as e: + print(e) + return e + + def tell_move(self, move: Move): + thisMove = { + 'type': 1, + 'msg':{ + "game_id": self.game_id, + "side": int(self.red_to_move), + "chessman": move.cur_piece, + "src": { + "x": move.start_row, + "y": move.start_col + }, + "dst": { + "x": move.end_row, + "y": move.end_col + } + } + } + self.client.send(thisMove) + + def login(self,myName): + myPlayerData = { + 'type': 0, # game state type ? + 'msg': { + 'name': myName + } + } + self.client.send(myPlayerData) +class GameState: -class Move: - def __init__(self,start_loc,end_loc,board): - self.start_row = start_loc[0] - self.start_col = start_loc[1] - self.end_row = end_loc[0] - self.end_col = end_loc[1] - self.nxt_piece = board[self.end_row][self.end_col] - self.cur_piece = board[self.start_row][self.start_col] - self.attack = self.nxt_piece != '00' - self.moveID = self.start_row + self.start_col*10 + self.end_row *100 +self.end_col * 1000 #Hash - def __eq__(self, __o: object) -> bool: - if isinstance(__o, Move): - return self.moveID == __o.moveID - return False -class GameState: - def __init__(self, isNet = False, MySide = True, - game_id = None, client:Network = None ): + def __init__(self): ''' 有关信息: 1. 棋盘尺寸为7*9(横向) @@ -107,51 +167,17 @@ class GameState: self.blue_pieces=[7,6,5,4,3,2,1] self.red_pieces=[7,6,5,4,3,2,1] #红方(右)先行 - self.isNet = isNet - self.game_id = game_id - self.red_to_move=MySide - self.MySide = MySide - self.client = client + self.red_to_move=True self.conquered=False self.win_person='' self.MASSACRE=False + self.isStarted = False def color(self): return 'r' if self.red_to_move else 'b' def exchange(self): - self.red_to_move = not self.red_to_move - #网络版相关组件 - def upldmv_server(self,move): - thisMove = { - 'type': 1, - 'msg':{ - "game_id": self.game_id, - "side": int(self.red_to_move), - "chessman": move.cur_piece, - "src": { - "x": move.start_row, - "y": move.start_col - }, - "dst": { - "x": move.end_row, - "y": move.end_col - } - } - } - self.client.send(thisMove) - def __updateOther__(self):#! 注意:考虑使用多线程执行该函数。 - #print('waiting 4 other player 2 act...') - thisMovejson = self.client.receive() - if not thisMovejson or thisMovejson['side'] == int(self.MySide): - return - print('passing checkpoint') - if 'chessman' in thisMovejson.keys(): #正常移动过程 - thisMove = Move([thisMovejson['src']['x'],thisMovejson['src']['y']],[thisMovejson['dst']['x'],thisMovejson['dst']['y']],self.board) - self.makeMove(thisMove,toUpload=False) - self.exchange() - - def updateEnemy(self): - startNewThread(self.__updateOther__)#测试多线程更新…… + self.red_to_move = not self.red_to_move + # 判断特殊位置 def inHome(self,row,col,color): if color=="b": @@ -347,7 +373,7 @@ class GameState: return self.conquered #移动操作 - def makeMove(self,move,toUpload = True):#cur是当前位置,nxt是下一个位置,参数传入为元组 + def makeMove(self,move):#cur是当前位置,nxt是下一个位置,参数传入为元组 # makeMove假设这个move一定是合法的 # 为什么添加toUpload?防止使用makeMove更新己方棋盘时,把敌方棋盘错误上传,导致“棋子消失”。 if self.board[move.end_row][move.end_col] != '00': @@ -360,8 +386,5 @@ class GameState: self.board[move.start_row][move.start_col] = '00' self.red_to_move = not self.red_to_move #由于使用多线程,考虑让另一个线程去更新red_to_move - if self.isNet and toUpload: - #因为界面更新在main中,考虑在main中执行相关指令。 - self.upldmv_server(move) diff --git a/__pycache__/SafariChess_Classes.cpython-39.pyc b/__pycache__/SafariChess_Classes.cpython-39.pyc index fc2a4cdd4ebffdd00dc2542e7c9237e45359cf37..741f4218a7cea57d68a3aae3362b9d09dd6a4d4d 100644 GIT binary patch delta 59 zcmX@YHk*w*k(ZZ?0SGn}Ds1GAW)%Bn?qU@aP?VpQnp_eQV`Su5T2h*uH+d=JY{pxY O9hqh^3QoSmlmP(zKN9Hx delta 104 zcmbQuc7%;Pk(ZZ?0SK5F#5ZzBGunTR1#;Z1VqUCU_AHC@wr`Db%*)J8%yG_0EiRs1!#JBUYO(~=EJlIJ@0r9W IpJECG09P|GPXGV_ diff --git a/__pycache__/SafariChess_backend.cpython-39.pyc b/__pycache__/SafariChess_backend.cpython-39.pyc index 699ae39d066129cdc3be73f18877fddc7328e07d..88bb4421204207ab9213579597c5967ded6a8fb6 100644 GIT binary patch delta 5424 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 delta 5003 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