Add readme, action challenges, admit that I did it totally wrong
This commit is contained in:
parent
7801eb0dd1
commit
12d0b86e78
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Coup
|
||||||
|
|
||||||
|
## Coup from a Programmers Perspective
|
||||||
|
|
||||||
|
Coup is played in turns, each players turn consists of phases, many
|
||||||
|
of which are skipped depending on the cards.
|
||||||
|
|
||||||
|
1. First player chooses an action that they have the coin to perform, and possibly a target for that action (Action Phase)
|
||||||
|
|
||||||
|
2. If the action requires an identity, all players are given an option to challenge the action (Action Challenge Phase).
|
||||||
|
If there is as challenge and it is successful it ends the first players turn and they lose influence.
|
||||||
|
If there is an unsuccessful challenge the challenger loses influence, and the turn continues.
|
||||||
|
If there is no challenge play continues.
|
||||||
|
|
||||||
|
3. If the action is a blockable action and a targeted action then the target may or may not block.
|
||||||
|
If the action is blockable and there is no target, all players may block (Block Phase).
|
||||||
|
|
||||||
|
4. If there is a block, the first player may choose to challenge the block. (Block Challenge Phase)
|
||||||
|
If there is a challenge and it is successful, the blocker loses influence and the first player's turn resolves.
|
||||||
|
If there is a challenge that is unsuccessful, the first player loses influence and the turn ends.
|
||||||
|
If there is not challenge the turn ends.
|
||||||
|
|
||||||
|
5. The player's action is resolved (Resolution phase)
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
1. Remove coins as part of the resolution phase
|
||||||
|
2. Add checks so dead players don't lose influence
|
||||||
|
3. Assert that no illegal actions can be performed by the agents
|
||||||
|
4. Re-order phases such that challenges come after counter-actions and can be performed by any player
|
185
src/lib.rs
185
src/lib.rs
@ -168,10 +168,15 @@ impl Player {
|
|||||||
pub fn is_alive(&self) -> bool {
|
pub fn is_alive(&self) -> bool {
|
||||||
!self.cards.is_empty()
|
!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<Card>) {
|
fn lose(&mut self, card: Card, deck: &mut Vec<Card>) {
|
||||||
self.cards.draw_first(card);
|
self.cards.draw_first(card);
|
||||||
deck.push(card);
|
deck.push(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn holds(&self, card: Card) -> bool {
|
fn holds(&self, card: Card) -> bool {
|
||||||
self.cards.iter().find(|&c| *c == card).is_some()
|
self.cards.iter().find(|&c| *c == card).is_some()
|
||||||
}
|
}
|
||||||
@ -201,6 +206,7 @@ macro_rules! thrice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
|
/// Creates a new game, with default [Players](Player) and a shuffled deck of [Cards](card)
|
||||||
pub fn new(num_players: usize) -> Self {
|
pub fn new(num_players: usize) -> Self {
|
||||||
let mut deck = thrice![Duke, Assassin, Captain, Ambassador, Contessa];
|
let mut deck = thrice![Duke, Assassin, Captain, Ambassador, Contessa];
|
||||||
deck.shuffle();
|
deck.shuffle();
|
||||||
@ -217,6 +223,7 @@ impl Game {
|
|||||||
turn: 0,
|
turn: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// If all but one [Players](Player) have lost all influence.
|
||||||
pub fn is_game_over(&self) -> bool {
|
pub fn is_game_over(&self) -> bool {
|
||||||
self.players.iter().filter(|p| p.is_alive()).count() == 1
|
self.players.iter().filter(|p| p.is_alive()).count() == 1
|
||||||
}
|
}
|
||||||
@ -240,11 +247,45 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn player_lose_influence(&mut self, id: usize, agents: &[&dyn Agent]) {
|
fn player_lose_influence(&mut self, id: usize, agents: &[&dyn Agent]) -> CoupResult<()>{
|
||||||
let player = &mut self.players[id];
|
let player = &mut self.players[id];
|
||||||
let card = agents[id]
|
let card = agents[id]
|
||||||
.choose_lost_influence(&player.cards);
|
.choose_lost_influence(&player.cards);
|
||||||
player.lose(card, &mut self.discard);
|
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<Phase> {
|
||||||
|
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<Phase> {
|
pub fn block(&mut self, block: phase::Block, agents: &[&dyn Agent]) -> CoupResult<Phase> {
|
||||||
@ -298,11 +339,11 @@ impl Game {
|
|||||||
if agents[self.turn].should_block_challenge(&block_challenge) {
|
if agents[self.turn].should_block_challenge(&block_challenge) {
|
||||||
if self.players[block_challenge.blocker].holds(block_challenge.block_card) {
|
if self.players[block_challenge.blocker].holds(block_challenge.block_card) {
|
||||||
// Player challenged incorrectly, loses influence and turn is forfeit
|
// Player challenged incorrectly, loses influence and turn is forfeit
|
||||||
self.player_lose_influence(self.turn, agents);
|
self.player_lose_influence(self.turn, agents)?;
|
||||||
Ok(Phase::Done)
|
Ok(Phase::Done)
|
||||||
} else {
|
} else {
|
||||||
// Player challenged correctly, blocker loses a card
|
// Player challenged correctly, blocker loses a card
|
||||||
self.player_lose_influence(block_challenge.blocker, agents);
|
self.player_lose_influence(block_challenge.blocker, agents)?;
|
||||||
// Game continues
|
// Game continues
|
||||||
Ok(Phase::Resolution(phase::Resolution {
|
Ok(Phase::Resolution(phase::Resolution {
|
||||||
action: block_challenge.action,
|
action: block_challenge.action,
|
||||||
@ -327,7 +368,7 @@ impl Game {
|
|||||||
// Target may have died from challenge
|
// Target may have died from challenge
|
||||||
let target_alive = self.players[target].is_alive();
|
let target_alive = self.players[target].is_alive();
|
||||||
if target_alive {
|
if target_alive {
|
||||||
self.player_lose_influence(target, agents);
|
self.player_lose_influence(target, agents)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return Err("Coup/Assassinate resolution has no target"),
|
_ => return Err("Coup/Assassinate resolution has no target"),
|
||||||
@ -373,13 +414,19 @@ pub mod phase {
|
|||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum Phase {
|
pub enum Phase {
|
||||||
Action(Action),
|
Action(Action),
|
||||||
//ActionChallenge(ActionChallenge),
|
ActionChallenge(ActionChallenge),
|
||||||
Block(Block),
|
Block(Block),
|
||||||
BlockChallenge(BlockChallenge),
|
BlockChallenge(BlockChallenge),
|
||||||
Resolution(Resolution),
|
Resolution(Resolution),
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct ActionChallenge {
|
||||||
|
pub action: Action,
|
||||||
|
pub target: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
pub action: Action,
|
pub action: Action,
|
||||||
@ -405,6 +452,8 @@ use phase::Phase;
|
|||||||
|
|
||||||
/// An interface to a game to make strategic decisions.
|
/// An interface to a game to make strategic decisions.
|
||||||
pub trait Agent : fmt::Debug {
|
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.
|
/// 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]
|
/// If the agent does not wish to block, it should return [None]
|
||||||
fn choose_block_card(&self, block: &phase::Block) -> Option<Card>;
|
fn choose_block_card(&self, block: &phase::Block) -> Option<Card>;
|
||||||
@ -422,37 +471,92 @@ pub trait Agent : fmt::Debug {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
macro_rules! make_dummy_agent {
|
#[derive(Debug)]
|
||||||
($cbc: expr, $sbc: expr, $e: expr, $cli: expr) => {{
|
struct DummyAgent(Card, Option<Card>, bool);
|
||||||
#[derive(Debug)]
|
impl Agent for DummyAgent {
|
||||||
struct DummyAgent;
|
fn should_action_challenge(&self, _action_challenge: &phase::ActionChallenge) -> bool {
|
||||||
impl Agent for DummyAgent {
|
self.2
|
||||||
#[allow(unused)]
|
}
|
||||||
fn choose_block_card(&self, block: &phase::Block) -> Option<Card> {
|
fn choose_block_card(&self, _block: &phase::Block) -> Option<Card> {
|
||||||
$cbc
|
self.1
|
||||||
}
|
}
|
||||||
#[allow(unused)]
|
fn should_block_challenge(&self, _block_challenge: &phase::BlockChallenge) -> bool {
|
||||||
fn should_block_challenge(&self, block_challenge: &phase::BlockChallenge) -> bool {
|
self.2
|
||||||
$sbc
|
}
|
||||||
}
|
fn exchange(&self, _cards: &[Card]) -> [Card; 2] {
|
||||||
#[allow(unused)]
|
[self.0, self.1.unwrap()]
|
||||||
fn exchange(&self, cards: &[Card]) -> [Card; 2] {
|
}
|
||||||
$e
|
fn choose_lost_influence(&self, _cards: &[Card]) -> Card {
|
||||||
}
|
self.0
|
||||||
#[allow(unused)]
|
}
|
||||||
fn choose_lost_influence(&self, cards: &[Card]) -> Card {
|
}
|
||||||
$cli
|
|
||||||
}
|
#[test]
|
||||||
}
|
fn test_action_challenge() {
|
||||||
DummyAgent
|
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]
|
#[test]
|
||||||
fn test_block() {
|
fn test_block() {
|
||||||
let non_blocking_agent = make_dummy_agent!(None, unimplemented!(), unimplemented!(), unimplemented!());
|
let non_blocking_agent = DummyAgent(Duke, None, false);
|
||||||
let ambassador_block_agent = make_dummy_agent!(Some(Ambassador), unimplemented!(), unimplemented!(), unimplemented!());
|
let ambassador_block_agent = DummyAgent(Ambassador, Some(Ambassador), false);
|
||||||
let duke_block_agent = make_dummy_agent!(Some(Duke), unimplemented!(), unimplemented!(), unimplemented!());
|
let duke_block_agent = DummyAgent(Duke, Some(Duke), false);
|
||||||
|
|
||||||
let deck = vec![];
|
let deck = vec![];
|
||||||
let discard = vec![];
|
let discard = vec![];
|
||||||
@ -564,9 +668,9 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_block_challenge() {
|
fn test_block_challenge() {
|
||||||
let challenge_agent = make_dummy_agent!(unimplemented!(), true, unimplemented!(), Assassin);
|
let challenge_agent = DummyAgent(Assassin, None, true);
|
||||||
let non_challenge_agent = make_dummy_agent!(unimplemented!(), false, unimplemented!(), Assassin);
|
let non_challenge_agent = DummyAgent(Assassin, None, false);
|
||||||
let block_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), unimplemented!(), Contessa);
|
let block_agent = DummyAgent(Contessa, None, false);
|
||||||
let deck = vec![];
|
let deck = vec![];
|
||||||
let discard = vec![];
|
let discard = vec![];
|
||||||
let players = vec![
|
let players = vec![
|
||||||
@ -632,7 +736,8 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolution() {
|
fn test_resolution() {
|
||||||
let dummy_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), [Contessa, Duke], Captain);
|
let dummy_agent = DummyAgent(Assassin, Some(Duke), false);
|
||||||
|
let loser_agent = DummyAgent(Captain, Some(Duke), false);
|
||||||
let deck = vec![Contessa, Contessa];
|
let deck = vec![Contessa, Contessa];
|
||||||
let discard = vec![];
|
let discard = vec![];
|
||||||
let players = vec![
|
let players = vec![
|
||||||
@ -672,7 +777,7 @@ mod test {
|
|||||||
game.resolution(phase::Resolution {
|
game.resolution(phase::Resolution {
|
||||||
action: Coup,
|
action: Coup,
|
||||||
target: Some(1),
|
target: Some(1),
|
||||||
}, &[&dummy_agent, &dummy_agent]).unwrap();
|
}, &[&dummy_agent, &loser_agent]).unwrap();
|
||||||
assert!(game.players[1].cards.is_empty());
|
assert!(game.players[1].cards.is_empty());
|
||||||
assert_eq!(game.discard, vec![Captain]);
|
assert_eq!(game.discard, vec![Captain]);
|
||||||
}
|
}
|
||||||
@ -696,9 +801,9 @@ mod test {
|
|||||||
target: Some(1),
|
target: Some(1),
|
||||||
}, &[&dummy_agent, &dummy_agent]).unwrap();
|
}, &[&dummy_agent, &dummy_agent]).unwrap();
|
||||||
game.players[0].cards.sort();
|
game.players[0].cards.sort();
|
||||||
assert_eq!(game.players[0].cards, vec![Assassin, Contessa]);
|
assert_eq!(game.players[0].cards, vec![Contessa, Contessa]);
|
||||||
game.deck.sort();
|
game.deck.sort();
|
||||||
assert_eq!(game.deck, vec![Duke, Contessa]);
|
assert_eq!(game.deck, vec![Duke, Assassin]);
|
||||||
assert!(game.discard.is_empty());
|
assert!(game.discard.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user