coup/src/lib.rs

559 lines
16 KiB
Rust

//! 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<T> = Result<T, &'static str>;
#[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<Card> {
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<Card>,
}
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<Card>) {
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<Player>,
pub deck: Vec<Card>,
pub discard: Vec<Card>,
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<Item = usize> + '_ {
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<Phase> {
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<Phase> {
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<Phase> {
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<Phase> {
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<Phase> {
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<usize>,
}
#[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<mpsc::Sender<Message>>,
rxs: Vec<mpsc::Receiver<Message>>,
}
impl Channels {
pub fn add_channel(&mut self, tx: mpsc::Sender<Message>, rx: mpsc::Receiver<Message>) {
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<Message> {
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<Move> {
let msg = self.recv(id);
match msg {
Message::Move(move_) => Ok(move_),
_ => Err("Expected move"),
}
}
pub fn recv_discard(&self, id: usize) -> CoupResult<Card> {
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<Card> {
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;