def test_make_move_draw_by_threefold_repetition(self):
        board = chess.Board()
        board.push_san("Nf3")
        board.push_san("Nf6")
        board.push_san("Ng1")
        board.push_san("Ng8")
        board.push_san("Nf3")
        board.push_san("Nf6")
        board.push_san("Ng1")
        game = Game()
        game.board = board
        game.player1 = FakeDiscordUser(id=1)
        game.player2 = FakeDiscordUser(id=2)
        game.current_player = game.player2

        chess_bot = Chess()
        chess_bot.games.append(game)

        self.assertFalse(chess_bot.is_game_over(game))

        result = asyncio.run(chess_bot.make_move(game, 'Ng8'))

        self.assertIsInstance(result, Game)
        self.assertEqual(len(chess_bot.games), 0)
        self.assertEqual(
            self.db_session.query(ChessGame).filter_by(result=0).count(), 1)
        self.assertTrue(chess_bot.is_game_over(result))
        self.assertEqual(result.result, '1/2-1/2')
    def test_make_move_finish_game(self):
        board = chess.Board()
        board.push_san("g4")
        board.push_san("e5")
        board.push_san("f4")
        game = Game()
        game.board = board
        game.player1 = FakeDiscordUser(id=1)
        game.player2 = FakeDiscordUser(id=2)
        game.current_player = game.player1

        chess_bot = Chess()
        chess_bot.games.append(game)
        result = asyncio.run(chess_bot.make_move(game, 'd8h4'))

        self.assertIsInstance(result, Game)
        self.assertEqual(len(chess_bot.games), 0)
        self.assertEqual(
            self.db_session.query(ChessGame).filter_by(result=-1).count(), 1)
        self.assertTrue(chess_bot.is_game_over(result))
        self.assertEqual(result.result, '0-1')
class ChessCog(commands.Cog):
    """
    Xadrez
    """

    def __init__(self, client):
        self.client = client
        self.chess_bot = Chess()
        self.puzzle_bot = Puzzle()

    @commands.Cog.listener()
    async def on_connect(self):
        await self.chess_bot.load_games()
        logging.info(f'Successfully loaded {len(self.chess_bot.games)} active chess games')
    
    @commands.command(aliases=['xn'])
    async def xadrez_novo(self, ctx, user2: discord.User, color_schema=None):
        """
        Inicie uma nova partida de xadrez com alguém

        Passe o usuário contra o qual deseja jogar para começar uma partida. \
        Se quiser personalizar as cores do tabuleiro, passe o nome da cor que deseja usar \
        (opções válidas: `blue`, `purple`, `green`, `red`, `gray` e `wood`).
        """
        bot_info = await self.client.application_info()
        if user2.id == bot_info.id:
            return await ctx.send(
                i(ctx, "In order to play a game against the bot, use the command `{prefix}xadrez_bot`")
                .format(prefix=self.client.command_prefix)
            )
        try:
            game = self.chess_bot.new_game(ctx.author, user2, color_schema=color_schema)
            await ctx.send(i(ctx, 'Game started! {}, play your move').format(game.player1.name))
        except ChessException as e:
            await ctx.send(i(ctx, e.message))

    @commands.command(aliases=['xpve', 'xcpu', 'xb'])
    async def xadrez_bot(self, ctx, cpu_level: int, color_schema=None):
        """
        Inicie uma nova partida de xadrez contra o bot

        Passe o nível de dificuldade que desejar (de 0 a 20). \
        Se quiser personalizar as cores do tabuleiro, passe o nome da cor que deseja usar \
        (opções válidas: `blue`, `purple`, `green`, `red`, `gray` e `wood`).
        """
        bot_info = await self.client.application_info()
        try:
            game = self.chess_bot.new_game(
                ctx.author, bot_info, cpu_level=cpu_level, color_schema=color_schema)
            await ctx.send(i(ctx, 'Game started! {}, play your move').format(game.player1.name))
        except ChessException as e:
            await ctx.send(i(ctx, e.message))

    @commands.command(aliases=['xj'])
    @get_current_game
    async def xadrez_jogar(self, ctx, move, *, game):
        """
        Faça uma jogada em sua partida atual

        Use anotação SAN ou UCI. Movimentos inválidos ou ambíguos são rejeitados. {user2_doc}
        """
        async with ctx.channel.typing():
            try:
                game = await self.chess_bot.make_move(game, move)
            except ChessException as e:
                return await ctx.send(i(ctx, e.message))
            
            if self.chess_bot.is_game_over(game):
                pgn = self.chess_bot.generate_pgn(game)
                message = f'{i(ctx, "Game over!")}\n\n{pgn}'
            else:
                message = i(ctx, "That's your turn now, {}").format(game.current_player.name)
            
            board_png_bytes = self.chess_bot.build_png_board(game)
            await ctx.send(
                content=message,
                file=discord.File(board_png_bytes, 'board.png')
            )

        evaluation = await self.chess_bot.eval_last_move(game)
        if evaluation["blunder"]:
            await ctx.send("👀")
        elif evaluation["mate_in"] and evaluation["mate_in"] in range(1, 4):
            sheev_msgs = [
                i(ctx, "DEW IT!"),
                i(ctx, "Kill him! Kill him now!"),
                i(ctx, "Good, {username}, good!").format(username=game.current_player.name)
            ]
            await ctx.send(sheev_msgs[evaluation["mate_in"] - 1])

    @commands.command(aliases=['xa'])
    @get_current_game
    async def xadrez_abandonar(self, ctx, *, game):
        """
        Abandone a partida atual

        {user2_doc}
        """
        async with ctx.channel.typing():
            game = await self.chess_bot.resign(game)
            pgn = self.chess_bot.generate_pgn(game)
            board_png_bytes = self.chess_bot.build_png_board(game)
            await ctx.send(
                content=i(ctx, '{} has abandoned the game!').format(game.current_player.name)+f'\n{pgn}',
                file=discord.File(board_png_bytes, 'board.png')
            )

    @commands.command(aliases=['xpgn'])
    @get_current_game
    async def xadrez_pgn(self, ctx, *, user2: discord.User=None, game):
        """
        Gera o PGN da partida atual

        {user2_doc}
        """
        async with ctx.channel.typing():
            result = self.chess_bot.generate_pgn(game)
            await ctx.send(result)

    @commands.command(aliases=['xpos'])
    @get_current_game
    async def xadrez_posicao(self, ctx, *, game):
        """
        Mostra a posição atual da partida em andamento

        {user2_doc}
        """
        async with ctx.channel.typing():
            image = self.chess_bot.build_png_board(game)
            await ctx.send(file=discord.File(image, 'board.png'))

    @commands.command(aliases=['xt', 'xadrez_jogos'])
    async def xadrez_todos(self, ctx, page: int=0):
        """
        Veja todas as partidas que estão sendo jogadas agora
        """
        async with ctx.channel.typing():
            png_bytes = await self.chess_bot.get_all_boards_png(page)
            if not png_bytes:
                await ctx.send(
                    i(ctx, "No game is being played currently... ☹️ Start a new one with `{prefix}!xadrez_novo`")
                    .format(prefix=self.client.command_prefix)
                )
            else:
                await ctx.send(file=discord.File(png_bytes, 'boards.png'))

    @commands.command(aliases=['xgif'])
    async def xadrez_gif(self, ctx, game_id: str, move_number: int, *moves):
        """
        Exibe um GIF animado com uma variante fornecida para o jogo em questão, a partir do lance fornecido

        É necessário passar o jogo em questão, identificado com seu UUID, e o número do lance a partir do qual \
            a sequência fornecida se inicia, que deve ser uma sequência de lances em UCI ou SAN separados por espaço.

        Exemplo de uso: `xgif f63e5e4f-dd94-4439-a283-33a1c1a065a0 11 Nxf5 Qxf5 Qxf5 gxf5`
        """
        async with ctx.channel.typing():
            chess_game = await self.chess_bot.get_game_by_id(game_id)
            if not chess_game:
                return await ctx.send(i(ctx, "Game not found"))
            
            gif_bytes = await self.chess_bot.build_animated_sequence_gif(
                chess_game, move_number, moves)
            if not gif_bytes:
                return await ctx.send(i(ctx, "Invalid move for the given sequence"))
            return await ctx.send(file=discord.File(gif_bytes, 'variation.gif'))

    @commands.command(aliases=['xp'])
    async def xadrez_puzzle(self, ctx, puzzle_id=None, move=''):
        """
        Pratique um puzzle de xadrez

        Envie o comando sem argumentos para um novo puzzle. Para tentar uma jogada em um puzzle, \
        envie o ID do puzzle como primeiro argumento e a jogada como segundo.

        Exemplo de novo puzzle: `xadrez_puzzle`
        Exemplo de jogada em puzzle existente: `xadrez_puzzle 557b7aa7e13823b82b9bc1e9 Qa2`
        """
        await ctx.trigger_typing()
        if not puzzle_id:
            puzzle_dict = await self.puzzle_bot.get_random_puzzle()
            if 'error' in puzzle_dict:
                return await ctx.send(
                    f'{i(ctx, "There has been an error when trying to fetch a new puzzle")}: {puzzle_dict["error"]}')
            puzzle = self.puzzle_bot.build_puzzle(puzzle_dict)
            if 'error' in puzzle:
                return await ctx.send(
                    f'{i(ctx, "There has been an error when trying to build a new puzzle")}: {puzzle["error"]}')
            return await ctx.send(puzzle["id"], file=discord.File(self.chess_bot.build_png_board(puzzle["game"]), 'puzzle.png'))
        
        try:
            puzzle_result = self.puzzle_bot.validate_puzzle_move(puzzle_id, move)
        except ChessException as e:
            return await ctx.send(i(ctx, e.message))
        
        if puzzle_result:
            if self.puzzle_bot.is_puzzle_over(puzzle_id):
                return await ctx.send(i(ctx, "Good job, puzzle solved 👍"))
        if puzzle_result or move == '':
            return await ctx.send(file=discord.File(
                self.chess_bot.build_png_board(
                    self.puzzle_bot.puzzles[puzzle_id]["game"]), 'puzzle.png')
            )
        
        return await ctx.send(i(ctx, "Wrong answer"))