197 lines
6.5 KiB
Python
197 lines
6.5 KiB
Python
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()
|