async def make_move(self, game: Game, move: str) -> Game: """ Makes a move in given game, if valid :param game: Game to be played :type game: Game :param move: Chess move in SAN or UCI notation :type move: str :return: Updated game :rtype: Game """ chess_move = self._parse_str_move(game, move) if not chess_move: raise InvalidMove() game.board.push(chess_move) game.current_player = game.player1 if game.current_player == game.player2 else game.player2 if self.is_pve_game(game) and not self.is_game_over(game): stockfish_result = await self._play_move(game) game.board.push(stockfish_result.move) game.current_player = game.player1 if self.is_game_over(game): game.result = game.board.result(claim_draw=True) self.games.remove(game) await game.save() return game
def test_is_pve_game_pvp_game(self): game = Game() game.cpu_level = None chess_bot = Chess() chess_bot.stockfish_path = "valid" chess_bot.games.append(game) self.assertFalse(chess_bot.is_pve_game(game))
def test_is_pve_game_stockfish_disabled(self): game = Game() game.cpu_level = 0 chess_bot = Chess() chess_bot.stockfish_path = "" chess_bot.games.append(game) self.assertFalse(chess_bot.is_pve_game(game))
def test_new_game_game_already_started(self): game = Game() game.player1 = FakeDiscordUser(id=1) game.player2 = FakeDiscordUser(id=2) chess_bot = Chess() chess_bot.games.append(game) with self.assertRaises(GameAlreadyInProgress): chess_bot.new_game(game.player1, game.player2) self.assertEqual(len(chess_bot.games), 1)
def _build_puzzle_bot_with_one_puzzle(self): puzzle_bot = Puzzle() game = Game() game.board = Board("8/7p/4p1p1/1p5k/8/PB4PK/2P2q1P/4R3 w - - 0 37") game.board.push_san("g4+") puzzle_id = "557ca53de13823b83a98100e" puzzle_bot.puzzles = { puzzle_id: { "game": game, "correct_sequence": ["Kg5", "Rf1", "Qxf1+", "Kg3", "Qf4+"] } } return puzzle_bot, puzzle_id
def test_make_move_legal_move_pve(self): board = chess.Board( 'rn2kb1r/pp1qpppp/2ppbn2/1B6/3PP3/2N2N2/PPP2PPP/R1BQK2R w KQkq - 0 6' ) game = Game() game.board = board game.player1 = FakeDiscordUser(id=1) game.player2 = FakeDiscordUser(id=2) game.current_player = game.player1 game.cpu_level = 0 chess_bot = Chess() chess_bot.games.append(game) chess_bot.stockfish_limit['time'] = 1 result = asyncio.run(chess_bot.make_move(game, 'b5d3')) self.assertIsInstance(result, Game) if chess_bot.is_stockfish_enabled(): self.assertEqual(len(result.board.move_stack), 2) self.assertEqual(result.current_player, game.player1) updated_chess_game = self.db_session.query(ChessGame).filter_by( player1_id=result.player1.id).first() updated_game_from_db = Game.from_chess_game_model(updated_chess_game) self.assertEqual(updated_game_from_db.board.move_stack, result.board.move_stack)
def test_get_all_boards_png_one_game(self): chess_bot = Chess() board1 = chess.Board() board1.push_san("e4") board1.push_san("e5") board1.push_san("Bc4") game1 = Game() game1.color_schema = "green" game1.board = board1 chess_bot.games.append(game1) image_bytesio = asyncio.run(chess_bot.get_all_boards_png()) with open( os.path.join('tests', 'support', 'get_all_boards_png_one_game.png'), 'rb') as f: self.assertEqual(image_bytesio.read(), f.read())
def test_build_animated_sequence_gif_invalid_move_in_sequence(self): board = chess.Board() board.push_san('e4') board.push_san('c5') board.push_san('Nc3') game = Game() game.board = board game.player1 = FakeDiscordUser(id=1) game.player2 = FakeDiscordUser(id=2) chess_bot = Chess() chess_bot.games.append(game) sequence = ['Nf3', 'd6', 'd4', 'Rxa8', 'Nxd4', 'Nf6', 'Nc3', 'a6'] result = asyncio.run( chess_bot.build_animated_sequence_gif(game, 2, sequence)) self.assertIsNone(result)
def build_animated_sequence_gif(self, game: Game, game_move: int, sequence: list) -> BytesIO: """ Builds an animated GIF for illustrating a given game's possible variation :param game: Game to be used for variation :type game: Game :param game_move: Number of game's move for the start of variation :type game_move: int :param sequence: Variation's sequence of move :type sequence: str[] :return: Animated gif's bytesIO :rtype: BytesIO """ game_moves = game.board.move_stack new_board = chess.Board() new_game = Game() new_game.board = new_board new_game.color_schema = game.color_schema for move in game_moves[:game_move]: new_board.push(move) first_gif_frame = Image.open(self.build_png_board(new_game)) gif_frames = [] for variant_move in sequence: variant_chess_move = self._parse_str_move(new_game, variant_move) if not variant_chess_move: return new_board.push(variant_chess_move) gif_frame = Image.open(self.build_png_board(new_game)) gif_frames.append(gif_frame) bytesio = BytesIO() first_gif_frame.save(bytesio, format='gif', save_all=True, append_images=gif_frames, duration=1000, loop=0) bytesio.seek(0) return bytesio
def test_build_animated_sequence_gif_valid_params(self): board = chess.Board() board.push_san('e4') board.push_san('c5') board.push_san('Nc3') game = Game() game.board = board game.player1 = FakeDiscordUser(id=1) game.player2 = FakeDiscordUser(id=2) chess_bot = Chess() chess_bot.games.append(game) sequence = ['Nf3', 'd6', 'd4', 'cxd4', 'Nxd4', 'Nf6', 'Nc3', 'a6'] result = asyncio.run( chess_bot.build_animated_sequence_gif(game, 2, sequence)) with open( os.path.join('tests', 'support', 'build_animated_sequence_gif.gif'), 'rb') as f: self.assertEqual(result.getvalue(), f.read())
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 _is_last_move_blunder(self, game: Game, analysis: dict): mate_score = 100000 last_eval = game.last_eval game.last_eval = analysis["score"].white() if last_eval.__class__ != game.last_eval.__class__: return True last_eval_score = last_eval.score(mate_score=mate_score) current_eval_score = game.last_eval.score(mate_score=mate_score) return abs( self._evaluation_normalizer(current_eval_score) - self._evaluation_normalizer(last_eval_score)) > 2
def build_puzzle(self, puzzle_dict): try: puzzle_id = puzzle_dict["data"]["id"] first_move = puzzle_dict["data"]["blunderMove"] fen = puzzle_dict["data"]["fenBefore"] correct_sequence = puzzle_dict["data"]["forcedLine"] board = Board(fen) board.push_san(first_move) game = Game() game.board = board game.color_schema = "default" self.puzzles[puzzle_id] = { "id": puzzle_id, "game": game, "correct_sequence": correct_sequence } return self.puzzles[puzzle_id] except KeyError: return {"error": "Missing required keys"} except: return {"error": "Invalid FEN or move"}
async def resign(self, game: Game) -> Game: """ Resigns the given game. Only the next player to move can resign their game. :param game: Game to be resigned :type game: Game :return: Updated game :rtype: Game """ board_png_bytes = self.build_png_board(game) game.result = '0-1' if game.board.turn == chess.WHITE else '1-0' await game.save() self.games.remove(game) return game
def new_game(self, user1, user2, color_schema=None, cpu_level=None): player1, player2 = convert_users_to_players(user1, user2) current_players_pairs = map(lambda x: [x.player1, x.player2], self.games) given_players_pairs = [player1, player2] if given_players_pairs in current_players_pairs: raise GameAlreadyInProgress() game = Game() game.board = chess.Board() game.player1 = player1 game.player2 = player2 game.current_player = player1 game.result = game.board.result() game.color_schema = color_schema game.cpu_level = cpu_level self.games.append(game) return game
async def load_games(self): """ Load all ongoing games from database :return: List of ongoing games :rtype: List[Games] """ try: chess_games_models = await ChessGameModel.get_all_ongoing_games() self.games = [ Game.from_chess_game_model(x) for x in chess_games_models ] except Exception as e: logging.warning(e, exc_info=True) finally: return self.games
async def get_game_by_id(self, chess_game_id: str) -> Game: """ Fetches from database chess game by given chess game id :param chess_game_id: Chess game's UUID :type chess_game_id: str :return: Chess game :rtype: Game """ try: chess_game_model = await ChessGameModel.get(chess_game_id, preload_players=True) if not chess_game_model: return None except: return None return Game.from_chess_game_model(chess_game_model)
def test_get_game_by_id_game_invalid_uuid(self): warnings.simplefilter('ignore') board1 = chess.Board() board1.push_san("e4") board1.push_san("e5") game1 = Game() game1.board = board1 game1.player1 = FakeDiscordUser(id=1) game1.player2 = FakeDiscordUser(id=2) asyncio.run(game1.save()) result = asyncio.run(Chess().get_game_by_id("invalid_id")) self.assertIsNone(result)
def test_get_game_by_id_game_exists(self): warnings.simplefilter('ignore') board1 = chess.Board() board1.push_san("e4") board1.push_san("e5") game1 = Game() game1.board = board1 game1.player1 = FakeDiscordUser(id=1) game1.player2 = FakeDiscordUser(id=2) asyncio.run(game1.save()) result = asyncio.run(Chess().get_game_by_id(game1.id)) self.assertEqual(result, game1)
def test_eval_last_move_no_blunder_no_mate(self): board = chess.Board() board.push_san("g4") board.push_san("e5") game = Game() game.board = board game.player1 = FakeDiscordUser(id=1) game.player2 = FakeDiscordUser(id=2) game.current_player = game.player1 game.last_eval = Cp(0) chess_bot = Chess() chess_bot.games.append(game) chess_bot.stockfish_limit["time"] = 2 result = asyncio.run(chess_bot.eval_last_move(game)) self.assertFalse(result["blunder"]) self.assertIsNone(result["mate_in"])
def test_generate_pgn(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, name='Player1') game.player2 = FakeDiscordUser(id=2, name='Player2') game.current_player = game.player1 game.result = game.board.result() chess_bot = Chess() chess_bot.games.append(game) result = chess_bot.generate_pgn(game) self.assertIn('[White "Player1"]', result) self.assertIn('[Black "Player2"]', result) self.assertIn('1. g4 e5 2. f4 *', result)
def test_find_current_game_in_players_turn_no_ambiguity(self): board = chess.Board() board.push_san("e4") board.push_san("e5") 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 = chess_bot.find_current_game(user=game.player1) self.assertEqual(result, game)
def test_eval_last_move_no_blunder_mate_in_two(self): board = chess.Board( "Q2r4/1p1k4/1P3ppp/1Kp1r3/4p2b/1B3P2/2P2q2/8 w - - 6 44") game = Game() game.board = board game.player1 = FakeDiscordUser(id=1) game.player2 = FakeDiscordUser(id=2) game.current_player = game.player1 game.last_eval = Mate(2) chess_bot = Chess() chess_bot.games.append(game) result = asyncio.run(chess_bot.eval_last_move(game)) self.assertFalse(result["blunder"]) if chess_bot.is_stockfish_enabled(): self.assertEqual(result["mate_in"], 2) else: self.assertIsNone(result["mate_in"])
def test_eval_last_move_lost_position_blunders_mate(self): board = chess.Board( "Q1kr4/1p6/1P3ppp/1Kp1r3/4p2b/1B3P2/2P2q2/8 b - - 5 43") game = Game() game.board = board game.player1 = FakeDiscordUser(id=1) game.player2 = FakeDiscordUser(id=2) game.current_player = game.player1 game.last_eval = Cp(1000) chess_bot = Chess() chess_bot.games.append(game) result = asyncio.run(chess_bot.eval_last_move(game)) if chess_bot.is_stockfish_enabled(): self.assertTrue(result["blunder"]) self.assertEqual(result["mate_in"], -2) else: self.assertFalse(result["blunder"]) self.assertIsNone(result["mate_in"])
def test_make_move_illegal_move_in_players_turn(self): board = chess.Board() board.push_san("e4") board.push_san("e5") 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) with self.assertRaises(InvalidMove) as e: asyncio.run(chess_bot.make_move(game, 'invalid')) self.assertEqual(len(game.board.move_stack), 2) self.assertEqual(game.current_player, game.player1)
def test_resign_game_found(self): board = chess.Board() board.push_san("e4") board.push_san("e5") board.push_san("Nf3") 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.resign(game)) self.assertEqual(len(chess_bot.games), 0) self.assertEqual( self.db_session.query(ChessGame).filter_by(result=1).count(), 1)
def test_eval_last_move_last_move_blunder_mate_in_one(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 game.last_eval = Cp(0) chess_bot = Chess() chess_bot.games.append(game) result = asyncio.run(chess_bot.eval_last_move(game)) if chess_bot.is_stockfish_enabled(): self.assertTrue(result["blunder"]) self.assertEqual(result["mate_in"], 1) else: self.assertFalse(result["blunder"]) self.assertIsNone(result["mate_in"])
def test_make_move_legal_san_move_in_players_turn(self): board = chess.Board() board.push_san("e4") board.push_san("e5") 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, 'Nf3')) self.assertIsInstance(result, Game) self.assertEqual(len(result.board.move_stack), 3) self.assertEqual(result.current_player, game.player2) updated_chess_game = self.db_session.query(ChessGame).filter_by( player1_id=result.player1.id).first() updated_game_from_db = Game.from_chess_game_model(updated_chess_game) self.assertEqual(updated_game_from_db.board.move_stack, result.board.move_stack)
def test_make_move_finish_game_pve_player_wins(self): board = chess.Board() board.push_san("e4") board.push_san("g5") board.push_san("d4") board.push_san("f5") game = Game() game.board = board game.player1 = FakeDiscordUser(id=1) game.player2 = FakeDiscordUser(id=2) game.current_player = game.player1 game.cpu_level = 20 chess_bot = Chess() chess_bot.games.append(game) result = asyncio.run(chess_bot.make_move(game, 'Qh5')) 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, '1-0')
def test_get_all_boards_png_no_twelve_games_second_page(self): chess_bot = Chess() board1 = chess.Board() board1.push_san("e4") board1.push_san("e5") game1 = Game() game1.color_schema = "blue" game1.board = board1 chess_bot.games.append(game1) board2 = chess.Board() board2.push_san("Nf3") board2.push_san("d6") game2 = Game() game2.color_schema = "wood" game2.board = board2 chess_bot.games.append(game2) board3 = chess.Board() board3.push_san("Nf3") game3 = Game() game3.color_schema = "green" game3.board = board3 chess_bot.games.append(game3) for i in range(3): chess_bot.games.append(game1) chess_bot.games.append(game2) chess_bot.games.append(game3) image_bytesio = asyncio.run(chess_bot.get_all_boards_png(page=2), debug=True) with open( os.path.join( 'tests', 'support', 'get_all_boards_png_twelve_games_second_page.png'), 'rb') as f: self.assertEqual(image_bytesio.read(), f.read())