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, ForeignAid) | (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, PartialEq, Debug)] 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); } pub fn holds(&self, card: Card) -> bool { self.cards.iter().find(|&c| *c == card).is_some() } } 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 advance(&mut self) { let next = self.turn_iterator().next(); if let Some(next) = next { self.turn = next; } } fn player_lose_influence(&mut self, id: usize, agents: &[&dyn Agent]) { let player = &mut self.players[id]; let card = agents[id] .choose_lost_influence(&player.cards); player.lose(card, &mut self.discard); } pub fn block(&mut self, block: phase::Block, agents: &[&dyn Agent]) -> CoupResult { match block.action.blocker_mode() { ResMode::None => Ok(Phase::Resolution(phase::Resolution { action: block.action, target: block.target, })), ResMode::Target => match agents[block.target.unwrap()].choose_block_card(&block) { Some(card) => { if card.blocks_action(block.action) { Ok(Phase::BlockChallenge(phase::BlockChallenge { blocker: block.target.unwrap(), block_card: card, action: block.action, target: block.target, })) } else { Err("Card does not block action") } }, None => Ok(Phase::Resolution(phase::Resolution { action: block.action, target: block.target, })), } ResMode::Anyone => { for id in self.turn_iterator() { if let Some(card) = agents[id].choose_block_card(&block) { if card.blocks_action(block.action) { return Ok(Phase::BlockChallenge(phase::BlockChallenge { blocker: id, block_card: card, action: block.action, target: block.target, })) } else { return Err("Card does not block action") } } } Ok(Phase::Resolution(phase::Resolution { action: block.action, target: block.target, })) } } } pub fn block_challenge(&mut self, block_challenge: phase::BlockChallenge, agents: &[&dyn Agent]) -> CoupResult { if agents[self.turn].should_block_challenge(&block_challenge) { if self.players[block_challenge.blocker].holds(block_challenge.block_card) { // Player challenged incorrectly, loses influence and turn is forfeit self.player_lose_influence(self.turn, agents); Ok(Phase::Done) } else { // Player challenged correctly, blocker loses a card self.player_lose_influence(block_challenge.blocker, agents); // Game continues Ok(Phase::Resolution(phase::Resolution { action: block_challenge.action, target: block_challenge.target, })) } } else { // Player chose not to challenge the block, turn is forfeit self.advance(); Ok(Phase::Done) } } pub fn resolution(&mut self, resolution: phase::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) => { // Target may have died from challenge let target_alive = self.players[target].is_alive(); if target_alive { self.player_lose_influence(target, agents); } } _ => 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"), } } self.advance(); Ok(Phase::Done) } } pub mod phase { use super::{ Card, Action }; #[derive(PartialEq, Debug)] pub enum Phase { Action(Action), //ActionChallenge(ActionChallenge), Block(Block), BlockChallenge(BlockChallenge), Resolution(Resolution), Done, } #[derive(PartialEq, Debug)] pub struct Block { pub action: Action, pub target: Option, } #[derive(PartialEq, Debug)] pub struct BlockChallenge { pub blocker: usize, pub block_card: Card, pub action: Action, pub target: Option, } #[derive(PartialEq, Debug)] pub struct Resolution { pub action: Action, pub target: Option, } } use phase::Phase; pub trait Agent : fmt::Debug { fn choose_block_card(&self, block: &phase::Block) -> Option; fn should_block_challenge(&self, block_challenge: &phase::BlockChallenge) -> bool; fn exchange(&self, cards: &[Card]) -> [Card; 2]; fn choose_lost_influence(&self, cards: &[Card]) -> Card; } #[cfg(test)] mod test { use super::*; macro_rules! make_dummy_agent { ($cbc: expr, $sbc: expr, $e: expr, $cli: expr) => {{ #[derive(Debug)] struct DummyAgent; impl Agent for DummyAgent { #[allow(unused)] fn choose_block_card(&self, block: &phase::Block) -> Option { $cbc } #[allow(unused)] fn should_block_challenge(&self, block_challenge: &phase::BlockChallenge) -> bool { $sbc } #[allow(unused)] fn exchange(&self, cards: &[Card]) -> [Card; 2] { $e } #[allow(unused)] fn choose_lost_influence(&self, cards: &[Card]) -> Card { $cli } } DummyAgent }} } #[test] fn test_block() { let non_blocking_agent = make_dummy_agent!(None, unimplemented!(), unimplemented!(), unimplemented!()); let ambassador_block_agent = make_dummy_agent!(Some(Ambassador), unimplemented!(), unimplemented!(), unimplemented!()); let duke_block_agent = make_dummy_agent!(Some(Duke), unimplemented!(), unimplemented!(), unimplemented!()); let deck = vec![]; let discard = vec![]; let players = vec![ Player { coins: 2, cards: vec![Duke, Captain] }, Player { coins: 2, cards: vec![Contessa] }, Player { coins: 2, cards: vec![Ambassador] }, ]; let game = Game { deck, discard, players, turn: 0 }; // Test unblockable { let mut game = game.clone(); assert_eq!( game.block(phase::Block { action: Income, target: None, }, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]), Ok(Phase::Resolution(phase::Resolution { action: Income, target: None })) ); } // Test target blocked { let mut game = game.clone(); assert_eq!( game.block(phase::Block { action: Steal, target: Some(2), }, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]), Ok(Phase::BlockChallenge(phase::BlockChallenge { blocker: 2, block_card: Ambassador, action: Steal, target: Some(2), })) ); } // Test target non-blocked { let mut game = game.clone(); assert_eq!( game.block(phase::Block { action: Steal, target: Some(2), }, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent]), Ok(Phase::Resolution(phase::Resolution { action: Steal, target: Some(2), })) ); } // Test anyone blocked { let mut game = game.clone(); assert_eq!( game.block(phase::Block { action: ForeignAid, target: None, }, &[&non_blocking_agent, &non_blocking_agent, &duke_block_agent]), Ok(Phase::BlockChallenge(phase::BlockChallenge { blocker: 2, block_card: Duke, action: ForeignAid, target: None, })) ); } // Test no one blocked { let mut game = game.clone(); assert_eq!( game.block(phase::Block { action: ForeignAid, target: None, }, &[&non_blocking_agent, &non_blocking_agent, &non_blocking_agent]), Ok(Phase::Resolution(phase::Resolution { action: ForeignAid, target: None, })) ); } // Test bad block card { let mut game = game.clone(); assert!( game.block(phase::Block { action: ForeignAid, target: None, }, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent]) .is_err() ); } } #[test] fn test_block_challenge() { let challenge_agent = make_dummy_agent!(unimplemented!(), true, unimplemented!(), Assassin); let non_challenge_agent = make_dummy_agent!(unimplemented!(), false, unimplemented!(), Assassin); let block_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), unimplemented!(), Contessa); let deck = vec![]; let discard = vec![]; let players = vec![ Player { coins: 2, cards: vec![Assassin] }, Player { coins: 2, cards: vec![Contessa] }, ]; let game = Game { deck, discard, players, turn: 0 }; // Test non-challenge { let mut game = game.clone(); assert_eq!( game.block_challenge( phase::BlockChallenge { blocker: 1, block_card: Contessa, action: Assassinate, target: Some(1), }, &[&non_challenge_agent, &block_agent]), Ok(Phase::Done) ); assert!(game.discard.is_empty()); } // Test failed challenge { let mut game = game.clone(); assert_eq!( game.block_challenge( phase::BlockChallenge { blocker: 1, block_card: Contessa, action: Assassinate, target: Some(1), }, &[&challenge_agent, &block_agent]), Ok(Phase::Done) ); assert!(!game.discard.is_empty()); } // Test successful challenge { let mut game = game.clone(); assert_eq!( game.block_challenge( phase::BlockChallenge { blocker: 1, block_card: Duke, action: ForeignAid, target: None, }, &[&challenge_agent, &block_agent]), Ok(Phase::Resolution(phase::Resolution { action: ForeignAid, target: None, })) ); assert!(!game.discard.is_empty()); } } #[test] fn test_resolution() { let dummy_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), [Contessa, Duke], 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(phase::Resolution { action: Income, target: None, }, &[&dummy_agent, &dummy_agent]).unwrap(); assert_eq!(game.players[0].coins, 3); } // Test foreign aid { let mut game = game.clone(); game.resolution(phase::Resolution { action: ForeignAid, target: None, }, &[&dummy_agent, &dummy_agent]).unwrap(); assert_eq!(game.players[0].coins, 4); } // Test coup / assassinate { let mut game = game.clone(); game.resolution(phase::Resolution { action: Coup, target: Some(1), }, &[&dummy_agent, &dummy_agent]).unwrap(); assert!(game.players[1].cards.is_empty()); assert_eq!(game.discard, vec![Captain]); } // Test steal { let mut game = game.clone(); game.resolution(phase::Resolution { action: Steal, target: Some(1), }, &[&dummy_agent, &dummy_agent]).unwrap(); assert_eq!(game.players[0].coins, 3); assert_eq!(game.players[1].coins, 0); } // Test exchange { let mut game = game.clone(); game.resolution(phase::Resolution { action: Exchange, target: Some(1), }, &[&dummy_agent, &dummy_agent]).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()); } } }