From 9b2a6735b651ce5b0c7d5e14c613d489988e9fd7 Mon Sep 17 00:00:00 2001 From: Jonathan Lamontagne Kratz <77954172+epicgamer17@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:46:00 -0400 Subject: [PATCH] fixed the dice rolling on MINI map so that it doesnt roll dice values not on the board. added a check in apply actions if the value is not on the board (or a 7) simply roll again --- catanatron/catanatron/apply_action.py | 13 +++- catanatron/catanatron/game.py | 9 ++- .../catanatron/gym/envs/catanatron_env.py | 1 + catanatron/catanatron/state.py | 3 + tests/test_dice_restrictions.py | 70 +++++++++++++++++++ 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 tests/test_dice_restrictions.py diff --git a/catanatron/catanatron/apply_action.py b/catanatron/catanatron/apply_action.py index 85672a1c..c102a41b 100644 --- a/catanatron/catanatron/apply_action.py +++ b/catanatron/catanatron/apply_action.py @@ -261,7 +261,18 @@ def apply_roll(state: State, action: Action, action_record=None): key = player_key(state, action.color) state.player_state[f"{key}_HAS_ROLLED"] = True - dices = action_record.result if action_record is not None else roll_dice() + if action_record is not None: + dices = action_record.result + elif state.restrict_dice_to_board: + allowed = state.board.map.land_tile_numbers + allowed.add(7) + while True: + dices = roll_dice() + if sum(dices) in allowed: + break + else: + dices = roll_dice() + number = dices[0] + dices[1] action = Action(action.color, action.action_type, dices) diff --git a/catanatron/catanatron/game.py b/catanatron/catanatron/game.py index 8eff1436..6ada8d1c 100644 --- a/catanatron/catanatron/game.py +++ b/catanatron/catanatron/game.py @@ -100,6 +100,7 @@ def __init__( vps_to_win: int = 10, catan_map: Optional[CatanMap] = None, initialize: bool = True, + restrict_dice_to_board: bool = False, ): """Creates a game (doesn't run it). @@ -110,6 +111,7 @@ def __init__( vps_to_win (int, optional): Victory Points needed to win. Defaults to 10. catan_map (CatanMap, optional): Map to use. Defaults to None. initialize (bool, optional): Whether to initialize. Defaults to True. + restrict_dice_to_board (bool, optional): Whether to restrict rolls to board numbers. Defaults to False. """ if initialize: self.seed = seed if seed is not None else random.randrange(sys.maxsize) @@ -117,7 +119,12 @@ def __init__( self.id = str(uuid.uuid4()) self.vps_to_win = vps_to_win - self.state = State(players, catan_map, discard_limit=discard_limit) + self.state = State( + players, + catan_map, + discard_limit=discard_limit, + restrict_dice_to_board=restrict_dice_to_board, + ) self.playable_actions = generate_playable_actions(self.state) def play(self, accumulators=[], decide_fn=None): diff --git a/catanatron/catanatron/gym/envs/catanatron_env.py b/catanatron/catanatron/gym/envs/catanatron_env.py index a1f43db9..07085d6f 100644 --- a/catanatron/catanatron/gym/envs/catanatron_env.py +++ b/catanatron/catanatron/gym/envs/catanatron_env.py @@ -235,6 +235,7 @@ def reset( seed=seed, catan_map=catan_map, vps_to_win=self.vps_to_win, + restrict_dice_to_board=self.config.get("restrict_dice_to_board", False), ) self.invalid_actions_count = 0 diff --git a/catanatron/catanatron/state.py b/catanatron/catanatron/state.py index 0659ddba..8a2260ef 100644 --- a/catanatron/catanatron/state.py +++ b/catanatron/catanatron/state.py @@ -89,12 +89,14 @@ def __init__( catan_map=None, discard_limit=7, initialize=True, + restrict_dice_to_board=False, ): if initialize: self.players = random.sample(players, len(players)) self.colors = tuple([player.color for player in self.players]) self.board = Board(catan_map or CatanMap.from_template(BASE_MAP_TEMPLATE)) self.discard_limit = discard_limit + self.restrict_dice_to_board = restrict_dice_to_board # feature-ready dictionary self.player_state = dict() @@ -184,4 +186,5 @@ def copy(self): state_copy.current_trade = self.current_trade state_copy.acceptees = self.acceptees + state_copy.restrict_dice_to_board = self.restrict_dice_to_board return state_copy diff --git a/tests/test_dice_restrictions.py b/tests/test_dice_restrictions.py new file mode 100644 index 00000000..8723bad8 --- /dev/null +++ b/tests/test_dice_restrictions.py @@ -0,0 +1,70 @@ +import pytest +import random +from catanatron.game import Game +from catanatron.models.player import SimplePlayer +from catanatron.models.player import Color +from catanatron.models.map import build_map +from catanatron.models.enums import Action, ActionType + +def test_dice_restriction_mini_map(): + # MINI map has numbers [3, 4, 5, 6, 8, 9, 10] + players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)] + catan_map = build_map("MINI") + + # Test case 1: Restrict to board, allow 7s + game = Game(players, catan_map=catan_map, restrict_dice_to_board=True, allow_sevens=True) + allowed = {3, 4, 5, 6, 7, 8, 9, 10} + + for _ in range(100): + game.state.player_state["P0_HAS_ROLLED"] = False + action = Action(Color.RED, ActionType.ROLL, None) + game.execute(action) + roll_sum = sum(game.state.last_roll) + assert roll_sum in allowed + +def test_dice_restriction_no_sevens(): + players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)] + catan_map = build_map("BASE") + + # Test case 2: Allow all board numbers, but NO 7s + game = Game(players, catan_map=catan_map, restrict_dice_to_board=False, allow_sevens=False) + + for _ in range(100): + game.state.player_state["P0_HAS_ROLLED"] = False + action = Action(Color.RED, ActionType.ROLL, None) + game.execute(action) + roll_sum = sum(game.state.last_roll) + assert roll_sum != 7 + assert 2 <= roll_sum <= 12 + +def test_dice_restriction_combined(): + # MINI map: [3, 4, 5, 6, 8, 9, 10] + players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)] + catan_map = build_map("MINI") + + # Test case 3: Restrict to board AND no 7s + game = Game(players, catan_map=catan_map, restrict_dice_to_board=True, allow_sevens=False) + allowed = {3, 4, 5, 6, 8, 9, 10} + + for _ in range(100): + game.state.player_state["P0_HAS_ROLLED"] = False + action = Action(Color.RED, ActionType.ROLL, None) + game.execute(action) + roll_sum = sum(game.state.last_roll) + assert roll_sum in allowed + assert roll_sum != 7 + +def test_bandit_map_restriction(): + # BANDIT map: [2, 3, 4, 5, 6, 8] + players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)] + catan_map = build_map("BANDIT") + + game = Game(players, catan_map=catan_map, restrict_dice_to_board=True, allow_sevens=False) + allowed = {2, 3, 4, 5, 6, 8} + + for _ in range(100): + game.state.player_state["P0_HAS_ROLLED"] = False + action = Action(Color.RED, ActionType.ROLL, None) + game.execute(action) + roll_sum = sum(game.state.last_roll) + assert roll_sum in allowed