async def suicide( *, db: Session = Depends(deps.get_db), current_user: User = Depends(deps.get_current_active_user), ): my_role = db.query(Role).get(current_user.uid) if not my_role.alive: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() game = db.query(Game).with_for_update().get(current_user.gid) if game.status is not GameEnum.GAME_STATUS_DAY: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() now = game.current_step() if now not in [GameEnum.TURN_STEP_ELECT_TALK, GameEnum.TURN_STEP_TALK]: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if now is GameEnum.TURN_STEP_ELECT_TALK: game.steps = [GameEnum.TURN_STEP_UNKNOWN, GameEnum.TURN_STEP_ANNOUNCE, GameEnum.TURN_STEP_USE_SKILLS] game.now_index = 0 elif now is GameEnum.TURN_STEP_TALK: game.steps = [GameEnum.TURN_STEP_UNKNOWN, GameEnum.TURN_STEP_USE_SKILLS] game.now_index = 0 publish_history(game.gid, f'{my_role.position}号玩家自爆了') game._kill(db, my_role.position, GameEnum.SKILL_SUICIDE) # try: # except GameFinished: # pass # todo game finished, or global except? ret = game.move_on(db) db.commit() return ret
async def shoot( *, db: Session = Depends(deps.get_db), current_user: User = Depends(deps.get_current_active_user), target: int ): my_role = db.query(Role).get(current_user.uid) if not my_role.alive: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if target == my_role.position: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() game = db.query(Game).with_for_update().get(current_user.gid) now = game.current_step() if now is not GameEnum.TURN_STEP_USE_SKILLS: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if not my_role.args['shootable'] or str(my_role.position) not in game.history['dying']: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if target > 0: target_role = game.get_role_by_pos(db, target) if not target_role.alive or str(target) in game.history['dying']: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() my_role.args['shootable'] = False publish_history(game.gid, f'{my_role.position}号玩家发动技能“枪击”,击倒了{target}号玩家') game._kill(db, target, GameEnum.SKILL_SHOOT) db.commit() return GameEnum.OK.digest()
async def handover( *, db: Session = Depends(deps.get_db), current_user: User = Depends(deps.get_current_active_user), target: int ): my_role = db.query(Role).get(current_user.uid) if not my_role.alive: logging.info('my_role is not alive') return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if target == my_role.position: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() game = db.query(Game).with_for_update().get(current_user.gid) now = game.current_step() if now is not GameEnum.TURN_STEP_USE_SKILLS: logging.info(f'wrong now step:{now.label}') return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if str(my_role.position) not in game.history['dying']: logging.info(f'not in dying: my position={my_role.position},dying={game.history["dying"]}') return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if game.captain_pos != my_role.position: logging.info(f'I am not captain, my position={my_role.position},captain pos={game.captain_pos}') return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if target > 0: target_role = game.get_role_by_pos(db, target) if not target_role.alive: logging.info(f'target not alive, target={target}') return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() game.captain_pos = target db.commit() publish_history(game.gid, f'{my_role.position}号玩家将警徽移交给了{target}号玩家') return GameEnum.OK.digest()
async def elect( *, db: Session = Depends(deps.get_db), current_user: User = Depends(deps.get_current_active_user), choice: str ): my_role = db.query(Role).get(current_user.uid) if not my_role.alive: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() game = db.query(Game).with_for_update().get(current_user.gid) now = game.current_step() if choice in ['yes', 'no'] and now is not GameEnum.TURN_STEP_ELECT: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if choice == 'quit' and now is not GameEnum.TURN_STEP_ELECT_TALK: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if choice in ['yes', 'no'] and (GameEnum.TAG_ELECT in my_role.tags or GameEnum.TAG_NOT_ELECT in my_role.tags): return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if choice == 'quit' and (GameEnum.TAG_ELECT not in my_role.tags or GameEnum.TAG_GIVE_UP_ELECT in my_role.tags): return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() ret = None if choice == 'yes': my_role.tags.append(GameEnum.TAG_ELECT) elif choice == 'no': my_role.tags.append(GameEnum.TAG_NOT_ELECT) elif choice == 'quit': publish_history(game.gid, f'{my_role.position}号玩家退水') my_role.tags.remove(GameEnum.TAG_ELECT) my_role.tags.append(GameEnum.TAG_GIVE_UP_ELECT) votee = game.history['voter_votee'][1] votee.remove(my_role.position) if len(votee) == 1: while game.now_index + 1 < len(game.steps) and game.steps[game.now_index + 1] in {GameEnum.TURN_STEP_ELECT_TALK, GameEnum.TURN_STEP_ELECT_VOTE}: # noqa E501 game.steps.pop(game.now_index + 1) captain_pos = votee[0] game.captain_pos = captain_pos publish_history(game.gid, f'仅剩一位警上玩家,{captain_pos}号玩家自动当选警长') ret = game.move_on(db) else: raise ValueError(f'Unknown choice: {choice}') db.commit() return ret or GameEnum.OK.digest()
async def game_finished_handler(request: Request, finish: GameFinished): try: db = finish.db db.commit() publish_history(finish.gid, f'游戏结束,{finish.winner.label}胜利') game = db.query(Game).with_for_update().get(finish.gid) game.status = GameEnum.GAME_STATUS_FINISHED original_players = game.players game.init_game() game.players = original_players all_players = db.query(Role).filter(Role.gid == game.gid).all() for p in all_players: p.reset() db.commit() except SQLAlchemyError: db.rollback() raise finally: db.rollback() db.close() return GameEnum.OK.digest()
async def deal( *, db: Session = Depends(deps.get_db), current_user: User = Depends(deps.get_current_active_user), ): game = db.query(Game).with_for_update().get(current_user.gid) if not game or datetime.utcnow() > game.end_time: return GameEnum.GAME_MESSAGE_CANNOT_START.digest() if game.status is not GameEnum.GAME_STATUS_WAIT_TO_START: return GameEnum.GAME_MESSAGE_CANNOT_START.digest() players_cnt = len(game.players) if players_cnt != game.get_seats_cnt(): return GameEnum.GAME_MESSAGE_CANNOT_START.digest() players = db.query(Role).filter_by(gid=game.gid).limit(players_cnt).all() if len(players) != players_cnt: return GameEnum.GAME_MESSAGE_CANNOT_START.digest() for p in players: if p.uid not in game.players: return GameEnum.GAME_MESSAGE_CANNOT_START.digest() if set([p.position for p in players]) != set(range(1, players_cnt + 1)): return GameEnum.GAME_MESSAGE_CANNOT_START.digest() # fine to deal game.status = GameEnum.GAME_STATUS_READY players.sort(key=lambda p: p.position) game.players = [p.uid for p in players] cards = game.cards.copy() random.shuffle(cards) for p, c in zip(players, cards): p.role_type = c p.prepare(game.captain_mode) db.commit() publish_info(game.gid, json.dumps({ 'action': 'getGameInfo' })) publish_history(game.gid, "身份牌已发放") return GameEnum.OK.digest()
def _enter_step(self, db: Session) -> GameEnum: now = self.current_step() if now is GameEnum.TURN_STEP_TURN_NIGHT: self.status = GameEnum.GAME_STATUS_NIGHT # for d in self.history['dying']: # role = db.query(Role).filter(Role.gid == self.gid, Role.position == d).limit(1).first() # role.alive = False self.reset_history() publish_music(self.gid, 'night_start_voice', 'night_bgm', True) publish_info( self.gid, json.dumps({ 'game': { 'days': self.days, # 'status': self.status.value }, 'mutation': 'SOCKET_GAME' })) publish_history(self.gid, ('***************************\n' '<pre> 第{}天 </pre>\n' '***************************').format(self.days), show=False) return GameEnum.STEP_FLAG_AUTO_MOVE_ON elif now is GameEnum.TAG_ATTACKABLE_WOLF: publish_music(self.gid, 'wolf_start_voice', 'wolf_bgm', True) all_players = db.query(Role).filter(Role.gid == self.gid, Role.alive == int(True)).limit( len(self.players)).all() for p in all_players: if GameEnum.TAG_ATTACKABLE_WOLF in p.tags: break else: pass # todo # scheduler.add_job(id=f'{self.gid}_WOLF_KILL_{self.step_cnt}', func=Game._timeout_move_on, # args=(self.gid, self.step_cnt), # next_run_time=datetime.now() + timedelta(seconds=random.randint(GameEnum.GAME_TIMEOUT_RANDOM_FROM.label, GameEnum.GAME_TIMEOUT_RANDOM_TO.label))) # noqa E501 return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now in [GameEnum.TURN_STEP_TALK, GameEnum.TURN_STEP_ELECT_TALK]: return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_ELECT: publish_music(self.gid, 'elect', None, False) publish_history(self.gid, '###上警阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_VOTE: self.history['vote_result'] = {} voters = [] votees = [] roles = db.query(Role).filter(Role.gid == self.gid).limit( len(self.players)).all() for r in roles: if not r.alive: continue votees.append(r.position) if r.voteable: voters.append(r.position) self.history['voter_votee'] = [voters, votees] publish_history(self.gid, '###投票阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_ELECT_VOTE: self.history['vote_result'] = {} publish_history(self.gid, '###警长投票阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_PK_VOTE: self.history['vote_result'] = {} publish_history(self.gid, '###PK投票阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_ELECT_PK_VOTE: self.history['vote_result'] = {} publish_history(self.gid, '###警长PK投票阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_ANNOUNCE: if self.history['dying']: publish_history( self.gid, '昨晚,以下位置的玩家倒下了,不分先后:{}'.format(','.join( [str(d) for d in sorted(self.history['dying'])]))) else: publish_history(self.gid, "昨晚是平安夜") while self.now_index + 1 < len( self.steps) and self.steps[self.now_index + 1] in [ GameEnum.TURN_STEP_USE_SKILLS, GameEnum.TURN_STEP_LAST_WORDS ]: # noqa E501 self.steps.pop(self.now_index + 1) return GameEnum.STEP_FLAG_AUTO_MOVE_ON elif now is GameEnum.TURN_STEP_TURN_DAY: self.status = GameEnum.GAME_STATUS_DAY publish_music(self.gid, 'day_start_voice', 'day_bgm', False) self._calculate_die_in_night(db) publish_info( self.gid, json.dumps({ 'game': { 'days': self.days, # 'status': self.status.value }, 'mutation': 'SOCKET_GAME' })) return GameEnum.STEP_FLAG_AUTO_MOVE_ON elif now is GameEnum.ROLE_TYPE_SEER: publish_music(self.gid, 'seer_start_voice', 'seer_bgm', True) seer_cnt = db.query(func.count(Role.uid)).filter( Role.gid == self.gid, Role.alive == int(True), Role.role_type == GameEnum.ROLE_TYPE_SEER).scalar() if seer_cnt == 0: pass # todo # scheduler.add_job(id=f'{self.gid}_SEER_{self.step_cnt}', func=Game._timeout_move_on, # args=(self.gid, self.step_cnt), # next_run_time=datetime.now() + timedelta(seconds=random.randint(GameEnum.GAME_TIMEOUT_RANDOM_FROM.label, GameEnum.GAME_TIMEOUT_RANDOM_TO.label))) # noqa E501 return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.ROLE_TYPE_WITCH: publish_music(self.gid, 'witch_start_voice', 'witch_bgm', True) witch_cnt = db.query(func.count(Role.uid)).filter( Role.gid == self.gid, Role.alive == int(True), Role.role_type == GameEnum.ROLE_TYPE_WITCH).scalar() if witch_cnt == 0: pass # todo # scheduler.add_job(id=f'{self.gid}_WITCH_{self.step_cnt}', func=Game._timeout_move_on, # args=(self.gid, self.step_cnt), # next_run_time=datetime.now() + timedelta(seconds=random.randint(GameEnum.GAME_TIMEOUT_RANDOM_FROM.label, GameEnum.GAME_TIMEOUT_RANDOM_TO.label))) # noqa E501 return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.ROLE_TYPE_SAVIOR: publish_music(self.gid, 'savior_start_voice', 'savior_bgm', True) savior_cnt = db.query(func.count(Role.uid)).filter( Role.gid == self.gid, Role.alive == int(True), Role.role_type == GameEnum.ROLE_TYPE_SAVIOR).scalar() if savior_cnt == 0: pass # todo # scheduler.add_job(id=f'{self.gid}_SAVIOR', func=Game._timeout_move_on, # args=(self.gid, self.step_cnt), # next_run_time=datetime.now() + timedelta(seconds=random.randint(GameEnum.GAME_TIMEOUT_RANDOM_FROM.label, GameEnum.GAME_TIMEOUT_RANDOM_TO.label))) # noqa E501 return GameEnum.STEP_FLAG_WAIT_FOR_ACTION
def _leave_step(self, db: Session) -> ResponseBase: now = self.current_step() if now is None: return GameEnum.OK.digest() if now is GameEnum.TURN_STEP_ELECT: roles = db.query(Role).filter(Role.gid == self.gid).limit( len(self.players)).all() for r in roles: if GameEnum.TAG_ELECT not in r.tags and GameEnum.TAG_NOT_ELECT not in r.tags: r.tags.append(GameEnum.TAG_NOT_ELECT) voters = [] votees = [] for r in roles: if not r.alive: continue if GameEnum.TAG_ELECT in r.tags: votees.append(r.position) else: voters.append(r.position) voters.sort() votees.sort() if not voters or not votees: # no captain while self.now_index + 1 < len( self.steps) and self.steps[self.now_index + 1] in { GameEnum.TURN_STEP_ELECT_TALK, GameEnum.TURN_STEP_ELECT_VOTE }: # noqa E501 self.steps.pop(self.now_index + 1) if not voters: publish_history(self.gid, '所有人都竞选警长,本局游戏无警长') else: publish_history(self.gid, '没有人竞选警长,本局游戏无警长') elif len(votees) == 1: # auto win captain while self.now_index + 1 < len( self.steps) and self.steps[self.now_index + 1] in { GameEnum.TURN_STEP_ELECT_TALK, GameEnum.TURN_STEP_ELECT_VOTE }: # noqa E501 self.steps.pop(self.now_index + 1) captain_pos = votees[0] self.captain_pos = captain_pos publish_history(self.gid, f'只有{captain_pos}号玩家竞选警长,自动当选') else: publish_history( self.gid, f"竞选警长的玩家为:{','.join(map(str,votees))}\n未竞选警长的玩家为:{','.join(map(str,voters))}" ) self.history['voter_votee'] = [voters, votees] return GameEnum.OK.digest() elif now in [ GameEnum.TURN_STEP_VOTE, GameEnum.TURN_STEP_ELECT_VOTE, GameEnum.TURN_STEP_PK_VOTE, GameEnum.TURN_STEP_ELECT_PK_VOTE ]: msg = "" announce_result = collections.defaultdict(list) ticket_cnt = collections.defaultdict(int) forfeit = [] most_voted = [] max_ticket = 0 for voter_pos, votee_pos in self.history['vote_result'].items(): voter_pos = int(voter_pos) votee_pos = int(votee_pos) if votee_pos in [ GameEnum.TARGET_NOT_ACTED.value, GameEnum.TARGET_NO_ONE.value ]: forfeit.append(voter_pos) continue announce_result[votee_pos].append(voter_pos) ticket_cnt[votee_pos] += 1 if voter_pos == self.captain_pos: ticket_cnt[votee_pos] += 0.5 for voter in self.history['voter_votee'][0]: if str(voter) not in self.history['vote_result']: forfeit.append(voter) forfeit.sort() if forfeit and now in [ GameEnum.TURN_STEP_PK_VOTE, GameEnum.TURN_STEP_ELECT_PK_VOTE ]: return GameEnum.GAME_MESSAGE_NOT_VOTED_YET.digest(*forfeit) for votee, voters in sorted(announce_result.items()): msg += '{} <= {}\n'.format(votee, ','.join(map(str, voters))) if forfeit: msg += '弃票:{}\n'.format(','.join(map(str, forfeit))) if not ticket_cnt: most_voted = self.history['voter_votee'][1] else: ticket_cnt = sorted(ticket_cnt.items(), key=lambda x: x[1], reverse=True) most_voted.append(ticket_cnt[0][0]) max_ticket = ticket_cnt[0][1] for votee, ticket in ticket_cnt[1:]: if ticket == max_ticket: most_voted.append(votee) else: break most_voted.sort() if len(most_voted) == 1: if now in [ GameEnum.TURN_STEP_VOTE, GameEnum.TURN_STEP_PK_VOTE ]: msg += f'{most_voted[0]}号玩家以{max_ticket}票被公投出局' publish_history(self.gid, msg) self._kill(db, most_voted[0], GameEnum.SKILL_VOTE) else: self.captain_pos = most_voted[0] msg += f'{most_voted[0]}号玩家以{max_ticket}票当选警长' publish_history(self.gid, msg) return GameEnum.OK.digest() else: # 平票 if now in [ GameEnum.TURN_STEP_VOTE, GameEnum.TURN_STEP_ELECT_VOTE ]: # todo 全体进入PK if now is GameEnum.TURN_STEP_VOTE: self.steps.insert(self.now_index + 1, GameEnum.TURN_STEP_PK_TALK) self.steps.insert(self.now_index + 2, GameEnum.TURN_STEP_PK_VOTE) else: self.steps.insert(self.now_index + 1, GameEnum.TURN_STEP_ELECT_PK_TALK) self.steps.insert(self.now_index + 2, GameEnum.TURN_STEP_ELECT_PK_VOTE) votees = most_voted voters = [] roles = db.query(Role).filter(Role.gid == self.gid).limit( len(self.players)).all() for r in roles: if r.alive and r.voteable and r.position not in votees: voters.append(r.position) self.history['voter_votee'] = [voters, votees] msg += '以下玩家以{}票平票进入PK:{}'.format( max_ticket, ','.join(map(str, votees))) publish_history(self.gid, msg) return GameEnum.OK.digest() else: msg += '以下玩家以{}票再次平票:{}\n'.format( max_ticket, ','.join(map(str, most_voted))) if now is GameEnum.TURN_STEP_PK_VOTE: msg += '今日无人被公投出局' while self.now_index + 1 < len( self.steps) and self.steps[ self.now_index + 1] is GameEnum.TURN_STEP_LAST_WORDS: self.steps.pop(self.now_index + 1) else: msg += '警徽流失,本局游戏无警长' publish_history(self.gid, msg) return GameEnum.OK.digest() elif now is GameEnum.TAG_ATTACKABLE_WOLF: publish_music(self.gid, 'wolf_end_voice', None, False) return GameEnum.OK.digest() elif now is GameEnum.ROLE_TYPE_SEER: publish_music(self.gid, 'seer_end_voice', None, False) return GameEnum.OK.digest() elif now is GameEnum.ROLE_TYPE_WITCH: publish_music(self.gid, 'witch_end_voice', None, False) return GameEnum.OK.digest() elif now is GameEnum.ROLE_TYPE_SAVIOR: publish_music(self.gid, 'savior_end_voice', None, False) return GameEnum.OK.digest() elif now is GameEnum.TURN_STEP_ANNOUNCE: # for d in self.history['dying']: # role = db.query(Role).filter(Role.gid == self.gid, Role.position == d).limit(1).first() # role.alive = False # self.history['dying'] = {} pass elif now is GameEnum.TURN_STEP_USE_SKILLS: for d in self.history['dying']: role = db.query(Role).filter( Role.gid == self.gid, Role.position == d).limit(1).first() role.alive = False publish_info( self.gid, json.dumps({ 'pos': role.position, 'mutation': 'SOCKET_PLAYER_OUT' })) self.history['dying'] = {} else: return GameEnum.OK.digest() return GameEnum.OK.digest()