Example #1
0
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
Example #2
0
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()
Example #3
0
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()
Example #4
0
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()
Example #5
0
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()
Example #6
0
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()
Example #7
0
 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
Example #8
0
    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()