//! 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().any(|c| c.allows_action(action)) } 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().any(|c| *c == 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 { /// 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, channels: &Channels) -> CoupResult<()> { channels.send(id, Message::LoseInfluence); let card = channels.recv_discard(id)?; let player = &self.players[id]; if player.holds(card) { self.players[id].lose(card, &mut self.discard); channels.broadcast(Message::Discard(card)); Ok(()) } else { Err("Player discarded a card they don't hold") } } pub fn action(&mut self, channels: &Channels) -> CoupResult { channels.broadcast(Message::Turn(self.turn)); let move_ = channels.recv_move(self.turn)?; if move_.action.is_targeted() && move_.target.is_none() { Err("Targeted action with no target") } else if move_.action.coin_cost() > self.players[self.turn].coins { Err("Cannot afford action") } else { channels.broadcast(Message::Move(move_)); Ok(Phase::ActionChallenge(move_)) } // TODO there're definately more cases, find and cover these } pub fn action_challenge(&mut self, move_: Move, channels: &Channels) -> CoupResult { match move_.action.challenger_mode() { ResMode::None => Ok(Phase::Block(move_)), ResMode::Target => unreachable!(), ResMode::Anyone => { let wait_timer = Timer::start(); let challenger = 'outer: loop { if wait_timer.is_timeout() { break None } for id in self.turn_iterator() { if let Some(()) = channels.try_recv_challenge(id) { break 'outer Some(id) } } }; if let Some(challenger) = challenger{ let current_player_wins = self.players[self.turn].wins_challenge(move_.action); if current_player_wins { // Challenger loses influence self.player_lose_influence(challenger, channels)?; Ok(Phase::Block(move_)) } else { // Player loses influence self.player_lose_influence(self.turn, channels)?; // Turn is forfeit self.advance(); Ok(Phase::Done) } } else { Ok(Phase::Block(move_)) } } } } pub fn block(&mut self, move_: Move, channels: &Channels) -> CoupResult { match move_.action.blocker_mode() { ResMode::None => Ok(Phase::Resolution(move_)), ResMode::Target => { let wait_timer = Timer::start(); let card = loop { if wait_timer.is_timeout() { break None } if let Some(card) = channels.try_recv_block(move_.target.unwrap()) { break Some(card) } }; match card { Some(card) => { if card.blocks_action(move_.action) { Ok(Phase::BlockChallenge(move_, Block { blocker: move_.target.unwrap(), card, })) } else { Err("Card does not block action") } }, None => Ok(Phase::Resolution(move_)), } } ResMode::Anyone => { let wait_timer = Timer::start(); let block = 'outer: loop { if wait_timer.is_timeout() { break None } for id in self.turn_iterator() { if let Some(card) = channels.try_recv_block(id) { break 'outer Some(Block { blocker: id, card, }) } } }; if let Some(block) = block { if block.card.blocks_action(move_.action) { Ok(Phase::BlockChallenge(move_, block)) } else { Err("Card does not block action") } } else { Ok(Phase::Resolution(move_)) } } } } pub fn block_challenge(&mut self, move_: Move, block: Block, channels: &Channels) -> CoupResult { let wait_timer = Timer::start(); let is_challenge = loop { if wait_timer.is_timeout() { break false } if let Some(()) = channels.try_recv_challenge(self.turn) { break true } }; if is_challenge { if self.players[block.blocker].holds(block.card) { // Player challenged incorrectly, loses influence and turn is forfeit self.player_lose_influence(self.turn, channels)?; self.advance(); Ok(Phase::Done) } else { // Player challenged correctly, blocker loses a card self.player_lose_influence(block.blocker, channels)?; // Game continues Ok(Phase::Resolution(move_)) } } else { // Player chose not to challenge the block, turn is forfeit self.advance(); Ok(Phase::Done) } } pub fn resolution(&mut self, move_: Move, channels: &Channels) -> CoupResult { self.players[self.turn].coins -= move_.action.coin_cost(); match move_.action { Income => self.players[self.turn].coins += 1, ForeignAid => self.players[self.turn].coins += 2, Coup | Assassinate => match move_.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, channels)?; } } _ => return Err("Coup/Assassinate resolution has no target"), }, Tax => self.players[self.turn].coins += 3, Exchange => { let drawn = vec![self.deck.pop().unwrap(), self.deck.pop().unwrap()]; let hand = self.players[self.turn].cards.clone(); let mut choices = [drawn, hand].concat(); let mut discarded = [Ambassador; 2]; for card in &mut discarded { *card = channels.recv_discard(self.turn)?; } for card in discarded { if !choices.draw_first(card) { return Err("Exchanged a card that was not in choices"); } else { self.deck.push(card); } } self.players[self.turn].cards = choices; self.deck.shuffle(); } Steal => match move_.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) } } #[derive(PartialEq, Debug, Clone, Copy)] pub struct Move { pub action: Action, pub target: Option, } #[derive(PartialEq, Debug, Clone, Copy)] pub struct Block { pub blocker: usize, pub card: Card, } /// Phase we should move to. /// /// Coup turns have 5 phases, depending on what actions are taken each phase /// some phases might be skipped. #[derive(PartialEq, Debug)] pub enum Phase { ActionChallenge(Move), Block(Move), BlockChallenge(Move, Block), Resolution(Move), Done, } use std::sync::mpsc; #[derive(Default)] pub struct Channels { txs: Vec>, rxs: Vec>, } impl Channels { pub fn add_channel(&mut self, tx: mpsc::Sender, rx: mpsc::Receiver) { self.txs.push(tx); self.rxs.push(rx); } pub fn send(&self, id: usize, msg: Message) { self.txs[id].send(msg).unwrap(); } pub fn recv(&self, id: usize) -> Message { self.rxs[id].recv().unwrap() } pub fn try_recv(&self, id: usize) -> Option { self.rxs[id].try_recv().ok() } pub fn broadcast(&self, msg: Message) { for id in 0..self.rxs.len() { self.send(id, msg); } } pub fn recv_move(&self, id: usize) -> CoupResult { let msg = self.recv(id); match msg { Message::Move(move_) => Ok(move_), _ => Err("Expected move"), } } pub fn recv_discard(&self, id: usize) -> CoupResult { let msg = self.recv(id); match msg { Message::Discard(card) => Ok(card), _ => Err("Expected discard") } } pub fn try_recv_challenge(&self, id: usize) -> Option<()> { let msg = self.try_recv(id); match msg { Some(Message::Challenge) => Some(()), _ => None, } } pub fn try_recv_block(&self, id: usize) -> Option { let msg = self.try_recv(id); match msg { Some(Message::Block(card)) => Some(card), _ => None, } } } #[derive(Clone, Copy, Debug, PartialEq)] pub enum Message { Turn(usize), LoseInfluence, Move(Move), Challenge, Block(Card), Discard(Card), } use std::time::{ Instant, Duration }; const WAIT_TIME: Duration = Duration::from_secs(1); struct Timer { start: Instant, } impl Timer { fn start() -> Self { Timer { start: Instant::now() } } fn is_timeout(&self) -> bool { Instant::now() > self.start + WAIT_TIME } } #[cfg(test)] mod test;