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
37 changes: 19 additions & 18 deletions catanatron/catanatron/models/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,26 +291,27 @@ def inner_maritime_trade_possibilities(hand_freqdeck, bank_freqdeck, port_resour
"""This inner function is to make this logic more shareable"""
trade_offers = set()

# Get lowest rate per resource
rates: Dict[FastResource, int] = {WOOD: 4, BRICK: 4, SHEEP: 4, WHEAT: 4, ORE: 4}
if None in port_resources:
rates = {WOOD: 3, BRICK: 3, SHEEP: 3, WHEAT: 3, ORE: 3}
for resource in port_resources:
if resource != None:
rates[resource] = 2

# For resource in hand
for index, resource in enumerate(RESOURCES):
amount = hand_freqdeck[index]
if amount >= rates[resource]:
resource_out: List[Any] = [resource] * rates[resource]
resource_out += [None] * (4 - rates[resource])
for j_resource in RESOURCES:
if (
resource != j_resource
and freqdeck_count(bank_freqdeck, j_resource) > 0
):
trade_offer = tuple(resource_out + [j_resource])
trade_offers.add(trade_offer)

# Get all possible rates for this resource
possible_rates = {4}
if None in port_resources:
possible_rates.add(3)
if resource in port_resources:
possible_rates.add(2)

for rate in possible_rates:
if amount >= rate:
resource_out: List[Any] = [resource] * rate
resource_out += [None] * (4 - rate)
for j_resource in RESOURCES:
if (
resource != j_resource
and freqdeck_count(bank_freqdeck, j_resource) > 0
):
trade_offer = tuple(resource_out + [j_resource])
trade_offers.add(trade_offer)

return trade_offers
110 changes: 110 additions & 0 deletions tests/test_maritime_trades.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import pytest
from catanatron.state import State
from catanatron.models.player import Color, SimplePlayer
from catanatron.models.enums import (
BRICK,
ORE,
SHEEP,
WHEAT,
WOOD,
ActionType,
)
from catanatron.models.actions import maritime_trade_possibilities
from catanatron.state_functions import player_deck_replenish

def test_maritime_trade_no_ports_only_4_to_1():
players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)]
state = State(players)

# Player has 4 woods
player_deck_replenish(state, Color.RED, WOOD, 4)

actions = maritime_trade_possibilities(state, Color.RED)

# Extract trade values
trades = [a.value for a in actions]

# Should only have 4:1 trades (4 WOODs, 1 j_resource)
# Trade offer is (r1, r2, r3, r4, r_asked)
for trade in trades:
# Check that it's 4:1
assert trade[:4].count(WOOD) == 4
assert trade[4] != WOOD

def test_maritime_trade_general_port_allows_3_to_1_and_4_to_1():
players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)]
state = State(players)

# Give player a general port (e.g., at node 0 if it's a port node,
# but easier to just mock get_player_port_resources or state.board)
# Let's use the actual board for realism.
# Node 0, 1 are ports in BASE_MAP_TEMPLATE (usually)
# Actually, let's look at the map nodes or just trust the logic.

# Instead of building, let's just force the port resources for the test
# by mocking get_player_port_resources because we want to test actions logic.
player_deck_replenish(state, Color.RED, WOOD, 4)

# Mocking port resources
state.board.get_player_port_resources = lambda color: {None}

actions = maritime_trade_possibilities(state, Color.RED)
trades = [a.value for a in actions]

# Should have both 3:1 and 4:1 trades
rates = set()
for trade in trades:
rate = trade[:4].count(WOOD)
rates.add(rate)

assert 3 in rates
assert 4 in rates

def test_maritime_trade_specific_port_allows_2_to_1_3_to_1_and_4_to_1():
players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)]
state = State(players)

player_deck_replenish(state, Color.RED, WOOD, 4)

# Mocking port resources: 2:1 WOOD and General port
state.board.get_player_port_resources = lambda color: {WOOD, None}

actions = maritime_trade_possibilities(state, Color.RED)
trades = [a.value for a in actions]

rates = set()
for trade in trades:
if trade[0] == WOOD: # ensure we are looking at WOOD trades
rate = sum(1 for r in trade[:4] if r == WOOD)
rates.add(rate)

assert 2 in rates
assert 3 in rates
assert 4 in rates

def test_maritime_trade_specific_port_for_other_resource_does_not_affect_wood():
players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)]
state = State(players)

player_deck_replenish(state, Color.RED, WOOD, 4)

# Mocking port resources: 2:1 BRICK
state.board.get_player_port_resources = lambda color: {BRICK}

actions = maritime_trade_possibilities(state, Color.RED)
trades = [a.value for a in actions]

rates = set()
for trade in trades:
if trade[0] == WOOD:
rate = sum(1 for r in trade[:4] if r == WOOD)
rates.add(rate)

assert 2 not in rates
assert 3 not in rates
assert 4 in rates

if __name__ == "__main__":
import sys
import pytest
sys.exit(pytest.main([__file__]))