def handover() -> dict: target = int(request.args.get('target')) my_role = Role.query.get(current_user.uid) if not my_role.alive: current_app.logger.info('my_role is not alive') return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() with Game.query.with_for_update().get(current_user.gid) as game: now = StepProcessor.current_step(game) if now is not GameEnum.TURN_STEP_LAST_WORDS: current_app.logger.info(f'wrong now step:{now.label}') return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() if str(my_role.position) not in game.history['dying']: current_app.logger.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: current_app.logger.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 = _get_role_by_pos(game, target) if not target_role.alive: current_app.logger.info(f'target not alive, target={target}') return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() game.captain_pos = target publish_history(game.gid, f'{my_role.position}号玩家将警徽移交给了{target}号玩家') return GameEnum.OK.digest()
def suicide(): my_role = Role.query.get(current_user.uid) if not my_role.alive: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() with Game.query.with_for_update().get(current_user.gid) as game: if game.status is not GameEnum.GAME_STATUS_DAY: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() game.steps = [] publish_history(game.gid, f'{my_role.position}号玩家自爆了') StepProcessor.kill(game, my_role.position, GameEnum.SKILL_SUICIDE) return StepProcessor.move_on(game)
def elect() -> dict: choice = request.args.get('choice') my_role = Role.query.get(current_user.uid) if not my_role.alive: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() with Game.query.with_for_update().get(current_user.gid) as game: now = StepProcessor.current_step(game) 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() 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 }: game.steps.pop(game.now_index + 1) captain_pos = votee[0] game.captain_pos = captain_pos publish_history(game.gid, f'仅剩一位警上玩家,{captain_pos}号玩家自动当选警长') return StepProcessor.move_on(game) else: raise ValueError(f'Unknown choice: {choice}') return GameEnum.OK.digest()
def shoot() -> dict: target = int(request.args.get('target')) my_role = Role.query.get(current_user.uid) if not my_role.alive: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() with Game.query.with_for_update().get(current_user.gid) as game: history = game.history now = StepProcessor.current_step(game) if now is not GameEnum.TURN_STEP_LAST_WORDS: 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 = _get_role_by_pos(game, target) if not target_role.alive: return GameEnum.GAME_MESSAGE_CANNOT_ACT.digest() publish_history(game.gid, f'{my_role.position}号玩家发动技能“枪击”,带走了{target}号玩家') StepProcessor.kill(game, target, GameEnum.SKILL_SHOOT) return GameEnum.OK.digest()
def check_win(game): # groups = Role.query.with_entities(Role.group_type, func.count(Role.group_type)).filter(Role.gid == game.gid, Role.alive == int(True)).group_by(Role.group_type).all() # groups = {g: cnt for g, cnt in groups} players = Role.query.with_entities(Role.position, Role.group_type).filter( Role.gid == game.gid, Role.alive == int(True)).all() groups = collections.defaultdict(int) for p, g in players: if p not in game.history['dying']: groups[g] += 1 if GameEnum.GROUP_TYPE_WOLVES not in groups: publish_history(game.gid, '游戏结束,好人阵营胜利') # game.status = GameEnum.GAME_STATUS_FINISHED original_players = game.players StepProcessor.init_game(game) game.players = original_players all_players = Role.query.filter(Role.gid == game.gid).all() for p in all_players: p.reset() raise GameFinished() if game.victory_mode is GameEnum.VICTORY_MODE_KILL_GROUP and ( GameEnum.GROUP_TYPE_GODS not in groups or GameEnum.GROUP_TYPE_VILLAGERS not in groups): publish_history(game.gid, '游戏结束,狼人阵营胜利') # game.status = GameEnum.GAME_STATUS_FINISHED original_players = game.players StepProcessor.init_game(game) game.players = original_players all_players = Role.query.filter(Role.gid == game.gid).all() for p in all_players: p.reset() raise GameFinished() if GameEnum.GROUP_TYPE_GODS not in groups and GameEnum.GROUP_TYPE_VILLAGERS not in groups: publish_history(game.gid, '游戏结束,狼人阵营胜利') # game.status = GameEnum.GAME_STATUS_FINISHED original_players = game.players StepProcessor.init_game(game) game.players = original_players all_players = Role.query.filter(Role.gid == game.gid).all() for p in all_players: p.reset() raise GameFinished()
def _leave_step(game: Game) -> dict: now = StepProcessor.current_step(game) if now is None: return GameEnum.OK.digest() if now is GameEnum.TURN_STEP_ELECT: roles = Role.query.filter(Role.gid == game.gid).limit( len(game.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 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 }: game.steps.pop(game.now_index + 1) if not voters: publish_history(game.gid, '所有人都竞选警长,本局游戏无警长') else: publish_history(game.gid, '没有人竞选警长,本局游戏无警长') elif len(votees) == 1: # auto win captain 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 }: game.steps.pop(game.now_index + 1) captain_pos = votees[0] game.captain_pos = captain_pos publish_history(game.gid, f'只有{captain_pos}号玩家竞选警长,自动当选') else: publish_history( game.gid, f"竞选警长的玩家为:{','.join(map(str,votees))}\n未竞选警长的玩家为:{','.join(map(str,voters))}" ) game.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 game.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 == game.captain_pos: ticket_cnt[votee_pos] += 0.5 for voter in game.history['voter_votee'][0]: if str(voter) not in game.history['vote_result']: forfeit.append(voter) 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 = game.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(game.gid, msg) StepProcessor.kill(game, most_voted[0], GameEnum.SKILL_VOTE) else: game.captain_pos = most_voted[0] msg += f'{most_voted[0]}号玩家以{max_ticket}票当选警长' publish_history(game.gid, msg) return GameEnum.OK.digest() else: # 平票 # todo 无人投票 if now in [ GameEnum.TURN_STEP_VOTE, GameEnum.TURN_STEP_ELECT_VOTE ]: if now is GameEnum.TURN_STEP_VOTE: game.steps.insert(game.now_index + 1, GameEnum.TURN_STEP_PK_TALK) game.steps.insert(game.now_index + 2, GameEnum.TURN_STEP_PK_VOTE) else: game.steps.insert(game.now_index + 1, GameEnum.TURN_STEP_ELECT_PK_TALK) game.steps.insert(game.now_index + 2, GameEnum.TURN_STEP_ELECT_PK_VOTE) votees = most_voted voters = [] roles = Role.query.filter(Role.gid == game.gid).limit( len(game.players)).all() for r in roles: if r.alive and r.voteable and r.position not in votees: voters.append(r.position) game.history['voter_votee'] = [voters, votees] msg += '以下玩家以{}票平票进入PK:{}'.format( max_ticket, ','.join(map(str, votees))) publish_history(game.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 += '今天是平安日,无人被公投出局' else: msg += '警徽流失,本局游戏无警长' publish_history(game.gid, msg) return GameEnum.OK.digest() elif now is GameEnum.TAG_ATTACKABLE_WOLF: publish_music(game.gid, 'wolf_end_voice', None, False) return GameEnum.OK.digest() elif now is GameEnum.ROLE_TYPE_SEER: publish_music(game.gid, 'seer_end_voice', None, False) return GameEnum.OK.digest() elif now is GameEnum.ROLE_TYPE_WITCH: publish_music(game.gid, 'witch_end_voice', None, False) return GameEnum.OK.digest() elif now is GameEnum.ROLE_TYPE_SAVIOR: publish_music(game.gid, 'savior_end_voice', None, False) return GameEnum.OK.digest() else: return GameEnum.OK.digest()
def _enter_step(game: Game) -> GameEnum: now = StepProcessor.current_step(game) if now is GameEnum.TURN_STEP_TURN_NIGHT: game.status = GameEnum.GAME_STATUS_NIGHT for d in game.history['dying']: role = Role.query.filter(Role.gid == game.gid, Role.position == d).limit(1).first() role.alive = False StepProcessor._reset_history(game) publish_music(game.gid, 'night_start_voice', 'night_bgm', True) publish_info( game.gid, json.dumps({ 'days': game.days, 'game_status': game.status.label })) publish_history(game.gid, ('***************************\n' '<pre> 第{}天 </pre>\n' '***************************').format(game.days), show=False) return GameEnum.STEP_FLAG_AUTO_MOVE_ON elif now is GameEnum.TAG_ATTACKABLE_WOLF: publish_music(game.gid, 'wolf_start_voice', 'wolf_bgm', True) all_players = Role.query.filter(Role.gid == game.gid, Role.alive == int(True)).limit( len(game.players)).all() for p in all_players: if GameEnum.TAG_ATTACKABLE_WOLF in p.tags: break else: scheduler.add_job(id=f'{game.gid}_WOLF_KILL_{game.step_cnt}', func=timeout_move_on, args=(game.gid, game.step_cnt), next_run_time=datetime.now() + timedelta(seconds=random.randint( GameEnum.GAME_TIMEOUT_RANDOM_FROM.label, GameEnum.GAME_TIMEOUT_RANDOM_TO.label))) 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(game.gid, 'elect', None, False) publish_history(game.gid, '###上警阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_VOTE: game.history['vote_result'] = {} voters = [] votees = [] roles = Role.query.filter(Role.gid == game.gid).limit( len(game.players)).all() for r in roles: if not r.alive: continue votees.append(r.position) if r.voteable: voters.append(r.position) game.history['voter_votee'] = [voters, votees] publish_history(game.gid, '###投票阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_ELECT_VOTE: game.history['vote_result'] = {} publish_history(game.gid, '###警长投票阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_PK_VOTE: game.history['vote_result'] = {} publish_history(game.gid, '###PK投票阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_ELECT_PK_VOTE: game.history['vote_result'] = {} publish_history(game.gid, '###警长PK投票阶段###', False) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.TURN_STEP_ANNOUNCE: if game.history['dying']: publish_history( game.gid, '昨晚,以下位置的玩家倒下了,不分先后:{}'.format(','.join( [str(d) for d in sorted(game.history['dying'])]))) else: publish_history(game.gid, "昨晚是平安夜") return GameEnum.STEP_FLAG_AUTO_MOVE_ON elif now is GameEnum.TURN_STEP_TURN_DAY: game.status = GameEnum.GAME_STATUS_DAY publish_music(game.gid, 'day_start_voice', 'day_bgm', False) StepProcessor.calculate_die_in_night(game) publish_info( game.gid, json.dumps({ 'days': game.days, 'game_status': game.status.label })) return GameEnum.STEP_FLAG_AUTO_MOVE_ON elif now is GameEnum.ROLE_TYPE_SEER: publish_music(game.gid, 'seer_start_voice', 'seer_bgm', True) seer_cnt = db.session.query(db.func.count(Role.uid)).filter( Role.gid == game.gid, Role.alive == int(True), Role.role_type == GameEnum.ROLE_TYPE_SEER).scalar() if seer_cnt == 0: scheduler.add_job(id=f'{game.gid}_SEER_{game.step_cnt}', func=timeout_move_on, args=(game.gid, game.step_cnt), next_run_time=datetime.now() + timedelta(seconds=random.randint( GameEnum.GAME_TIMEOUT_RANDOM_FROM.label, GameEnum.GAME_TIMEOUT_RANDOM_TO.label))) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.ROLE_TYPE_WITCH: publish_music(game.gid, 'witch_start_voice', 'witch_bgm', True) witch_cnt = db.session.query(db.func.count(Role.uid)).filter( Role.gid == game.gid, Role.alive == int(True), Role.role_type == GameEnum.ROLE_TYPE_WITCH).scalar() if witch_cnt == 0: scheduler.add_job(id=f'{game.gid}_WITCH_{game.step_cnt}', func=timeout_move_on, args=(game.gid, game.step_cnt), next_run_time=datetime.now() + timedelta(seconds=random.randint( GameEnum.GAME_TIMEOUT_RANDOM_FROM.label, GameEnum.GAME_TIMEOUT_RANDOM_TO.label))) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION elif now is GameEnum.ROLE_TYPE_SAVIOR: publish_music(game.gid, 'savior_start_voice', 'savior_bgm', True) savior_cnt = db.session.query(db.func.count(Role.uid)).filter( Role.gid == game.gid, Role.alive == int(True), Role.role_type == GameEnum.ROLE_TYPE_SAVIOR).scalar() if savior_cnt == 0: scheduler.add_job(id=f'{game.gid}_SAVIOR', func=timeout_move_on, args=(game.gid, game.step_cnt), next_run_time=datetime.now() + timedelta(seconds=random.randint( GameEnum.GAME_TIMEOUT_RANDOM_FROM.label, GameEnum.GAME_TIMEOUT_RANDOM_TO.label))) return GameEnum.STEP_FLAG_WAIT_FOR_ACTION