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
7 changes: 6 additions & 1 deletion catanatron/catanatron/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,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,
vps_to_win=vps_to_win,
)
self.playable_actions = generate_playable_actions(self.state)

def play(self, accumulators=[], decide_fn=None):
Expand Down
5 changes: 5 additions & 0 deletions catanatron/catanatron/models/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@


def generate_playable_actions(state: State) -> List[Action]:
# If someone won, no more actions.
for color in state.colors:
if get_actual_victory_points(state, color) >= state.vps_to_win:
return []

action_prompt = state.current_prompt
color = state.current_color()

Expand Down
3 changes: 3 additions & 0 deletions catanatron/catanatron/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ def __init__(
players: Sequence[Player],
catan_map=None,
discard_limit=7,
vps_to_win=10,
initialize=True,
):
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.vps_to_win = vps_to_win

# feature-ready dictionary
self.player_state = dict()
Expand Down Expand Up @@ -152,6 +154,7 @@ def copy(self):
state_copy = State([], None, initialize=False)
state_copy.players = self.players
state_copy.discard_limit = self.discard_limit # immutable
state_copy.vps_to_win = self.vps_to_win

state_copy.board = self.board.copy()

Expand Down
30 changes: 30 additions & 0 deletions tests/test_immediate_win.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

from catanatron.game import Game
from catanatron.models.player import Color, SimplePlayer
from catanatron.models.actions import generate_playable_actions
from catanatron.state_functions import player_key

def test_immediate_win_logic():
# 1. Initialize a game
players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)]
vps_to_win = 10
game = Game(players, vps_to_win=vps_to_win)

p0 = game.state.current_player()
p0_key = player_key(game.state, p0.color)

# 2. Initially p0 should have some actions (ROLL or initial build)
actions = generate_playable_actions(game.state)
assert len(actions) > 0

# 3. Buff p0 to 10 VPs manually
game.state.player_state[f"{p0_key}_ACTUAL_VICTORY_POINTS"] = 10

# 4. Verify generate_playable_actions returns empty list IMMEDIATELY
actions = generate_playable_actions(game.state)
assert actions == [], f"Expected 0 actions, got {len(actions)}"

print("Test passed: generate_playable_actions returns empty list once VPs reached!")

if __name__ == "__main__":
test_immediate_win_logic()