from __future__ import annotations import numpy as np from blokus_gym.core.bots import GreedyBot, GreedyCornersBot, RandomBot from blokus_gym.core.game import BlokusGame class TestBlokusGame: def test_game_creation(self): game = BlokusGame(board_size=20, num_players=4) game.reset() assert game.board_size == 20 assert game.num_players == 4 assert game.current_player == 0 assert len(game.players) == 4 def test_reset(self): game = BlokusGame(board_size=20, num_players=4) game.reset() assert game.current_player == 0 for player in game.players: assert len(player.available_pieces) == 21 def test_action_space_size(self): game = BlokusGame(board_size=20, num_players=4) assert game.num_actions > 0 def test_valid_actions_initial(self): game = BlokusGame(board_size=20, num_players=4) game.reset() valid = game.get_valid_actions(0) assert np.any(valid) def test_play_first_move_corner(self): game = BlokusGame(board_size=20, num_players=4) game.reset() valid = game.get_valid_actions(0) valid_indices = np.where(valid)[0] action = valid_indices[0] assert game.play_move(0, action) assert game.board.grid.sum() > 0 def test_invalid_action(self): game = BlokusGame(board_size=20, num_players=4) game.reset() # Use an action that's likely invalid (very high index) assert not game.play_move(0, game.num_actions - 1) def test_next_player(self): game = BlokusGame(board_size=20, num_players=4) game.reset() assert game.current_player == 0 game.next_player() assert game.current_player == 1 game.next_player() assert game.current_player == 2 game.next_player() assert game.current_player == 3 game.next_player() assert game.current_player == 0 def test_has_valid_moves(self): game = BlokusGame(board_size=20, num_players=4) game.reset() assert game.has_valid_moves(0) def test_is_game_over(self): game = BlokusGame(board_size=20, num_players=4) game.reset() assert not game.is_game_over() def test_get_scores(self): game = BlokusGame(board_size=20, num_players=4) game.reset() scores = game.get_scores() assert len(scores) == 4 for score in scores: assert isinstance(score, (int, float)) def test_get_action_mask(self): game = BlokusGame(board_size=20, num_players=4) game.reset() mask = game.get_action_mask(0) assert mask.shape == (game.num_actions,) assert mask.dtype == bool def test_get_current_observation(self): game = BlokusGame(board_size=20, num_players=4) game.reset() obs = game.get_current_observation() assert "board" in obs assert "pieces" in obs assert "corners" in obs assert obs["board"].shape == (20, 20) assert obs["pieces"].shape == (21,) assert obs["corners"].shape == (20, 20) def test_two_player_game(self): game = BlokusGame(board_size=14, num_players=2) game.reset() assert game.num_players == 2 assert game.has_valid_moves(0) def test_three_player_game(self): game = BlokusGame(board_size=20, num_players=3) game.reset() assert game.num_players == 3 def test_copy(self): game = BlokusGame(board_size=20, num_players=4) game.reset() game.play_move(0, 0) game_copy = game.copy() assert game_copy.current_player == game.current_player def test_play_multiple_moves(self): game = BlokusGame(board_size=20, num_players=2) game.reset() # Play a few moves for _ in range(5): valid = game.get_valid_actions(game.current_player) valid_indices = np.where(valid)[0] if len(valid_indices) > 0: game.play_move(game.current_player, valid_indices[0]) game.next_player() else: break def test_get_winners_not_over(self): game = BlokusGame(board_size=20, num_players=4) game.reset() assert game.get_winners() is None def test_valid_move_first_corner_only(self): game = BlokusGame(board_size=20, num_players=4) game.reset() from blokus_gym.core.pieces import Move # First move must be in corner (0, 0) for player 0 move = Move(piece_id=0, orientation_id=0, x=0, y=0) assert game.valid_move(0, move) # Try a non-corner first move move2 = Move(piece_id=0, orientation_id=0, x=5, y=5) assert not game.valid_move(0, move2) class TestBots: def test_random_bot(self): game = BlokusGame(board_size=20, num_players=4) game.reset() bot = RandomBot(player_idx=0, seed=42) valid = game.get_valid_actions(0) action = bot.select_action(game, valid) assert action is not None assert valid[action] def test_greedy_bot(self): game = BlokusGame(board_size=20, num_players=4) game.reset() bot = GreedyBot(player_idx=0) valid = game.get_valid_actions(0) action = bot.select_action(game, valid) assert action is not None assert valid[action] def test_greedy_corners_bot(self): game = BlokusGame(board_size=20, num_players=4) game.reset() bot = GreedyCornersBot(player_idx=0) valid = game.get_valid_actions(0) action = bot.select_action(game, valid) assert action is not None assert valid[action] def test_bot_no_valid_moves(self): game = BlokusGame(board_size=20, num_players=4) game.reset() bot = RandomBot(player_idx=0, seed=42) valid = np.zeros(game.num_actions, dtype=bool) action = bot.select_action(game, valid) assert action is None def test_bots_play_game(self): game = BlokusGame(board_size=7, num_players=2) game.reset() bots = [RandomBot(player_idx=i, seed=i) for i in range(2)] steps = 0 while not game.is_game_over() and steps < 100: bot = bots[game.current_player] valid = game.get_valid_actions(game.current_player) action = bot.select_action(game, valid) if action is not None: game.play_move(game.current_player, action) game.next_player() steps += 1 assert game.is_game_over()