coup/src/lib.rs

706 lines
22 KiB
Rust
Raw Normal View History

2022-05-18 14:04:39 -05:00
//! 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)]
2022-05-18 14:04:39 -05:00
/// 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)]
2022-05-18 14:04:39 -05:00
/// The actions a player can perform on their turn.
pub enum Action {
Income,
ForeignAid,
Coup,
Tax,
Assassinate,
Exchange,
Steal,
}
impl Action {
2022-05-18 14:04:39 -05:00
/// If the action needs a target.
pub fn is_targeted(self) -> bool {
matches!(self, Coup | Steal | Assassinate)
}
2022-05-18 14:04:39 -05:00
/// 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,
}
}
2022-05-18 14:04:39 -05:00
/// 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,
}
}
2022-05-18 14:04:39 -05:00
/// 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)]
2022-05-18 14:04:39 -05:00
/// How the other players may respond to an action.
pub enum ResMode {
None,
Target,
Anyone,
}
#[derive(Clone)]
2022-05-18 14:04:39 -05:00
/// The cards and coins a single player possesses.
pub struct Player {
pub coins: u8,
pub cards: Vec<Card>,
}
impl Player {
2022-05-18 14:04:39 -05:00
/// If the player still possesses any cards, and thus is still in the game
pub fn is_alive(&self) -> bool {
!self.cards.is_empty()
}
2022-05-18 14:04:39 -05:00
fn lose(&mut self, card: Card, deck: &mut Vec<Card>) {
self.cards.draw_first(card);
deck.push(card);
}
2022-05-18 14:04:39 -05:00
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<Player>,
pub deck: Vec<Card>,
pub discard: Vec<Card>,
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<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, agents: &[&dyn Agent]) {
let player = &mut self.players[id];
let card = agents[id]
.choose_lost_influence(&player.cards);
player.lose(card, &mut self.discard);
}
2022-05-18 12:19:12 -05:00
pub fn block(&mut self, block: phase::Block, agents: &[&dyn Agent]) -> CoupResult<Phase> {
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")
}
},
2022-05-18 12:19:12 -05:00
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")
}
2022-05-18 12:19:12 -05:00
}
}
Ok(Phase::Resolution(phase::Resolution {
action: block.action,
target: block.target,
}))
}
}
}
2022-05-18 11:25:39 -05:00
pub fn block_challenge(&mut self, block_challenge: phase::BlockChallenge, agents: &[&dyn Agent]) -> CoupResult<Phase> {
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
2022-05-18 11:25:39 -05:00
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)
}
}
2022-05-18 11:25:39 -05:00
pub fn resolution(&mut self, resolution: phase::Resolution, agents: &[&dyn Agent]) -> CoupResult<Phase> {
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)
}
}
2022-05-18 14:04:39 -05:00
2022-05-18 11:25:39 -05:00
pub mod phase {
2022-05-18 14:04:39 -05:00
//! 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.
2022-05-18 11:25:39 -05:00
use super::{ Card, Action };
#[derive(PartialEq, Debug)]
pub enum Phase {
Action(Action),
//ActionChallenge(ActionChallenge),
Block(Block),
BlockChallenge(BlockChallenge),
Resolution(Resolution),
Done,
}
2022-05-18 11:25:39 -05:00
#[derive(PartialEq, Debug)]
pub struct Block {
2022-05-18 12:19:12 -05:00
pub action: Action,
pub target: Option<usize>,
2022-05-18 11:25:39 -05:00
}
2022-05-18 11:25:39 -05:00
#[derive(PartialEq, Debug)]
pub struct BlockChallenge {
pub blocker: usize,
pub block_card: Card,
pub action: Action,
pub target: Option<usize>,
}
2022-05-18 11:25:39 -05:00
#[derive(PartialEq, Debug)]
pub struct Resolution {
pub action: Action,
pub target: Option<usize>,
}
}
2022-05-18 11:25:39 -05:00
use phase::Phase;
2022-05-18 14:04:39 -05:00
/// An interface to a game to make strategic decisions.
pub trait Agent : fmt::Debug {
2022-05-18 14:04:39 -05:00
/// 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]
2022-05-18 12:19:12 -05:00
fn choose_block_card(&self, block: &phase::Block) -> Option<Card>;
2022-05-18 14:04:39 -05:00
/// Should the agent challenge the block?
2022-05-18 11:25:39 -05:00
fn should_block_challenge(&self, block_challenge: &phase::BlockChallenge) -> bool;
2022-05-18 14:04:39 -05:00
/// 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];
2022-05-18 14:04:39 -05:00
/// 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::*;
macro_rules! make_dummy_agent {
2022-05-18 12:19:12 -05:00
($cbc: expr, $sbc: expr, $e: expr, $cli: expr) => {{
#[derive(Debug)]
struct DummyAgent;
impl Agent for DummyAgent {
2022-05-18 12:19:12 -05:00
#[allow(unused)]
fn choose_block_card(&self, block: &phase::Block) -> Option<Card> {
$cbc
}
#[allow(unused)]
2022-05-18 11:25:39 -05:00
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
}}
}
2022-05-18 12:19:12 -05:00
#[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()
);
}
2022-05-18 12:19:12 -05:00
}
#[test]
fn test_block_challenge() {
2022-05-18 12:19:12 -05:00
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!(
2022-05-18 11:25:39 -05:00
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!(
2022-05-18 11:25:39 -05:00
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!(
2022-05-18 11:25:39 -05:00
game.block_challenge( phase::BlockChallenge {
blocker: 1,
block_card: Duke,
action: ForeignAid,
target: None,
}, &[&challenge_agent, &block_agent]),
2022-05-18 11:25:39 -05:00
Ok(Phase::Resolution(phase::Resolution {
action: ForeignAid,
target: None,
}))
);
assert!(!game.discard.is_empty());
}
}
#[test]
fn test_resolution() {
2022-05-18 12:19:12 -05:00
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();
2022-05-18 11:25:39 -05:00
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();
2022-05-18 11:25:39 -05:00
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();
2022-05-18 11:25:39 -05:00
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();
2022-05-18 11:25:39 -05:00
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();
2022-05-18 11:25:39 -05:00
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());
}
}
}