Add block challenge resolution phase
This commit is contained in:
parent
0dfad4ad30
commit
4101157e5f
183
src/lib.rs
183
src/lib.rs
@ -84,7 +84,7 @@ impl fmt::Display for Card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Income,
|
Income,
|
||||||
ForeignAid,
|
ForeignAid,
|
||||||
@ -161,6 +161,9 @@ impl Player {
|
|||||||
self.cards.draw_first(card);
|
self.cards.draw_first(card);
|
||||||
deck.push(card);
|
deck.push(card);
|
||||||
}
|
}
|
||||||
|
pub fn holds(&self, card: Card) -> bool {
|
||||||
|
self.cards.iter().find(|&c| *c == card).is_some()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Player {
|
impl Default for Player {
|
||||||
@ -219,7 +222,43 @@ impl Game {
|
|||||||
players_turn_order.skip(1).filter(|p| self.players[*p].is_alive())
|
players_turn_order.skip(1).filter(|p| self.players[*p].is_alive())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolution(&mut self, resolution: Resolution, agents: &[&dyn Agent]) -> CoupResult<Phase> {
|
fn advance(&mut self) {
|
||||||
|
let next = self.turn_iterator().next();
|
||||||
|
if let Some(next) = next {
|
||||||
|
self.turn = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn block_challenge(&mut self, block_challenge: 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
|
||||||
|
let current_player = &mut self.players[self.turn];
|
||||||
|
let card = agents[self.turn]
|
||||||
|
.choose_lost_influence(¤t_player.cards);
|
||||||
|
current_player.lose(card, &mut self.discard);
|
||||||
|
Ok(Phase::Done)
|
||||||
|
} else {
|
||||||
|
// Player challenged correctly, blocker loses a card
|
||||||
|
let blocking_player = &mut self.players[block_challenge.blocker];
|
||||||
|
let card = agents[block_challenge.blocker]
|
||||||
|
.choose_lost_influence(&blocking_player.cards);
|
||||||
|
blocking_player.lose(card, &mut self.discard);
|
||||||
|
|
||||||
|
// Game continues
|
||||||
|
Ok(Phase::Resolution(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: Resolution, agents: &[&dyn Agent]) -> CoupResult<Phase> {
|
||||||
let current_player = &mut self.players.get_mut(self.turn).unwrap();
|
let current_player = &mut self.players.get_mut(self.turn).unwrap();
|
||||||
let current_agent = agents[self.turn];
|
let current_agent = agents[self.turn];
|
||||||
match resolution.action {
|
match resolution.action {
|
||||||
@ -228,8 +267,10 @@ impl Game {
|
|||||||
Coup | Assassinate => match resolution.target {
|
Coup | Assassinate => match resolution.target {
|
||||||
Some(target) => {
|
Some(target) => {
|
||||||
let target_player = &self.players[target];
|
let target_player = &self.players[target];
|
||||||
let card = agents[target].choose_lost_influence(&target_player.cards);
|
if target_player.is_alive() { // Target may have died from challenge
|
||||||
self.players[target].lose(card, &mut self.discard);
|
let card = agents[target].choose_lost_influence(&target_player.cards);
|
||||||
|
self.players[target].lose(card, &mut self.discard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => return Err("Coup/Assassinate resolution has no target"),
|
_ => return Err("Coup/Assassinate resolution has no target"),
|
||||||
},
|
},
|
||||||
@ -259,29 +300,37 @@ impl Game {
|
|||||||
_ => return Err("Steal resolution has no target"),
|
_ => return Err("Steal resolution has no target"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let next = self.turn_iterator().next();
|
self.advance();
|
||||||
if let Some(next) = next {
|
|
||||||
self.turn = next;
|
|
||||||
}
|
|
||||||
Ok(Phase::Done)
|
Ok(Phase::Done)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Phase {
|
#[derive(PartialEq, Debug)]
|
||||||
|
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 BlockChallenge {
|
||||||
|
blocker: usize,
|
||||||
|
block_card: Card,
|
||||||
|
action: Action,
|
||||||
|
target: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct Resolution {
|
pub struct Resolution {
|
||||||
action: Action,
|
action: Action,
|
||||||
target: Option<usize>,
|
target: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Agent : fmt::Debug {
|
pub trait Agent : fmt::Debug {
|
||||||
|
fn should_block_challenge(&self, block_challenge: &BlockChallenge) -> bool;
|
||||||
fn exchange(&self, cards: &[Card]) -> [Card; 2];
|
fn exchange(&self, cards: &[Card]) -> [Card; 2];
|
||||||
fn choose_lost_influence(&self, cards: &[Card]) -> Card;
|
fn choose_lost_influence(&self, cards: &[Card]) -> Card;
|
||||||
}
|
}
|
||||||
@ -290,18 +339,102 @@ pub trait Agent : fmt::Debug {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! make_dummy_agent {
|
||||||
|
($sbc: expr, $e: expr, $cli: expr) => {{
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DummyAgent;
|
||||||
|
impl Agent for DummyAgent {
|
||||||
|
#[allow(unused)]
|
||||||
|
fn should_block_challenge(&self, block_challenge: &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
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_block_challenge() {
|
||||||
|
let challenge_agent = make_dummy_agent!(true, unimplemented!(), Assassin);
|
||||||
|
let non_challenge_agent = make_dummy_agent!(false, unimplemented!(), Assassin);
|
||||||
|
let block_agent = make_dummy_agent!(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!(
|
||||||
|
game.block_challenge( 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( 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( BlockChallenge {
|
||||||
|
blocker: 1,
|
||||||
|
block_card: Duke,
|
||||||
|
action: ForeignAid,
|
||||||
|
target: None,
|
||||||
|
}, &[&challenge_agent, &block_agent]),
|
||||||
|
Ok(Phase::Resolution(Resolution {
|
||||||
|
action: ForeignAid,
|
||||||
|
target: None,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
assert!(!game.discard.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolution() {
|
fn test_resolution() {
|
||||||
#[derive(Debug)]
|
let dummy_agent = make_dummy_agent!(unimplemented!(), [Contessa, Duke], Captain);
|
||||||
struct DummyAgent;
|
|
||||||
impl Agent for DummyAgent {
|
|
||||||
fn exchange(&self, cards: &[Card]) -> [Card; 2] {
|
|
||||||
[Contessa, Duke]
|
|
||||||
}
|
|
||||||
fn choose_lost_influence(&self, cards: &[Card]) -> Card {
|
|
||||||
Captain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let deck = vec![Contessa, Contessa];
|
let deck = vec![Contessa, Contessa];
|
||||||
let discard = vec![];
|
let discard = vec![];
|
||||||
let players = vec![
|
let players = vec![
|
||||||
@ -321,7 +454,7 @@ mod test {
|
|||||||
game.resolution(Resolution {
|
game.resolution(Resolution {
|
||||||
action: Income,
|
action: Income,
|
||||||
target: None,
|
target: None,
|
||||||
}, &[&DummyAgent, &DummyAgent]).unwrap();
|
}, &[&dummy_agent, &dummy_agent]).unwrap();
|
||||||
assert_eq!(game.players[0].coins, 3);
|
assert_eq!(game.players[0].coins, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,7 +464,7 @@ mod test {
|
|||||||
game.resolution(Resolution {
|
game.resolution(Resolution {
|
||||||
action: ForeignAid,
|
action: ForeignAid,
|
||||||
target: None,
|
target: None,
|
||||||
}, &[&DummyAgent, &DummyAgent]).unwrap();
|
}, &[&dummy_agent, &dummy_agent]).unwrap();
|
||||||
assert_eq!(game.players[0].coins, 4);
|
assert_eq!(game.players[0].coins, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,7 +474,7 @@ mod test {
|
|||||||
game.resolution(Resolution {
|
game.resolution(Resolution {
|
||||||
action: Coup,
|
action: Coup,
|
||||||
target: Some(1),
|
target: Some(1),
|
||||||
}, &[&DummyAgent, &DummyAgent]).unwrap();
|
}, &[&dummy_agent, &dummy_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]);
|
||||||
}
|
}
|
||||||
@ -352,7 +485,7 @@ mod test {
|
|||||||
game.resolution(Resolution {
|
game.resolution(Resolution {
|
||||||
action: Steal,
|
action: Steal,
|
||||||
target: Some(1),
|
target: Some(1),
|
||||||
}, &[&DummyAgent, &DummyAgent]).unwrap();
|
}, &[&dummy_agent, &dummy_agent]).unwrap();
|
||||||
assert_eq!(game.players[0].coins, 3);
|
assert_eq!(game.players[0].coins, 3);
|
||||||
assert_eq!(game.players[1].coins, 0);
|
assert_eq!(game.players[1].coins, 0);
|
||||||
}
|
}
|
||||||
@ -363,7 +496,7 @@ mod test {
|
|||||||
game.resolution(Resolution {
|
game.resolution(Resolution {
|
||||||
action: Exchange,
|
action: Exchange,
|
||||||
target: Some(1),
|
target: Some(1),
|
||||||
}, &[&DummyAgent, &DummyAgent]).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![Assassin, Contessa]);
|
||||||
game.deck.sort();
|
game.deck.sort();
|
||||||
|
Loading…
Reference in New Issue
Block a user