//! Coup is a game of deception for two to six players. 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)] /// Each card represents the right to perform specific actions or counteractions. 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)] /// The actions a player can perform on their turn. pub enum Action { Income, ForeignAid, Coup, Tax, Assassinate, Exchange, Steal, } impl Action { /// If the action needs a target. pub fn is_targeted(self) -> bool { matches!(self, Coup | Steal | Assassinate) } /// Which players may challenge the action. pub fn challenger_mode(self) -> ResMode { match self { Income | ForeignAid | Coup => ResMode::None, Assassinate | Steal | Tax | Exchange => ResMode::Anyone, } } /// Which players may block the action. pub fn blocker_mode(self) -> ResMode { match self { Income | Tax | Exchange | Coup => ResMode::None, Assassinate | Steal => ResMode::Target, ForeignAid => ResMode::Anyone, } } /// How much the action costs to perform. 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)] /// How the other players may respond to an action. pub enum ResMode { None, Target, Anyone, } #[derive(Clone)] /// The cards and coins a single player possesses. pub struct Player { pub coins: u8, pub cards: Vec, } impl Player { /// If the player still possesses any cards, and thus is still in the game pub fn is_alive(&self) -> bool { !self.cards.is_empty() } fn wins_challenge(&self, action: Action) -> bool { self.cards.iter().find(|&c| c.allows_action(action)).is_some() } fn lose(&mut self, card: Card, deck: &mut Vec) { self.cards.draw_first(card); deck.push(card); } 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 { /// Creates a new game, with default [Players](Player) and a shuffled deck of [Cards](card) 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, } } /// If all but one [Players](Player) have lost all influence. 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]) -> CoupResult<()>{ let player = &mut self.players[id]; let card = agents[id] .choose_lost_influence(&player.cards); if player.holds(card) { player.lose(card, &mut self.discard); Ok(()) } else { Err("Player discarded a card they don't hold") } } pub fn action_challenge(&mut self, action_challenge: phase::ActionChallenge, agents: &[&dyn Agent]) -> CoupResult { let block = Phase::Block(phase::Block { action: action_challenge.action, target: action_challenge.target, }); match action_challenge.action.challenger_mode() { ResMode::None => Ok(block), ResMode::Target => unreachable!(), ResMode::Anyone => { let challenger = self.turn_iterator().find(|&id| agents[id].should_action_challenge(&action_challenge)); if let Some(challenger) = challenger{ let current_player_wins = self.players[self.turn].wins_challenge(action_challenge.action); if current_player_wins { // Challenger loses influence self.player_lose_influence(challenger, agents)?; Ok(block) } else { // Player loses influence self.player_lose_influence(self.turn, agents)?; // Turn is forfeit Ok(Phase::Done) } } else { Ok(block) } } } } 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 { //! Structures relating to game phases. //! //! Coup turns have 5 phases, depending on what actions are taken each phase //! some phases might be skipped. These structures marshal information //! between the phases. 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 ActionChallenge { pub action: Action, pub target: Option, } #[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; /// An interface to a game to make strategic decisions. pub trait Agent : fmt::Debug { /// Should the agent challenge the action? fn should_action_challenge(&self, action_challenge: &phase::ActionChallenge) -> bool; /// Which [card](Card) the agent wishes to use to block the current action. /// If the agent does not wish to block, it should return [None] fn choose_block_card(&self, block: &phase::Block) -> Option; /// Should the agent challenge the block? fn should_block_challenge(&self, block_challenge: &phase::BlockChallenge) -> bool; /// The [Ambassador]'s exchange. /// Given 3 or 4 [Cards](Card) the agent must return two cards to the deck. fn exchange(&self, cards: &[Card]) -> [Card; 2]; /// The player has lost influence, and must choose a [Card] from their hand /// to discard. fn choose_lost_influence(&self, cards: &[Card]) -> Card; } #[cfg(test)] mod test { use super::*; #[derive(Debug)] struct DummyAgent(Card, Option, bool); impl Agent for DummyAgent { fn should_action_challenge(&self, _action_challenge: &phase::ActionChallenge) -> bool { self.2 } fn choose_block_card(&self, _block: &phase::Block) -> Option { self.1 } fn should_block_challenge(&self, _block_challenge: &phase::BlockChallenge) -> bool { self.2 } fn exchange(&self, _cards: &[Card]) -> [Card; 2] { [self.0, self.1.unwrap()] } fn choose_lost_influence(&self, _cards: &[Card]) -> Card { self.0 } } #[test] fn test_action_challenge() { let challenge_agent = DummyAgent(Contessa, None, true); let non_challenge_agent = DummyAgent(Duke, None, false); 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 non-challengable { let mut game = game.clone(); assert_eq!( game.action_challenge(phase::ActionChallenge { action: Income, target: None, }, &[&challenge_agent, &challenge_agent, &challenge_agent]), Ok(Phase::Block(phase::Block { action: Income, target: None })) ); } // Test failed challenge { let mut game = game.clone(); assert_eq!( game.action_challenge(phase::ActionChallenge { action: Steal, target: Some(2), }, &[&challenge_agent, &challenge_agent, &challenge_agent]), Ok(Phase::Block(phase::Block { action: Steal, target: Some(2) })) ); } // Test successful challenge { let mut game = game.clone(); assert_eq!( game.action_challenge(phase::ActionChallenge { action: Assassinate, target: Some(2), }, &[&non_challenge_agent, &challenge_agent, &challenge_agent]), Ok(Phase::Done), ); } } #[test] fn test_block() { let non_blocking_agent = DummyAgent(Duke, None, false); let ambassador_block_agent = DummyAgent(Ambassador, Some(Ambassador), false); let duke_block_agent = DummyAgent(Duke, Some(Duke), false); 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 = DummyAgent(Assassin, None, true); let non_challenge_agent = DummyAgent(Assassin, None, false); let block_agent = DummyAgent(Contessa, None, false); 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 = DummyAgent(Assassin, Some(Duke), false); let loser_agent = DummyAgent(Captain, Some(Duke), false); 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, &loser_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![Contessa, Contessa]); game.deck.sort(); assert_eq!(game.deck, vec![Duke, Assassin]); assert!(game.discard.is_empty()); } } }