Skip to content

Commit

Permalink
Merge pull request #302 from zarns/feature/zarns/catanatron_rust
Browse files Browse the repository at this point in the history
Feature/zarns/catanatron rust
  • Loading branch information
bcollazo authored Feb 10, 2025
2 parents 454a908 + e5046bb commit f88ad0a
Show file tree
Hide file tree
Showing 7 changed files with 897 additions and 190 deletions.
90 changes: 68 additions & 22 deletions catanatron_rust/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,74 @@ pub enum ActionPrompt {

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Action {
// The first value in all these is the color of the player.
Roll(u8, Option<(u8, u8)>), // None. Log instead sets it to (int, int) rolled.
MoveRobber(u8, Coordinate, Option<u8>), // Log has extra element of card stolen.
Discard(u8), // value is None|Resource[].
BuildRoad(u8, EdgeId),
BuildSettlement(u8, NodeId),
BuildCity(u8, NodeId),
BuyDevelopmentCard(u8), // value is None. Log value is card.
PlayKnight(u8),
PlayYearOfPlenty(u8, [u8; 2]), // Two resources to take from bank
PlayMonopoly(u8, u8), // value is Resource
PlayRoadBuilding(u8),

// First element of tuples is in, last is out.
MaritimeTrade(u8, (u8, u8, u8)), // (Give Resource, Get Resource, Ratio)
OfferTrade(u8, (FreqDeck, FreqDeck)),
AcceptTrade(u8, (FreqDeck, FreqDeck)),
RejectTrade(u8),
ConfirmTrade(u8, (FreqDeck, FreqDeck, u8)), // 11-tuple. First 10 like OfferTrade, last is color of accepting player.
CancelTrade(u8),

EndTurn(u8), // None
Roll {
color: u8,
dice_opt: Option<(u8, u8)>,
},
MoveRobber {
color: u8,
coordinate: Coordinate,
victim_opt: Option<u8>,
},
Discard {
color: u8,
},
BuildRoad {
color: u8,
edge_id: EdgeId,
},
BuildSettlement {
color: u8,
node_id: NodeId,
},
BuildCity {
color: u8,
node_id: NodeId,
},
BuyDevelopmentCard {
color: u8,
},
PlayKnight {
color: u8,
},
PlayYearOfPlenty {
color: u8,
resources: (u8, Option<u8>),
},
PlayMonopoly {
color: u8,
resource: u8,
},
PlayRoadBuilding {
color: u8,
},
MaritimeTrade {
color: u8,
give: u8,
take: u8,
ratio: u8,
},
OfferTrade {
color: u8,
trade: (FreqDeck, FreqDeck),
},
AcceptTrade {
color: u8,
trade: (FreqDeck, FreqDeck),
},
RejectTrade {
color: u8,
},
ConfirmTrade {
color: u8,
trade: (FreqDeck, FreqDeck, u8),
},
CancelTrade {
color: u8,
},
EndTurn {
color: u8,
},
}

#[derive(Debug)]
Expand Down
16 changes: 10 additions & 6 deletions catanatron_rust/src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,11 @@ mod tests {
play_tick(&players, &mut state); // third player road
let second_player_second_settlement_action = play_tick(&players, &mut state);
let second_player_second_node_id;
if let Action::BuildSettlement(player, node_id) = second_player_second_settlement_action {
if let Action::BuildSettlement {
color: player,
node_id,
} = second_player_second_settlement_action
{
assert_eq!(player, second_player);
second_player_second_node_id = node_id;
} else {
Expand All @@ -160,7 +164,7 @@ mod tests {
assert_all_build_roads(playable_actions.clone(), second_player);
// assert playable_actions are connected to the last settlement
assert!(playable_actions.iter().all(|e| {
if let Action::BuildRoad(_, edge_id) = e {
if let Action::BuildRoad { edge_id, .. } = e {
second_player_second_node_id == edge_id.0
|| second_player_second_node_id == edge_id.1
} else {
Expand Down Expand Up @@ -208,8 +212,8 @@ mod tests {
fn assert_all_build_settlements(playable_actions: Vec<Action>, player: u8) {
assert!(
playable_actions.iter().all(|e| {
if let Action::BuildSettlement(p, _) = e {
*p == player
if let Action::BuildSettlement { color, .. } = e {
*color == player
} else {
false
}
Expand All @@ -222,8 +226,8 @@ mod tests {
fn assert_all_build_roads(playable_actions: Vec<Action>, player: u8) {
assert!(
playable_actions.iter().all(|e| {
if let Action::BuildRoad(p, _) = e {
*p == player
if let Action::BuildRoad { color, .. } = e {
*color == player
} else {
false
}
Expand Down
29 changes: 25 additions & 4 deletions catanatron_rust/src/map_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub enum Tile {
pub struct MapInstance {
tiles: HashMap<Coordinate, Tile>,
land_tiles: HashMap<Coordinate, LandTile>,
port_nodes: HashSet<NodeId>,
port_nodes: HashMap<NodeId, Option<Resource>>,
adjacent_land_tiles: HashMap<NodeId, Vec<LandTile>>,
node_production: HashMap<NodeId, HashMap<Resource, f64>>,

Expand All @@ -130,6 +130,9 @@ pub struct MapInstance {
// - BFS capabilities
// all which doesn't sound too bad to implement.
land_nodes: HashSet<NodeId>,

// TODO: Track valid edges for building roads.
#[allow(dead_code)]
land_edges: HashSet<EdgeId>,
node_neighbors: HashMap<NodeId, Vec<NodeId>>,
edge_neighbors: HashMap<NodeId, Vec<EdgeId>>,
Expand All @@ -148,6 +151,18 @@ impl MapInstance {
&self.land_nodes
}

pub fn get_port_nodes(&self) -> &HashMap<NodeId, Option<Resource>> {
&self.port_nodes
}

pub fn get_node_production(&self, node_id: NodeId) -> Option<&HashMap<Resource, f64>> {
self.node_production.get(&node_id)
}

pub fn get_all_node_production(&self) -> &HashMap<NodeId, HashMap<Resource, f64>> {
&self.node_production
}

pub fn get_tile(&self, coordinate: Coordinate) -> Option<&Tile> {
self.tiles.get(&coordinate)
}
Expand Down Expand Up @@ -259,7 +274,7 @@ impl MapInstance {

fn from_tiles(tiles: HashMap<Coordinate, Tile>, dice_probas: &HashMap<u8, f64>) -> Self {
let mut land_tiles: HashMap<Coordinate, LandTile> = HashMap::new();
let mut port_nodes: HashSet<NodeId> = HashSet::new();
let mut port_nodes: HashMap<NodeId, Option<Resource>> = HashMap::new();
let mut adjacent_land_tiles: HashMap<NodeId, Vec<LandTile>> = HashMap::new();
let mut node_production: HashMap<NodeId, HashMap<Resource, f64>> = HashMap::new();

Expand Down Expand Up @@ -310,8 +325,14 @@ impl MapInstance {
});
} else if let Tile::Port(port_tile) = tile {
let (a_noderef, b_noderef) = get_noderefs_from_port_direction(port_tile.direction);
port_nodes.insert(*port_tile.hexagon.nodes.get(&a_noderef).unwrap());
port_nodes.insert(*port_tile.hexagon.nodes.get(&b_noderef).unwrap());
port_nodes.insert(
*port_tile.hexagon.nodes.get(&a_noderef).unwrap(),
port_tile.resource,
);
port_nodes.insert(
*port_tile.hexagon.nodes.get(&b_noderef).unwrap(),
port_tile.resource,
);
}
}

Expand Down
70 changes: 67 additions & 3 deletions catanatron_rust/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
player_played_devhand_slice, seating_order_slice, StateVector, BANK_RESOURCE_SLICE,
CURRENT_TICK_SEAT_INDEX, FREE_ROADS_AVAILABLE_INDEX, HAS_PLAYED_DEV_CARD, HAS_ROLLED_INDEX,
IS_DISCARDING_INDEX, IS_INITIAL_BUILD_PHASE_INDEX, IS_MOVING_ROBBER_INDEX,
ROBBER_TILE_INDEX,
},
};

Expand Down Expand Up @@ -403,13 +404,13 @@ impl State {
self.vector[BANK_RESOURCE_SLICE][resource as usize] > 0
}

pub fn take_from_bank_give_to_player(&mut self, color: u8, resource: u8) {
pub fn from_bank_to_player(&mut self, color: u8, resource: u8) {
let resource_idx = resource as usize;
self.vector[BANK_RESOURCE_SLICE][resource_idx] -= 1;
self.get_mut_player_hand(color)[resource_idx] += 1;
}

pub fn take_from_player_give_to_bank(&mut self, color: u8, resource: u8, amount: u8) {
pub fn from_player_to_bank(&mut self, color: u8, resource: u8, amount: u8) {
let resource_idx = resource as usize;
self.get_mut_player_hand(color)[resource_idx] -= amount;
self.vector[BANK_RESOURCE_SLICE][resource_idx] += amount;
Expand All @@ -419,7 +420,7 @@ impl State {
self.get_player_hand(color)[resource as usize]
}

pub fn take_from_player_give_to_player(
pub fn from_player_to_player(
&mut self,
from_color: u8,
to_color: u8,
Expand All @@ -430,6 +431,69 @@ impl State {
self.get_mut_player_hand(from_color)[resource_idx] -= amount;
self.get_mut_player_hand(to_color)[resource_idx] += amount;
}

pub fn get_robber_tile(&self) -> u8 {
self.vector[ROBBER_TILE_INDEX]
}

pub fn set_robber_tile(&mut self, tile_id: u8) {
self.vector[ROBBER_TILE_INDEX] = tile_id;
}

pub fn get_bank_resources(&self) -> &[u8] {
&self.vector[BANK_RESOURCE_SLICE]
}

pub fn set_bank_resource(&mut self, resource_index: usize, count: u8) {
self.vector[BANK_RESOURCE_SLICE.start + resource_index] = count;
}

/// Calculates effective production (considering robber) for a player
pub fn get_effective_production(&self, color: u8) -> Vec<f64> {
self.get_player_production_internal(color, true)
}

/// Calculates total production (ignoring robber) for a player
pub fn get_total_production(&self, color: u8) -> Vec<f64> {
self.get_player_production_internal(color, false)
}

fn get_player_production_internal(&self, color: u8, consider_robber: bool) -> Vec<f64> {
let mut production = vec![0.0; 5]; // One for each resource
let robber_tile = if consider_robber {
Some(self.get_robber_tile())
} else {
None
};

// Get all buildings for this player
if let Some(buildings) = self.buildings_by_color.get(&color) {
for building in buildings {
let (node_id, multiplier) = match building {
Building::Settlement(_, node) => (*node, 1.0),
Building::City(_, node) => (*node, 2.0),
};

// Skip if robber is blocking this node
if let Some(robber_id) = robber_tile {
if let Some(adjacent_tiles) = self.map_instance.get_adjacent_tiles(node_id) {
if adjacent_tiles.iter().any(|tile| tile.id == robber_id) {
continue;
}
}
}

// Get production for this node
if let Some(node_prod) = self.map_instance.get_node_production(node_id) {
for (resource, prob) in node_prod {
production[*resource as usize] += prob * multiplier;
}
}
}
}

production
}
}

#[cfg(test)]
Expand Down
Loading

1 comment on commit f88ad0a

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance Alert

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.30.

Benchmark suite Current: f88ad0a Previous: f2b016d Ratio
tests/integration_tests/test_speed.py::test_alphabeta_speed 0.8670163466015203 iter/sec (stddev: 1.1742265344538412) 1.6926291989952666 iter/sec (stddev: 0.9926622981793083) 1.95

This comment was automatically generated by workflow.

Please sign in to comment.