diff --git a/catanatron_core/catanatron/models/actions.py b/catanatron_core/catanatron/models/actions.py
index 9bb414dc..1657ccaa 100644
--- a/catanatron_core/catanatron/models/actions.py
+++ b/catanatron_core/catanatron/models/actions.py
@@ -51,14 +51,26 @@ def generate_playable_actions(state) -> List[Action]:
elif action_prompt == ActionPrompt.MOVE_ROBBER:
return robber_possibilities(state, color)
elif action_prompt == ActionPrompt.PLAY_TURN:
+ actions = []
+ # Allow playing dev cards before and after rolling
+ if player_can_play_dev(state, color, "YEAR_OF_PLENTY"):
+ actions.extend(year_of_plenty_possibilities(color, state.resource_freqdeck))
+ if player_can_play_dev(state, color, "MONOPOLY"):
+ actions.extend(monopoly_possibilities(color))
+ if player_can_play_dev(state, color, "KNIGHT"):
+ actions.append(Action(color, ActionType.PLAY_KNIGHT_CARD, None))
+ if (
+ player_can_play_dev(state, color, "ROAD_BUILDING")
+ and len(road_building_possibilities(state, color, False)) > 0
+ ):
+ actions.append(Action(color, ActionType.PLAY_ROAD_BUILDING, None))
+
if state.is_road_building:
actions = road_building_possibilities(state, color, False)
elif not player_has_rolled(state, color):
- actions = [Action(color, ActionType.ROLL, None)]
- if player_can_play_dev(state, color, "KNIGHT"):
- actions.append(Action(color, ActionType.PLAY_KNIGHT_CARD, None))
+ actions.append(Action(color, ActionType.ROLL, None))
else:
- actions = [Action(color, ActionType.END_TURN, None)]
+ actions.append(Action(color, ActionType.END_TURN, None))
actions.extend(road_building_possibilities(state, color))
actions.extend(settlement_possibilities(state, color))
actions.extend(city_possibilities(state, color))
@@ -70,21 +82,6 @@ def generate_playable_actions(state) -> List[Action]:
if can_buy_dev_card:
actions.append(Action(color, ActionType.BUY_DEVELOPMENT_CARD, None))
- # Play Dev Cards
- if player_can_play_dev(state, color, "YEAR_OF_PLENTY"):
- actions.extend(
- year_of_plenty_possibilities(color, state.resource_freqdeck)
- )
- if player_can_play_dev(state, color, "MONOPOLY"):
- actions.extend(monopoly_possibilities(color))
- if player_can_play_dev(state, color, "KNIGHT"):
- actions.append(Action(color, ActionType.PLAY_KNIGHT_CARD, None))
- if (
- player_can_play_dev(state, color, "ROAD_BUILDING")
- and len(road_building_possibilities(state, color, False)) > 0
- ):
- actions.append(Action(color, ActionType.PLAY_ROAD_BUILDING, None))
-
# Trade
actions.extend(maritime_trade_possibilities(state, color))
return actions
diff --git a/catanatron_core/catanatron/state.py b/catanatron_core/catanatron/state.py
index 665eab8c..2a3c98d5 100644
--- a/catanatron_core/catanatron/state.py
+++ b/catanatron_core/catanatron/state.py
@@ -76,6 +76,10 @@
# de-normalized features (for performance since we think they are good features)
"ACTUAL_VICTORY_POINTS": 0,
"LONGEST_ROAD_LENGTH": 0,
+ "KNIGHT_PURCHASED_THIS_TURN": 0,
+ "MONOPOLY_PURCHASED_THIS_TURN": 0,
+ "YEAR_OF_PLENTY_PURCHASED_THIS_TURN": 0,
+ "ROAD_BUILDING_PURCHASED_THIS_TURN": 0,
}
for resource in RESOURCES:
PLAYER_INITIAL_STATE[f"{resource}_IN_HAND"] = 0
diff --git a/catanatron_core/catanatron/state_functions.py b/catanatron_core/catanatron/state_functions.py
index acf71799..4b71391d 100644
--- a/catanatron_core/catanatron/state_functions.py
+++ b/catanatron_core/catanatron/state_functions.py
@@ -225,9 +225,17 @@ def player_resource_freqdeck_contains(state, color, freqdeck):
def player_can_play_dev(state, color, dev_card):
key = player_key(state, color)
+
+ cards_in_hand = state.player_state[f"{key}_{dev_card}_IN_HAND"]
+ cards_purchased_this_turn = state.player_state[
+ f"{key}_{dev_card}_PURCHASED_THIS_TURN"
+ ]
+ playable_cards = cards_in_hand - cards_purchased_this_turn
+
return (
not state.player_state[f"{key}_HAS_PLAYED_DEVELOPMENT_CARD_IN_TURN"]
- and state.player_state[f"{key}_{dev_card}_IN_HAND"] >= 1
+ # Must have at least 1 card that wasn't purchased this turn
+ and playable_cards > 0
)
@@ -259,6 +267,9 @@ def buy_dev_card(state, color, dev_card):
state.player_state[f"{key}_{dev_card}_IN_HAND"] += 1
if dev_card == VICTORY_POINT:
state.player_state[f"{key}_ACTUAL_VICTORY_POINTS"] += 1
+ else:
+ # Mark as purchased this turn
+ state.player_state[f"{key}_{dev_card}_PURCHASED_THIS_TURN"] += 1
state.player_state[f"{key}_SHEEP_IN_HAND"] -= 1
state.player_state[f"{key}_WHEAT_IN_HAND"] -= 1
@@ -334,3 +345,8 @@ def player_clean_turn(state, color):
key = player_key(state, color)
state.player_state[f"{key}_HAS_PLAYED_DEVELOPMENT_CARD_IN_TURN"] = False
state.player_state[f"{key}_HAS_ROLLED"] = False
+ # Reset all purchased-this-turn counters
+ state.player_state[f"{key}_KNIGHT_PURCHASED_THIS_TURN"] = 0
+ state.player_state[f"{key}_MONOPOLY_PURCHASED_THIS_TURN"] = 0
+ state.player_state[f"{key}_YEAR_OF_PLENTY_PURCHASED_THIS_TURN"] = 0
+ state.player_state[f"{key}_ROAD_BUILDING_PURCHASED_THIS_TURN"] = 0
diff --git a/tests/test_game.py b/tests/test_game.py
index c00d7561..80b4f503 100644
--- a/tests/test_game.py
+++ b/tests/test_game.py
@@ -327,10 +327,7 @@ def test_play_road_building(fake_roll_dice):
):
game.play_tick()
- # roll not a 7
- fake_roll_dice.return_value = (1, 2)
- game.play_tick() # roll
-
+ # Play Road Building before rolling
game.execute(Action(p0.color, ActionType.PLAY_ROAD_BUILDING, None))
assert game.state.is_road_building
assert game.state.free_roads_available == 2
diff --git a/ui/src/pages/ActionsToolbar.js b/ui/src/pages/ActionsToolbar.js
index 74285758..2933c844 100644
--- a/ui/src/pages/ActionsToolbar.js
+++ b/ui/src/pages/ActionsToolbar.js
@@ -47,7 +47,7 @@ function PlayButtons() {
[enqueueSnackbar, closeSnackbar]
);
- const { gameState, isPlayingMonopoly, isPlayingYearOfPlenty } = state;
+ const { gameState, isPlayingMonopoly, isPlayingYearOfPlenty, isRoadBuilding } = state;
const key = playerKey(gameState, gameState.current_color);
const isRoll =
gameState.current_prompt === "PLAY_TURN" &&
@@ -198,7 +198,7 @@ function PlayButtons() {
return (
<>
}
items={useItems}
@@ -206,7 +206,7 @@ function PlayButtons() {
Use
}
items={buildItems}
@@ -214,7 +214,7 @@ function PlayButtons() {
Buy
}
items={tradeItems}
@@ -222,27 +222,27 @@ function PlayButtons() {
Trade
}
onClick={
- isRoll
- ? rollAction
- : isDiscard
+ isDiscard
? proceedAction
: isMoveRobber
? setIsMovingRobber
: isPlayingYearOfPlenty || isPlayingMonopoly
? handleOpenResourceSelector
+ : isRoll
+ ? rollAction
: endTurnAction
}
>
{
- isRoll ? "ROLL" :
isDiscard ? "DISCARD" :
isMoveRobber ? "ROB" :
isPlayingYearOfPlenty || isPlayingMonopoly ? "SELECT" :
+ isRoll ? "ROLL" :
"END"
}