use rand::seq::SliceRandom; use std::fmt; use Action::*; use Card::*; pub type CoupResult = Result; #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum Card { Duke, Assassin, Captain, Ambassador, Contessa, } impl Card { pub fn allows_action(self, action: Action) -> bool { matches!((self, action), (_, Income) | (_, ForeignAid) | (Duke, Tax) | (Assassin, Assassinate) | (Ambassador, Exchange) | (Captain, Steal)) } pub fn blocks_action(self, action: Action) -> bool { matches!((self, action), (Duke, Income) | (Captain, Steal) | (Ambassador, Steal) | (Contessa, Assassinate)) } } trait Stack { fn draw(&mut self, player: &mut Player) -> CoupResult<()>; fn draw_first(&mut self, card: Card) -> bool; fn shuffle(&mut self); } impl Stack for Vec { fn draw(&mut self, player: &mut Player) -> CoupResult<()> { match self.pop() { Some(card) => { player.cards.push(card); Ok(()) }, None => Err("Tried to draw from an empty deck!"), } } fn draw_first(&mut self, card: Card) -> bool { match self.iter().position(|c| *c == card) { Some(i) => { self.remove(i); true }, None => false, } } fn shuffle(&mut self) { let deck = self.as_mut_slice(); let mut rng = rand::thread_rng(); deck.shuffle(&mut rng); } } impl fmt::Display for Card { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = match self { Duke => "Duke", Assassin => "Assassin", Captain => "Captain", Ambassador => "Ambassador", Contessa => "Contessa", }; write!(f, "{}", name) } } #[repr(u8)] #[derive(Clone, Copy)] pub enum Action { Income, ForeignAid, Coup, Tax, Assassinate, Exchange, Steal, } impl Action { pub fn is_targeted(self) -> bool { matches!(self, Coup | Steal | Assassinate) } pub fn challenger_mode(self) -> ResMode { match self { Income | ForeignAid | Coup => ResMode::None, Assassinate | Steal | Tax | Exchange => ResMode::Anyone, } } pub fn blocker_mode(self) -> ResMode { match self { Income | Tax | Exchange | Coup => ResMode::None, Assassinate | Steal => ResMode::Target, ForeignAid => ResMode::Anyone, } } pub fn coin_cost(self) -> u8 { match self { Assassinate => 3, Coup => 7, _ => 0, } } } impl fmt::Display for Action { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = match self { Income => "Income", ForeignAid => "Foreign Aid", Coup => "Coup", Tax => "Tax", Assassinate => "Assassinate", Exchange => "Exchange", Steal => "Steal", }; write!(f, "{}", name) } } #[repr(u8)] #[derive(Clone, Copy)] pub enum ResMode { None, Target, Anyone, } #[derive(Clone)] pub struct Player { pub coins: u8, pub cards: Vec, } impl Player { pub fn is_alive(&self) -> bool { !self.cards.is_empty() } pub fn lose(&mut self, card: Card, deck: &mut Vec) { self.cards.draw_first(card); deck.push(card); } } impl Default for Player { fn default() -> Self { Player { cards: Vec::new(), coins: 2, } } } #[derive(Clone)] pub struct Game { pub players: Vec, pub deck: Vec, pub discard: Vec, pub turn: usize, } macro_rules! thrice { ($($e:expr),*) => { vec![$($e, $e, $e),*] } } impl Game { pub fn new(num_players: usize) -> Self { let mut deck = thrice![Duke, Assassin, Captain, Ambassador, Contessa]; deck.shuffle(); let mut players = Vec::new(); players.resize_with(num_players, Player::default); for player in &mut players { deck.draw(player).unwrap(); deck.draw(player).unwrap(); } Game { deck, players, discard: Vec::new(), turn: 0, } } pub fn is_game_over(&self) -> bool { self.players.iter().filter(|p| p.is_alive()).count() == 1 } fn turn_iterator(&self) -> impl Iterator + '_ { let players_turn_order = std::iter::successors(Some(self.turn), |p| { let next = (p + 1) % self.players.len(); if next == self.turn { None } else { Some(next) } }); players_turn_order.skip(1).filter(|p| self.players[*p].is_alive()) } fn resolution(&mut self, resolution: Resolution, agents: &[&dyn Agent]) -> CoupResult { let current_player = &mut self.players.get_mut(self.turn).unwrap(); let current_agent = agents[self.turn]; match resolution.action { Income => current_player.coins += 1, ForeignAid => current_player.coins += 2, Coup | Assassinate => match resolution.target { Some(target) => { let target_player = &self.players[target]; let card = agents[target].choose_lost_influence(&target_player.cards); self.players[target].lose(card, &mut self.discard); } _ => return Err("Coup/Assassinate resolution has no target"), }, Tax => current_player.coins += 3, Exchange => { let drawn = vec![self.deck.pop().unwrap(), self.deck.pop().unwrap()]; let hand = current_player.cards.clone(); let mut choices = [drawn, hand].concat(); let discarded = current_agent.exchange(&choices); for card in discarded { if !choices.draw_first(card) { return Err("Exchanged a card that was not in choices"); } else { self.deck.push(card); } } current_player.cards = choices; self.deck.shuffle(); } Steal => match resolution.target { Some(target) => { let val = self.players[target].coins.min(2); self.players[self.turn].coins += val; self.players[target].coins -= val; } _ => return Err("Steal resolution has no target"), } } let next = self.turn_iterator().next(); if let Some(next) = next { self.turn = next; } Ok(Phase::Done) } } enum Phase { Action(Action), //ActionChallenge(ActionChallenge), //Block(Block), //BlockChallenge(BlockChallenge), Resolution(Resolution), Done, } pub struct Resolution { action: Action, target: Option, } pub trait Agent : fmt::Debug { fn exchange(&self, cards: &[Card]) -> [Card; 2]; fn choose_lost_influence(&self, cards: &[Card]) -> Card; } #[cfg(test)] mod test { use super::*; #[test] fn test_resolution() { #[derive(Debug)] struct DummyAgent; impl Agent for DummyAgent { fn exchange(&self, cards: &[Card]) -> [Card; 2] { [Contessa, Duke] } fn choose_lost_influence(&self, cards: &[Card]) -> Card { Captain } } let deck = vec![Contessa, Contessa]; let discard = vec![]; let players = vec![ Player { coins: 2, cards: vec![Duke, Assassin] }, Player { coins: 1, cards: vec![Captain] }, ]; let game = Game { deck, discard, players, turn: 0, }; // Test income { let mut game = game.clone(); game.resolution(Resolution { action: Income, target: None, }, &[&DummyAgent, &DummyAgent]).unwrap(); assert_eq!(game.players[0].coins, 3); } // Test foreign aid { let mut game = game.clone(); game.resolution(Resolution { action: ForeignAid, target: None, }, &[&DummyAgent, &DummyAgent]).unwrap(); assert_eq!(game.players[0].coins, 4); } // Test coup / assassinate { let mut game = game.clone(); game.resolution(Resolution { action: Coup, target: Some(1), }, &[&DummyAgent, &DummyAgent]).unwrap(); assert!(game.players[1].cards.is_empty()); assert_eq!(game.discard, vec![Captain]); } // Test steal { let mut game = game.clone(); game.resolution(Resolution { action: Steal, target: Some(1), }, &[&DummyAgent, &DummyAgent]).unwrap(); assert_eq!(game.players[0].coins, 3); assert_eq!(game.players[1].coins, 0); } // Test exchange { let mut game = game.clone(); game.resolution(Resolution { action: Exchange, target: Some(1), }, &[&DummyAgent, &DummyAgent]).unwrap(); game.players[0].cards.sort(); assert_eq!(game.players[0].cards, vec![Assassin, Contessa]); game.deck.sort(); assert_eq!(game.deck, vec![Duke, Contessa]); assert!(game.discard.is_empty()); } } }