Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion catanatron/catanatron/apply_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
9 changes: 8 additions & 1 deletion catanatron/catanatron/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -110,14 +111,20 @@ 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)
random.seed(self.seed)

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):
Expand Down
1 change: 1 addition & 0 deletions catanatron/catanatron/gym/envs/catanatron_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions catanatron/catanatron/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
70 changes: 70 additions & 0 deletions tests/test_dice_restrictions.py
Original file line number Diff line number Diff line change
@@ -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