def get_gamer_by_id(self, id_): gamer_index = self._GamerIdIndexMapping.get(id_, None) if gamer_index is None: logger.error('gamer.GamerGroup', f'No such a index mapping for gamer id {id_}') return None return self._Gamers[gamer_index]
def login_thread(self, socket_obj, con_addr): socket_obj.settimeout(None) # 先出于等待登录状态 while True: cmd, vals = recv_cmd(socket_obj, decode=True) if cmd == 'Login': try: user_name = vals['Username'] password = vals['Password'] passed, login_info = self.check_login(user_name, password) except KeyError: logger.error('server.login', 'Login指令中缺少 username 和 password 项!') continue send_cmd(socket_obj, command='Login', LoginStateCode=1 if passed else 0, LoginMessage=login_info, ID=id) if passed: logger.info('server.login', 'sending gamer info...') self.add_gamer(socket_obj, con_addr, user_name) # todo: 修改为Gamer实现 self.sendGamerInfo() return # 玩家主动或者被动放弃登录 elif cmd == 'ExitLogin': return
def handle_gamer_info(engine: ClientEngine, signals: ClientSignal, **kwargs): gamers = parse_gamer_info_command(kwargs) if gamers is None: logger.warning('client.handle_gamer_info' 'gamers were not extracted from command body') return engine.update_gamers(gamers)
def handle_common_chat(engine: ClientEngine, signals: ClientSignal, **kwargs): name, ID, content = parse_chat_command(kwargs) if name is None or content is None: logger.warning('client.handle_common_chat', f'no name or content extracted: {name},{content}') return engine.add_chat_message(name, content)
def handle_common_chat(server, **kwargs): name, ID, content = parse_chat_command(kwargs) if name is None or content is None: logger.warning('common_handlers.handle_common_chat', f'no name or content extracted: {name},{content}') return server.send_all_cmd(**make_chat_command(ID, name, content))
def listen_gamer_command(self, queue): self.Socket.settimeout(None) # todo:监听循环退出机制补充 while True: msg = self.recv_cmd(decode=False) if msg != b'': logger.debug('server.game_thread', 'receiving msg: {}'.format(msg)) queue.put(msg)
def __init__(self, pth): self.LoggedGamers = [] self.Lock = Lock() try: self.Accounts = loadJson(pth) except Exception as e: logger.critical('server.account', 'fail to load gamer account: %s' % str(e)) self.Accounts = {}
def game_thread(self, i): print('thread starting!') self.UsrSocket[i].settimeout(None) while True: msg = self.recv_cmd(i, False) if msg != b'': logger.debug('server.game_thread', 'receiving msg: {}'.format(msg)) self.CmdQueue.put(msg)
def decode_length(length_header): try: length_header = json.loads(length_header.decode(CodingFormat)) except json.JSONDecodeError as e: logger.error( 'protocol.decode_length', '[decode_length] Fail to decode length header: {}'.format(e)) return 0 return int(length_header.get('len', 0))
def handle_inform(engine: ClientEngine, signals: ClientSignal, **kwargs): try: # inform = extract_kwargs(kwargs, ('Content', 'content'), 'client.handlers.handle_inform') inform = parse_inform_command(kwargs) if inform is None: return engine.update_inform(inform) except Exception as e: logger.error('client.handlers.handle_inform', 'err: {}'.format(e))
def handle_host_begin_game_cmd(self, host_index=0): logger.debug('handle_host_cmd', 'host handling threading start!') while True: if len(self.UnloggedGamers) != 0: cmd, body = self.UnloggedGamers[host_index].recv_cmd() logger.debug('handle_host_cmd', f'cmd: {cmd}, body:{body}') handler = get_handler(S_LOGIN_STATE, cmd, supress_log=True) handler(self, gamer=self.UnloggedGamers[host_index], **body) # 该线程如果接收到了主机有关游戏开始的命令后就退出 if cmd == CMD_BEGIN_GAME: return else: time.sleep(1)
def run(self): logger.info('Server.run', 'Server is running now') self.login_state() # 对所有gamer,启动监听其请求的线程 for g in self.Gamers: t = threading.Thread(target=g.listen_gamer_command, args=(self.CmdQueue, ), daemon=True) self.GamerCmdListenThreads.append(t) for t in self.GamerCmdListenThreads: t.start() self.game_state()
def handle_make_puzzle(server, **kwargs): answer, hint = parse_make_puzzle_command(kwargs) # 将谜底和提示交给游戏逻辑实体管理 server.GameLogic.set_answer_hint(answer, hint) # 启动本轮游戏的倒计时计时器,每一秒在命令队列中加入一条tick指令 server.TimerManager.start_gametime_count_timer( timer_name=GAME_DOWNCOUNT_TIMER, msg_queue=server.CmdQueue, time_interval=1, max_ticks=config.game.RoundTime) # 将提示发送给所有玩家 server.send_all_cmd(**make_inform_command(f'提示: {hint}')) logger.info('game_handlers.handle_make_puzzle', f'puzzle has been made, answer: {answer}, hint:{hint}')
def wait_for_ready(self): while True: try: cmd, vals = self.Engine.recv_cmd() logger.debug('client.core.wait_for_ready', 'recv cmd: {}, {}'.format(cmd, vals)) handler = get_handler(C_WAIT_FOR_READY_STATE, cmd) ret = handler(self.Engine, self.Signals, **vals) if ret is not None and check_game_is_begin(ret): break except Exception as e: logger.error( 'client.core.activate', 'err when handling, cmd: {}, vals: {}, err: {}'.format( cmd, vals, e)) self.game()
def decode_msg(raw_msg_body, raise_exception=False) -> (str, dict): try: msg_body = json.loads(raw_msg_body) command = msg_body.pop('command') return command, msg_body except json.JSONDecodeError as e: if raise_exception: raise DecodeError(raw_msg_body, str(e)) logger.error('protocol.decode_msg', 'Fail to decode msg body: {}'.format(e)) return 'unknown', {} except Exception as ue: if raise_exception: raise DecodeError(raw_msg_body, str(ue)) logger.error('protocol.decode_msg', 'unknown err: {}, raw_msg: {}'.format(ue, msg_body)) return 'unknown', {}
def handle_game_chat(server, **kwargs): # 如果所有玩家都猜对,则终止当前游戏循环的回调函数 name, id_, cont = parse_chat_command(kwargs) is_answered, answered_num = server.GameLogic.process_answer( answer=cont, answer_gamer_id=id_, gamer_group=server.Gamers) # 如果猜对,则发送服务器通告所有玩家,不会告知聊天信息 if is_answered: logger.info('game_handlers.handle_game_chat', f'gamer {name} has answered the question') server_chat_msg = server.ServerMessage.make_gamer_answered_inform_message( name) server.send_all_cmd(**make_chat_command( id_=-1, name=SERVER_CHAT_NAME, content=server_chat_msg)) # 刷新分数 server.send_all_cmd( **make_gamer_info_command(server.Gamers.pack_all_gamers_info())) # 否则将chat视为一般的聊天,广播给所有玩家 else: server.send_all_cmd( **make_chat_command(id_=id_, name=name, content=cont)) # 如果所有玩家都猜对了 if answered_num == len(server.Gamers): logger.info('game_handlers.handle_game_chat', f'all gamers have answered the question, this round ends') # 1. 终止游戏倒计时 server.TimerManager.stop_timers_by_name(GAME_DOWNCOUNT_TIMER) cur_gamer = server.Gamers.get_gamer_by_id( server.GameLogic.CurrentPaintingGamerId) # 2. 向当前画图玩家发出停止画图的指令,向所有玩家发出当前游戏round结束的通告 cur_gamer.send_cmd(**make_stop_paint_command()) server.send_all_cmd(**make_inform_command( server.ServerMessage.make_all_gamers_answered_message( server.GameLogic.Answer))) # 3. 设置flag终止消息监听循环,同时放入一条信息刷新循环判断 server.MessageLoopFlag = False # 只取解码信息的body,不取包含body大小的头部信息,因为是本地的直接指令存入 break_msg_loop_cmd, _ = encode_msg(**make_break_message_loop_command()) server.CmdQueue.put(break_msg_loop_cmd) # 4. 将答案置为无效,防止其他玩家在谜底揭晓处于无效状态时进行答题 server.GameLogic.set_answer_valid_state(False)
def accept_new_gamer(self): ''' 使用欢迎套接字来接受玩家连接,同时监听Host的开始游戏命令来终止 接受更多的玩家连接 :return: 玩家连接的套接字和地址,如果游戏开始则返回None ''' self.WelcomeSocket.settimeout( config.server.HostCommandInterval) # 设置accept退出的检查周期 while self.GameBeginFlag.get_val(): try: con_socket, addr = self.WelcomeSocket.accept() logger.info('Server.login', 'new gamer connecting...') gamer = UnloggedGamer(len(self.UnloggedGamers), addr, con_socket) # 接受到新玩家以后加入到gamer列表中 # 由于accept套接字的时候是串行的,因此不需要互斥读写未登录玩家列表 self.UnloggedGamers.append(gamer) return gamer # socket超时后检查游戏开始的全局flag是否被修改 except socket.timeout: pass return None
def handle_gamer_login(server, **kwargs): gamer = extract_kwargs(kwargs, ('Gamer', 'gamer'), 'server.login_handler') # 首先检查玩家数量是否超过限制 with server.Gamers as gp: is_gamer_enough = gp.check_gamer_is_enough() if is_gamer_enough: fail_cmd = make_login_result_command(status_code=0, login_message='玩家数量超过限制', ID=-1) gamer.send_cmd(**fail_cmd) return username, passwd = parse_login_command(kwargs) passed, login_msg, gamer_id = server.GamerAccount.check_gamer_login( username, passwd) cmd = make_login_result_command(status_code=1 if passed else 0, login_message=login_msg, ID=gamer_id) logger.debug('handlers.handle_gamer_login', f'send msg: {cmd}') if passed: from server.gamer import Gamer # 逻辑内部import,防止循环import问题 new_gamer = Gamer(gamer, gamer_id, username) with server.Gamers as gp: # 首先发送登录结果信息 gamer.send_cmd(**cmd) gp.add_gamer(new_gamer, gamer_id, safe_call=True) # 然后发送所有玩家信息,更新玩家情况 gamers_info = gp.pack_all_gamers_info() server.send_all_cmd(**make_gamer_info_command(gamers_info)) # 登录成功后将flag标志为True告知外部可以退出登录循环 gamer.LoginFlag.write_val(True) logger.info('server.login', 'gamer {}:{} login'.format(gamer_id, username)) else: # 不通过也要发送登录结果 gamer.send_cmd(**cmd)
def login(self): try: usrName, password = self.getUsrAndPsw() logger.info('LoginPanel.login', 'username: {}, psw: {}'.format(usrName, password)) self.send_cmd(**make_login_command(usrName, password)) # self.send_cmd(command='Login', Username=usrName, Password=password) # 收取服务器的回复 while True: cmd, body = self.recv_cmd() if cmd == CMD_LOGIN_RESULT: break else: logger.warning( 'LoginPanel.login', 'recv {} cmd with {}, skip'.format(cmd, body)) time.sleep(1) logger.info('LoginPanel.login', 'login success: {}'.format(body)) # login_status_code = body['LoginStateCode'] # login_info = body['LoginMessage'] login_status_code, login_info, ID = parse_login_result_command( body) self.ClientId = ID if login_status_code == 1: self.close() logger.info('LoginPanel.login', 'activating...') self.Activator(self.Socket, self.ClientId, self.UsrName.text()) else: widgets.QMessageBox.warning(self, '登陆失败', login_info) # self.UsrName.clear() self.Psw.clear() except Exception as e: logger.error('LoginPanel.login', 'Fail to login, err: {}'.format(e)) raise e
def process_answer(self, answer, answer_gamer_id, gamer_group): logger.debug('GameLogic.process_answer', f'state: {self.IsAnswerValid}, {answer} : {self.Answer}') # 问题处于无效状态时不做判断 if not self.IsAnswerValid: return False, len(self.AnsweredGamerIds) if not self.check_gamer_is_answered( answer_gamer_id) and self.check_answer(answer): ans_gamer = gamer_group.get_gamer_by_id(answer_gamer_id) pat_gamer = gamer_group.get_gamer_by_id( self.CurrentPaintingGamerId) logger.info( 'handlers.handle_game_chat', f'gamer {ans_gamer.UserName} has answered this puzzle') # 将答对的玩家的id加入到已完成回答的列表中 self.add_answered_gamer_id(answer_gamer_id) # 答题玩家和画图玩家都得分 ans_gamer.score(self.get_next_point()) pat_gamer.score(config.game.DrawPoint) return True, len(self.AnsweredGamerIds) else: return False, len(self.AnsweredGamerIds)
def activate(self, socket_obj, id_, usrname): try: self.Signals = ClientSignal() self.init_slots() self.Engine = ClientEngine(self.Signals, socket_obj) self.Engine.set_gamer_name_id(id_, usrname) logger.info('client.activate', 'activating...') cmd, gamers = self.Engine.recv_cmd() while cmd != 'GamerInfo': time.sleep(1) # 忽略其他指令 cmd, gamers = self.Engine.recv_cmd() for gname, gscore in gamers['gamers']: self.Engine.add_gamer(gname) # 启动一个额外的线程用于background任务 self.GameThread.start() self.Engine.show() except Exception as e: logger.critical('client.core.activate', 'Err when activate: {}'.format(e)) raise e
def accept_and_monitor_host(self): ''' 使用欢迎套接字来接受玩家连接,同时监听Host的开始游戏命令来终止 接受更多的玩家连接 :return: 玩家连接的套接字和地址,如果游戏开始则返回None ''' # 内层循环不断在监听主机命令和监听玩家连接之间循环 while True: try: # print('accepting...')\ con_socket, addr = self.Socket.accept() print('new gamer connected...') # 接受到新玩家以后,本轮循环不再监听主机命令 return con_socket, addr # 每3秒就切换到监听主机命令 except socket.timeout: if self.CntedUsrNumber != 0: self.UsrSocket[0].settimeout( config.server.HostCommandInterval) try: host_cmd, host_cmd_vals = self.recv_cmd(0, decode=True) # 如果主机发出开始游戏命令,则返回None代表不再接受玩家连接 if host_cmd == 'BeginGame': logger.info('server.accept', 'Game has begun!') self.send_all_cmd(command='BeginGame') # for i, s_ in enumerate(self.UsrSocket): # self.send_cmd(i, command='BeginGame') return None, None else: logger.warning( 'server.accept', f'Not accepted command type during login waiting: {host_cmd}, ignore' ) except socket.timeout: pass
def get_handler(state, cmd): logger.debug('client.handlers', 'getting handler of {}'.format(cmd)) try: handler = __handler_switch[state][cmd] return handler except KeyError: logger.error('client.handlers.get_handler', 'can not get handler for {}.{}'.format(state, cmd)) return handle_none except Exception as e: logger.error('client.handlers.get_handler', 'unknown err: {}'.format(e)) return handle_none
def get_handler(state, cmd, supress_log=False): logger.debug('server.handlers', 'getting handler of {}'.format(cmd)) try: handler = __handler_switch[state][cmd] return handler except KeyError: if not supress_log: logger.warning( 'server.handlers.get_handler', 'can not get handler for {c} in {s}'.format(c=cmd, s=state)) return handle_none except Exception as e: if not supress_log: logger.error('server.handlers.get_handler', 'unknown err: {}'.format(e)) return handle_none
def game(self): while not self.Engine.GameEndFlag: try: cmd, vals = self.Engine.recv_cmd() logger.info('client.core.game', f'cmd: {cmd}, vals: {vals}') handler = get_handler(C_GAME_STATE, cmd) handler(self.Engine, self.Signals, **vals) logger.info('client.core.game', f'cmd: {cmd} executed') except BaseException as e: logger.error('client.core.game', 'err when game: {}'.format(e)) self.exit()
def game_state(self): logger.debug('server.gamer_state', 'entering game state') # 初始化游戏逻辑 self.GameLogic = GameLogic(len(self.Gamers)) for round_index in range(self.GameRoundPerGamer): # 游戏循环次数等于玩家数量 for cur_gamer_index in range(len(self.Gamers)): cur_gamer = self.Gamers[cur_gamer_index] self.GameLogic.init_game_state(cur_gamer.Id) self.send_all_cmd(**make_newround_command()) # 将当前出题者加入到已回答玩家列表中,防止其自己猜自己 self.GameLogic.add_answered_gamer_id(cur_gamer.Id) # 发送开始画图和通告画图者的通知 paint_message = self.ServerMessage.make_paint_inform_message( cur_gamer.UserName) self.send_all_cmd(**make_inform_command(paint_message)) # 当前画图者发出开始画图指令 cur_gamer.send_cmd(**make_begin_paint_command()) # 进入指令处理循环 self.MessageLoopFlag = True while self.MessageLoopFlag: msg = self.CmdQueue.get() # 阻塞队列,处理接受到的命令 try: cmd, cmd_body = decode_msg(msg, raise_exception=True) handler = get_handler(S_GAME_STATE, cmd) handler(self, cur_gamer=cur_gamer, raw_message=msg, **cmd_body) except DecodeError as de: logger.error( 'server.game_state', f'decoding error in game message loop: {de}') except Exception as e: she = ServerHandlingError(cmd, cmd_body, e) logger.error( 'server.game_state', f'unknown error when handling in game state: {she}' ) # 关闭游戏 self.close()
def handle_begin_paint(engine: ClientEngine, signals: ClientSignal, **kwargs): try: engine.Panel.set_painting(True) engine.Panel.State = 'painting' logger.info('client.handlers.handle_begin_paint', 'getting answer and hint') answer = engine.Panel.get_input_by_dialog('出题', '请输入谜底', '谜底不能为空', True, lambda s: len(s) <= 20, '谜底长度不能超过20个字符') hint = engine.Panel.get_input_by_dialog('出题', '请输入提示', '', False, lambda s: len(s) <= 20, '提示长度不能超过20个字符') logger.info('client.handlers.handle_begin_paint', '谜题: {}, 提示: {}'.format(answer, hint)) # engine.send_cmd(command=CMD_BEGIN_PAINT, answer=answer, hint=hint) engine.send_cmd(**make_make_puzzle_command(answer, hint)) # 只有要画图的人才能看到设置面板 engine.Panel.PaintPanel.set_setting_visible(True) except BaseException as e: logger.error('client.handlers.handle_begin_paint', 'err: {}'.format(e))
def send_cmd(self, i, command, **kwargs): logger.debug('server.send_cmd', 'send cmd: {}, vals: {}'.format(command, kwargs)) send_cmd(self.UsrSocket[i], command, **kwargs)
def handle_game_begin(server, **kwargs): logger.info('server.handle_game_begin', 'game is begun') # 设置游戏开始flag,停止接受更多玩家连接 server.GameBeginFlag.write_val(False) # 向所有玩家发送游戏开始指令 server.send_all_cmd_unlogged(**make_begin_game_command())
def game_state_(self): print('entering game state') for cur_gamer_index in range(self.CntedUsrNumber): # 每开始一轮游戏,先调用初始化方法 self.initGameState() # 向所有玩家发出新一轮游戏的指令来初始化 self.send_all_cmd(command='NewRound') # 将画图者添加到已完成玩家列表中,防止其自己猜自己 self.AnsweredGamer.append(cur_gamer_index) # 发送游戏提示 for i, s_ in enumerate(self.UsrSocket): self.send_cmd(i, command='Inform', Content='现在由 {name} 画图'.format( name=self.UsrName[cur_gamer_index])) # time.sleep(1) # 向当前画图玩家发出开始画图的指令 self.send_cmd(cur_gamer_index, command='BeginPaint') # # 接受画图玩家出的题 # cmd, vals = decode_msg(self.recv_cmd(cur_gamer_index)) # if cmd print('thread starting from parent thread!') while True: msg = self.CmdQueue.get() # 阻塞队列,处理接受到的命令 cmd, vals = decode_msg(msg) if cmd != '': logger.info('server.game', 'cmd: {}, vals: {}'.format(cmd, vals)) if cmd == 'BeginPaint': self.Answer = vals['answer'] self.Hint = vals['hint'] # 启动倒计时定时器 self.\ startTimer(timerId=0, downCount=int(config.game.RoundTime), interval=1) print('timer starting!') elif cmd == 'Chat': usr_id = int(vals['id']) content = vals['content'] if usr_id not in self.AnsweredGamer and self.checkAnswer( content): # 一旦玩家的答案正确 print('用户%d的答案%s正确' % (usr_id, content)) self.AnsweredGamer.append(usr_id) self.gamerScore(hid=cur_gamer_index, uid=usr_id) self.sendGamerInfo() self.send_all_cmd(command='Chat', Name='服务器', Content='%s 已经猜对了答案' % self.UsrName[usr_id]) print('游戏进度:', len(self.AnsweredGamer), len(self.UsrPoint)) # 如果所有玩家都猜对了 if len(self.AnsweredGamer) == len(self.UsrPoint): self.stopTimer(0) # 当前玩家停止画图 self.send_cmd(cur_gamer_index, command='StopPaint') # 跳到下一个玩家 break # 只有不是正确答案的聊天才会被公布 else: self.send_all_cmd(command='Chat', **vals) elif cmd == 'Timeout': # for i in range(len(self.UsrSocket)): # self.send_cmd(i, command='Inform', Content='时间到') self.send_all_cmd(command='Inform', Content='时间到') self.send_cmd(cur_gamer_index, command='StopPaint') time.sleep(2) break elif cmd == 'PaintPoint' or \ cmd == 'ClickPoint': for i_, s_ in enumerate(self.UsrSocket): if i_ != cur_gamer_index: self.send_cmd(i_, command=cmd, **vals) elif cmd == 'TimerEvent': for i_, s_ in enumerate(self.UsrSocket): self.send_cmd(i_, command=cmd, **vals) elif cmd == 'SettingChanged': for i_, s_ in enumerate(self.UsrSocket): if i_ != cur_gamer_index: self.send_cmd(i_, command=cmd, **vals) self.send_all_cmd(command='EndGame')