initial commit
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from blokus_gym.core.board import Board
|
||||
|
||||
|
||||
class TestBoard:
|
||||
def test_board_creation(self):
|
||||
board = Board(20)
|
||||
assert board.size == 20
|
||||
assert board.grid.shape == (20, 20)
|
||||
assert np.all(board.grid == 0)
|
||||
|
||||
def test_in_bounds(self):
|
||||
board = Board(20)
|
||||
assert board.in_bounds(0, 0)
|
||||
assert board.in_bounds(19, 19)
|
||||
assert not board.in_bounds(-1, 0)
|
||||
assert not board.in_bounds(20, 0)
|
||||
assert not board.in_bounds(0, 20)
|
||||
|
||||
def test_place_and_get(self):
|
||||
board = Board(20)
|
||||
cells = [(0, 0), (1, 0), (2, 0)]
|
||||
board.place(1, cells)
|
||||
assert board.get_cell(0, 0) == 1
|
||||
assert board.get_cell(1, 0) == 1
|
||||
assert board.get_cell(2, 0) == 1
|
||||
assert board.get_cell(3, 0) == 0
|
||||
|
||||
def test_has_overlap(self):
|
||||
board = Board(20)
|
||||
board.place(1, [(0, 0), (1, 0)])
|
||||
assert board.has_overlap([(0, 0), (1, 0)]) # overlap
|
||||
assert not board.has_overlap([(2, 0), (3, 0)]) # no overlap
|
||||
|
||||
def test_has_overlap_out_of_bounds(self):
|
||||
board = Board(20)
|
||||
assert board.has_overlap([(20, 0)]) # out of bounds
|
||||
|
||||
def test_clear(self):
|
||||
board = Board(20)
|
||||
board.place(1, [(0, 0)])
|
||||
board.clear()
|
||||
assert np.all(board.grid == 0)
|
||||
|
||||
def test_get_player_squares(self):
|
||||
board = Board(20)
|
||||
board.place(1, [(0, 0), (1, 0)])
|
||||
squares = board.get_player_squares(1)
|
||||
assert len(squares) == 2
|
||||
assert (0, 0) in squares
|
||||
assert (1, 0) in squares
|
||||
|
||||
def test_get_player_corners(self):
|
||||
board = Board(20)
|
||||
board.place(1, [(0, 0)])
|
||||
corners = board.get_player_corners(1)
|
||||
assert (1, 1) in corners # diagonal
|
||||
assert (0, 0) not in corners # occupied
|
||||
|
||||
def test_is_full(self):
|
||||
board = Board(2)
|
||||
assert not board.is_full()
|
||||
board.place(1, [(0, 0), (0, 1), (1, 0), (1, 1)])
|
||||
assert board.is_full()
|
||||
|
||||
def test_copy(self):
|
||||
board = Board(20)
|
||||
board.place(1, [(0, 0)])
|
||||
board_copy = board.copy()
|
||||
assert board_copy.get_cell(0, 0) == 1
|
||||
board_copy.place(2, [(1, 0)])
|
||||
assert board.get_cell(1, 0) == 0 # original unchanged
|
||||
|
||||
def test_is_empty(self):
|
||||
board = Board(20)
|
||||
assert board.is_empty(0, 0)
|
||||
board.place(1, [(0, 0)])
|
||||
assert not board.is_empty(0, 0)
|
||||
|
||||
def test_coverage(self):
|
||||
board = Board(2)
|
||||
assert board.coverage() == 0.0
|
||||
board.place(1, [(0, 0)])
|
||||
assert board.coverage() == 0.25
|
||||
|
||||
def test_get_occupied(self):
|
||||
board = Board(20)
|
||||
board.place(1, [(0, 0), (1, 0)])
|
||||
board.place(2, [(5, 5)])
|
||||
occupied = board.get_occupied()
|
||||
assert (0, 0) in occupied
|
||||
assert (1, 0) in occupied
|
||||
assert (5, 5) in occupied
|
||||
assert (2, 0) not in occupied
|
||||
@@ -0,0 +1,197 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import gymnasium as gym
|
||||
import numpy as np
|
||||
|
||||
from blokus_gym import (
|
||||
STANDARD_PIECES,
|
||||
ActionMaskWrapper,
|
||||
BlokusEnv,
|
||||
BlokusMultiAgentEnv,
|
||||
GreedyBot,
|
||||
)
|
||||
|
||||
|
||||
class TestBlokusEnv:
|
||||
def test_env_creation(self):
|
||||
env = BlokusEnv(num_players=4, board_size=20)
|
||||
assert env.num_players == 4
|
||||
assert env.board_size == 20
|
||||
|
||||
def test_observation_space(self):
|
||||
env = BlokusEnv(num_players=4, board_size=20)
|
||||
obs, _ = env.reset(seed=42)
|
||||
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_action_space(self):
|
||||
env = BlokusEnv(num_players=4, board_size=20)
|
||||
assert env.action_space.shape == ()
|
||||
assert env.action_space.n > 0
|
||||
|
||||
def test_reset_returns_obs_and_info(self):
|
||||
env = BlokusEnv(num_players=4, board_size=20)
|
||||
obs, info = env.reset(seed=42)
|
||||
assert isinstance(obs, dict)
|
||||
assert isinstance(info, dict)
|
||||
assert "action_mask" in info
|
||||
|
||||
def test_action_mask_valid(self):
|
||||
env = BlokusEnv(num_players=4, board_size=20)
|
||||
obs, info = env.reset(seed=42)
|
||||
mask = info["action_mask"]
|
||||
assert mask.dtype == bool
|
||||
assert mask.sum() > 0 # Should have valid moves
|
||||
|
||||
def test_step_with_valid_action(self):
|
||||
env = BlokusEnv(num_players=4, board_size=20)
|
||||
obs, info = env.reset(seed=42)
|
||||
valid_actions = np.where(info["action_mask"])[0]
|
||||
action = valid_actions[0]
|
||||
obs, reward, terminated, truncated, info = env.step(action)
|
||||
assert isinstance(reward, float)
|
||||
assert isinstance(terminated, bool)
|
||||
assert isinstance(truncated, bool)
|
||||
|
||||
def test_step_with_invalid_action(self):
|
||||
env = BlokusEnv(num_players=4, board_size=20)
|
||||
obs, info = env.reset(seed=42)
|
||||
# Use an action that's likely invalid
|
||||
obs, reward, terminated, truncated, info = env.step(env.action_space.n - 1)
|
||||
assert reward < 0 # Penalty for invalid action
|
||||
assert terminated # Should end episode
|
||||
|
||||
def test_two_player_env(self):
|
||||
env = BlokusEnv(num_players=2, board_size=14)
|
||||
obs, info = env.reset(seed=42)
|
||||
assert env.num_players == 2
|
||||
|
||||
def test_three_player_env(self):
|
||||
env = BlokusEnv(num_players=3, board_size=20)
|
||||
obs, info = env.reset(seed=42)
|
||||
assert env.num_players == 3
|
||||
|
||||
def test_custom_pieces(self):
|
||||
custom = [p for p in STANDARD_PIECES if p.size < 5]
|
||||
env = BlokusEnv(num_players=2, board_size=10, pieces=custom)
|
||||
obs, info = env.reset(seed=42)
|
||||
assert obs["pieces"].shape == (len(custom),)
|
||||
|
||||
def test_greedy_bot_opponent(self):
|
||||
env = BlokusEnv(num_players=2, board_size=7, bot_type=GreedyBot)
|
||||
obs, info = env.reset(seed=42)
|
||||
assert env.bots[1] is not None
|
||||
|
||||
def test_game_over(self):
|
||||
env = BlokusEnv(num_players=2, board_size=5, max_steps=10)
|
||||
obs, info = env.reset(seed=42)
|
||||
terminated = False
|
||||
truncated = False
|
||||
steps = 0
|
||||
while not (terminated or truncated) and steps < 50:
|
||||
valid = np.where(info["action_mask"])[0]
|
||||
if len(valid) == 0:
|
||||
break
|
||||
obs, reward, terminated, truncated, info = env.step(valid[0])
|
||||
steps += 1
|
||||
|
||||
def test_seed_reproducibility(self):
|
||||
env1 = BlokusEnv(num_players=2, board_size=7)
|
||||
env2 = BlokusEnv(num_players=2, board_size=7)
|
||||
obs1, _ = env1.reset(seed=42)
|
||||
obs2, _ = env2.reset(seed=42)
|
||||
np.testing.assert_array_equal(obs1["board"], obs2["board"])
|
||||
|
||||
def test_render_text(self):
|
||||
env = BlokusEnv(num_players=2, board_size=7, render_mode="ansi")
|
||||
obs, info = env.reset(seed=42)
|
||||
result = env.render()
|
||||
assert isinstance(result, str)
|
||||
|
||||
|
||||
class TestActionMaskWrapper:
|
||||
def test_wrapper_creation(self):
|
||||
env = BlokusEnv(num_players=2, board_size=7)
|
||||
env = ActionMaskWrapper(env)
|
||||
obs, info = env.reset(seed=42)
|
||||
assert "observation" in obs
|
||||
assert "action_mask" in obs
|
||||
|
||||
def test_wrapper_step(self):
|
||||
env = BlokusEnv(num_players=2, board_size=7)
|
||||
env = ActionMaskWrapper(env)
|
||||
obs, info = env.reset(seed=42)
|
||||
valid = np.where(obs["action_mask"])[0]
|
||||
obs, reward, terminated, truncated, info = env.step(valid[0])
|
||||
assert "observation" in obs
|
||||
assert "action_mask" in obs
|
||||
|
||||
|
||||
class TestMultiAgentEnv:
|
||||
def test_env_creation(self):
|
||||
env = BlokusMultiAgentEnv(num_players=2, board_size=7)
|
||||
assert env.num_players == 2
|
||||
assert len(env.agents) == 2
|
||||
|
||||
def test_reset(self):
|
||||
env = BlokusMultiAgentEnv(num_players=2, board_size=7)
|
||||
obs, info = env.reset(seed=42)
|
||||
assert "player_0" in obs
|
||||
|
||||
def test_step(self):
|
||||
env = BlokusMultiAgentEnv(num_players=2, board_size=7)
|
||||
obs, info = env.reset(seed=42)
|
||||
mask = env.get_action_mask("player_0")
|
||||
valid = np.where(mask)[0]
|
||||
obs, rewards, terminations, truncations, infos = env.step(valid[0])
|
||||
assert isinstance(rewards, dict)
|
||||
assert isinstance(terminations, dict)
|
||||
|
||||
def test_observation_space(self):
|
||||
env = BlokusMultiAgentEnv(num_players=2, board_size=7)
|
||||
obs, _ = env.reset(seed=42)
|
||||
for agent in env.agents:
|
||||
if agent in obs:
|
||||
assert "board" in obs[agent]
|
||||
|
||||
def test_action_space(self):
|
||||
env = BlokusMultiAgentEnv(num_players=2, board_size=7)
|
||||
env.reset(seed=42)
|
||||
space = env.action_space("player_0")
|
||||
assert space.n > 0
|
||||
|
||||
def test_four_player(self):
|
||||
env = BlokusMultiAgentEnv(num_players=4, board_size=7)
|
||||
obs, info = env.reset(seed=42)
|
||||
assert len(env.agents) == 4
|
||||
|
||||
|
||||
class TestRegisteredEnvs:
|
||||
def test_blokus_v0(self):
|
||||
env = gym.make("Blokus-v0")
|
||||
obs, info = env.reset(seed=42)
|
||||
assert obs["board"].shape == (20, 20)
|
||||
|
||||
def test_blokus_duo_v0(self):
|
||||
env = gym.make("BlokusDuo-v0")
|
||||
obs, info = env.reset(seed=42)
|
||||
assert obs["board"].shape == (14, 14)
|
||||
|
||||
def test_blokus_junior_v0(self):
|
||||
env = gym.make("BlokusJunior-v0")
|
||||
obs, info = env.reset(seed=42)
|
||||
assert obs["board"].shape == (14, 14)
|
||||
|
||||
def test_blokus_simple_v0(self):
|
||||
env = gym.make("BlokusSimple-v0")
|
||||
obs, info = env.reset(seed=42)
|
||||
assert obs["board"].shape == (7, 7)
|
||||
|
||||
def test_blokus_greedy_v0(self):
|
||||
env = gym.make("BlokusGreedy-v0")
|
||||
obs, info = env.reset(seed=42)
|
||||
assert obs["board"].shape == (20, 20)
|
||||
@@ -0,0 +1,196 @@
|
||||
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()
|
||||
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_package_import():
|
||||
import blokus_gym
|
||||
assert blokus_gym.__version__ == "0.1.0"
|
||||
|
||||
|
||||
def test_core_imports():
|
||||
from blokus_gym import BlokusGame, Board, Move
|
||||
assert Board is not None
|
||||
assert BlokusGame is not None
|
||||
assert Move is not None
|
||||
|
||||
|
||||
def test_env_imports():
|
||||
from blokus_gym import BlokusEnv, BlokusMultiAgentEnv
|
||||
assert BlokusEnv is not None
|
||||
assert BlokusMultiAgentEnv is not None
|
||||
|
||||
|
||||
def test_wrapper_imports():
|
||||
from blokus_gym import ActionMaskWrapper
|
||||
assert ActionMaskWrapper is not None
|
||||
|
||||
|
||||
def test_piece_imports():
|
||||
from blokus_gym import (
|
||||
DUO_PIECES,
|
||||
JUNIOR_PIECES,
|
||||
STANDARD_PIECES,
|
||||
Piece,
|
||||
PieceSet,
|
||||
)
|
||||
assert len(STANDARD_PIECES) == 21
|
||||
assert len(DUO_PIECES) > 0
|
||||
assert len(JUNIOR_PIECES) > 0
|
||||
assert Piece is not None
|
||||
assert PieceSet is not None
|
||||
|
||||
|
||||
def test_bot_imports():
|
||||
from blokus_gym import GreedyBot, GreedyCornersBot, MinimaxBot, RandomBot
|
||||
assert RandomBot is not None
|
||||
assert GreedyBot is not None
|
||||
assert GreedyCornersBot is not None
|
||||
assert MinimaxBot is not None
|
||||
|
||||
|
||||
def test_all_exports():
|
||||
import blokus_gym
|
||||
expected = [
|
||||
"BlokusEnv",
|
||||
"BlokusMultiAgentEnv",
|
||||
"ActionMaskWrapper",
|
||||
"Board",
|
||||
"BlokusGame",
|
||||
"Move",
|
||||
"Piece",
|
||||
"PieceOrientation",
|
||||
"PieceSet",
|
||||
"STANDARD_PIECES",
|
||||
"DUO_PIECES",
|
||||
"JUNIOR_PIECES",
|
||||
"generate_orientations",
|
||||
"Bot",
|
||||
"RandomBot",
|
||||
"GreedyBot",
|
||||
"GreedyCornersBot",
|
||||
"MinimaxBot",
|
||||
"__version__",
|
||||
]
|
||||
for name in expected:
|
||||
assert hasattr(blokus_gym, name), f"Missing export: {name}"
|
||||
@@ -0,0 +1,85 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from blokus_gym.core.pieces import (
|
||||
STANDARD_PIECES,
|
||||
Piece,
|
||||
PieceOrientation,
|
||||
PieceSet,
|
||||
generate_orientations,
|
||||
)
|
||||
|
||||
|
||||
class TestPiece:
|
||||
def test_piece_creation(self):
|
||||
piece = Piece(name="F", squares=frozenset([(0, 0), (1, 0), (1, 1), (2, 1)]))
|
||||
assert piece.name == "F"
|
||||
assert piece.size == 4
|
||||
assert len(piece.squares) == 4
|
||||
|
||||
def test_piece_size(self):
|
||||
piece = Piece(name="I4", squares=frozenset([(0, 0), (1, 0), (2, 0), (3, 0)]))
|
||||
assert piece.size == 4
|
||||
|
||||
def test_standard_pieces_count(self):
|
||||
assert len(STANDARD_PIECES) == 21
|
||||
|
||||
def test_standard_pieces_total_squares(self):
|
||||
total = sum(p.size for p in STANDARD_PIECES)
|
||||
assert total == 89
|
||||
|
||||
def test_piece_orientations_via_pieceset(self):
|
||||
piece_set = PieceSet(STANDARD_PIECES)
|
||||
orientations = piece_set.get_orientations(0)
|
||||
assert len(orientations) > 0
|
||||
for orient in orientations:
|
||||
assert isinstance(orient, PieceOrientation)
|
||||
|
||||
|
||||
class TestPieceSet:
|
||||
def test_piece_set_creation(self):
|
||||
piece_set = PieceSet(STANDARD_PIECES)
|
||||
assert piece_set.num_pieces == 21
|
||||
assert len(piece_set.pieces) == 21
|
||||
|
||||
def test_piece_set_custom(self):
|
||||
custom = [p for p in STANDARD_PIECES if p.size <= 3]
|
||||
piece_set = PieceSet(custom)
|
||||
assert piece_set.num_pieces == len(custom)
|
||||
|
||||
def test_piece_set_get_piece_id(self):
|
||||
piece_set = PieceSet(STANDARD_PIECES)
|
||||
piece_id = piece_set.get_piece_id("F5")
|
||||
assert piece_set.get_piece(piece_id).name == "F5"
|
||||
|
||||
def test_piece_set_get_orientations(self):
|
||||
piece_set = PieceSet(STANDARD_PIECES)
|
||||
for i in range(piece_set.num_pieces):
|
||||
orientations = piece_set.get_orientations(i)
|
||||
assert len(orientations) > 0
|
||||
|
||||
|
||||
class TestGenerateOrientations:
|
||||
def test_square_piece_one_orientation(self):
|
||||
piece = Piece("O4", frozenset([(0, 0), (0, 1), (1, 0), (1, 1)]))
|
||||
orientations = generate_orientations(piece)
|
||||
assert len(orientations) == 1
|
||||
|
||||
def test_line_piece_two_orientations(self):
|
||||
piece = Piece("I3", frozenset([(0, 0), (1, 0), (2, 0)]))
|
||||
orientations = generate_orientations(piece)
|
||||
assert len(orientations) == 2
|
||||
|
||||
def test_L_piece_four_orientations(self):
|
||||
piece = Piece("L3", frozenset([(0, 0), (0, 1), (1, 0)]))
|
||||
orientations = generate_orientations(piece)
|
||||
assert len(orientations) == 4
|
||||
|
||||
def test_all_orientations_unique(self):
|
||||
piece_set = PieceSet(STANDARD_PIECES)
|
||||
for piece_id in range(piece_set.num_pieces):
|
||||
orientations = piece_set.get_orientations(piece_id)
|
||||
unique_cells = set()
|
||||
for orient in orientations:
|
||||
key = tuple(sorted(orient.squares))
|
||||
assert key not in unique_cells, f"Duplicate orientation in piece {piece_id}"
|
||||
unique_cells.add(key)
|
||||
Reference in New Issue
Block a user