Use common structures for game phases

This commit is contained in:
Dane Johnson 2022-05-20 11:55:20 -05:00
parent 0e1381c15f
commit 1c8ffb151c
2 changed files with 132 additions and 151 deletions

View File

@ -258,22 +258,18 @@ impl Game {
}
}
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),
pub fn action_challenge(&mut self, move_: Move, agents: &[&dyn Agent]) -> CoupResult<Phase> {
match move_.action.challenger_mode() {
ResMode::None => Ok(Phase::Block(move_)),
ResMode::Target => unreachable!(),
ResMode::Anyone => {
let challenger = self.turn_iterator().find(|&id| agents[id].should_action_challenge(self, &action_challenge));
let challenger = self.turn_iterator().find(|&id| agents[id].should_action_challenge(self, move_));
if let Some(challenger) = challenger{
let current_player_wins = self.players[self.turn].wins_challenge(action_challenge.action);
let current_player_wins = self.players[self.turn].wins_challenge(move_.action);
if current_player_wins {
// Challenger loses influence
self.player_lose_influence(challenger, agents)?;
Ok(block)
Ok(Phase::Block(move_))
} else {
// Player loses influence
self.player_lose_influence(self.turn, agents)?;
@ -281,73 +277,57 @@ impl Game {
Ok(Phase::Done)
}
} else {
Ok(block)
Ok(Phase::Block(move_))
}
}
}
}
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(self, &block) {
pub fn block(&mut self, move_: Move, agents: &[&dyn Agent]) -> CoupResult<Phase> {
match move_.action.blocker_mode() {
ResMode::None => Ok(Phase::Resolution(move_)),
ResMode::Target => match agents[move_.target.unwrap()].choose_block_card(self, move_) {
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,
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(phase::Resolution {
action: block.action,
target: block.target,
})),
None => Ok(Phase::Resolution(move_)),
}
ResMode::Anyone => {
for id in self.turn_iterator() {
if let Some(card) = agents[id].choose_block_card(self, &block) {
if card.blocks_action(block.action) {
return Ok(Phase::BlockChallenge(phase::BlockChallenge {
if let Some(card) = agents[id].choose_block_card(self, move_) {
if card.blocks_action(move_.action) {
return Ok(Phase::BlockChallenge(move_, Block {
blocker: id,
block_card: card,
action: block.action,
target: block.target,
card,
}))
} else {
return Err("Card does not block action")
}
}
}
Ok(Phase::Resolution(phase::Resolution {
action: block.action,
target: block.target,
}))
Ok(Phase::Resolution(move_))
}
}
}
pub fn block_challenge(&mut self, block_challenge: phase::BlockChallenge, agents: &[&dyn Agent]) -> CoupResult<Phase> {
if agents[self.turn].should_block_challenge(self, &block_challenge) {
if self.players[block_challenge.blocker].holds(block_challenge.block_card) {
pub fn block_challenge(&mut self, move_: Move, block: Block, agents: &[&dyn Agent]) -> CoupResult<Phase> {
if agents[self.turn].should_block_challenge(self, move_, block) {
if self.players[block.blocker].holds(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)?;
self.player_lose_influence(block.blocker, agents)?;
// Game continues
Ok(Phase::Resolution(phase::Resolution {
action: block_challenge.action,
target: block_challenge.target,
}))
Ok(Phase::Resolution(move_))
}
} else {
// Player chose not to challenge the block, turn is forfeit
@ -356,11 +336,11 @@ impl Game {
}
}
pub fn resolution(&mut self, resolution: phase::Resolution, agents: &[&dyn Agent]) -> CoupResult<Phase> {
match resolution.action {
pub fn resolution(&mut self, move_: Move, agents: &[&dyn Agent]) -> CoupResult<Phase> {
match move_.action {
Income => self.players[self.turn].coins += 1,
ForeignAid => self.players[self.turn].coins += 2,
Coup | Assassinate => match resolution.target {
Coup | Assassinate => match move_.target {
Some(target) => {
// Target may have died from challenge
let target_alive = self.players[target].is_alive();
@ -386,7 +366,7 @@ impl Game {
self.players[self.turn].cards = choices;
self.deck.shuffle();
}
Steal => match resolution.target {
Steal => match move_.target {
Some(target) => {
let val = self.players[target].coins.min(2);
self.players[self.turn].coins += val;
@ -401,61 +381,41 @@ impl Game {
}
}
pub mod phase {
//! 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.
use super::{ Card, Action };
#[derive(PartialEq, Debug)]
pub enum Phase {
Action(Action),
ActionChallenge(ActionChallenge),
Block(Block),
BlockChallenge(BlockChallenge),
Resolution(Resolution),
Done,
}
#[derive(PartialEq, Debug)]
pub struct ActionChallenge {
pub action: Action,
pub target: Option<usize>,
}
#[derive(PartialEq, Debug)]
pub struct Block {
pub action: Action,
pub target: Option<usize>,
}
#[derive(PartialEq, Debug)]
pub struct BlockChallenge {
pub blocker: usize,
pub block_card: Card,
pub action: Action,
pub target: Option<usize>,
}
#[derive(PartialEq, Debug)]
pub struct Resolution {
pub action: Action,
pub target: Option<usize>,
}
#[derive(PartialEq, Debug, Clone, Copy)]
pub struct Move {
pub action: Action,
pub target: Option<usize>,
}
use phase::Phase;
#[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 {
Action(Move),
ActionChallenge(Move),
Block(Move),
BlockChallenge(Move, Block),
Resolution(Move),
Done,
}
/// An interface to a game to make strategic decisions.
pub trait Agent : fmt::Debug {
/// Should the agent challenge the action?
fn should_action_challenge(&self, game: &Game, action_challenge: &phase::ActionChallenge) -> bool;
fn should_action_challenge(&self, game: &Game, move_: Move) -> bool;
/// 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]
fn choose_block_card(&self, game: &Game, block: &phase::Block) -> Option<Card>;
fn choose_block_card(&self, game: &Game, move_: Move) -> Option<Card>;
/// Should the agent challenge the block?
fn should_block_challenge(&self, game: &Game, block_challenge: &phase::BlockChallenge) -> bool;
fn should_block_challenge(&self, game: &Game, move_: Move, block: Block) -> bool;
/// The [Ambassador]'s exchange.
/// Given 3 or 4 [Cards](Card) the agent must return two cards to the deck.
fn exchange(&self, game: &Game, cards: &[Card]) -> [Card; 2];

View File

@ -3,13 +3,13 @@ use super::*;
#[derive(Debug)]
struct DummyAgent(Card, Option<Card>, bool);
impl Agent for DummyAgent {
fn should_action_challenge(&self, _game: &Game, _action_challenge: &phase::ActionChallenge) -> bool {
fn should_action_challenge(&self, _game: &Game, _move: Move) -> bool {
self.2
}
fn choose_block_card(&self, _game: &Game, _block: &phase::Block) -> Option<Card> {
fn choose_block_card(&self, _game: &Game, _move: Move) -> Option<Card> {
self.1
}
fn should_block_challenge(&self, _game: &Game, _block_challenge: &phase::BlockChallenge) -> bool {
fn should_block_challenge(&self, _game: &Game, _move: Move, _block: Block) -> bool {
self.2
}
fn exchange(&self, _game: &Game, _cards: &[Card]) -> [Card; 2] {
@ -42,11 +42,11 @@ fn test_action_challenge() {
{
let mut game = game.clone();
assert_eq!(
game.action_challenge(phase::ActionChallenge {
game.action_challenge(Move {
action: Income,
target: None,
}, &[&challenge_agent, &challenge_agent, &challenge_agent]),
Ok(Phase::Block(phase::Block {
Ok(Phase::Block(Move {
action: Income,
target: None
}))
@ -57,11 +57,11 @@ fn test_action_challenge() {
{
let mut game = game.clone();
assert_eq!(
game.action_challenge(phase::ActionChallenge {
game.action_challenge(Move {
action: Steal,
target: Some(2),
}, &[&challenge_agent, &challenge_agent, &challenge_agent]),
Ok(Phase::Block(phase::Block {
Ok(Phase::Block(Move {
action: Steal,
target: Some(2)
}))
@ -72,7 +72,7 @@ fn test_action_challenge() {
{
let mut game = game.clone();
assert_eq!(
game.action_challenge(phase::ActionChallenge {
game.action_challenge(Move {
action: Assassinate,
target: Some(2),
}, &[&non_challenge_agent, &challenge_agent, &challenge_agent]),
@ -106,11 +106,11 @@ fn test_block() {
{
let mut game = game.clone();
assert_eq!(
game.block(phase::Block {
game.block(Move {
action: Income,
target: None,
}, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]),
Ok(Phase::Resolution(phase::Resolution {
Ok(Phase::Resolution(Move {
action: Income,
target: None
}))
@ -121,16 +121,20 @@ fn test_block() {
{
let mut game = game.clone();
assert_eq!(
game.block(phase::Block {
game.block(Move {
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),
}))
Ok(Phase::BlockChallenge(
Move {
action: Steal,
target: Some(2),
},
Block {
blocker: 2,
card: Ambassador,
},
)),
);
}
@ -138,11 +142,11 @@ fn test_block() {
{
let mut game = game.clone();
assert_eq!(
game.block(phase::Block {
game.block(Move {
action: Steal,
target: Some(2),
}, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent]),
Ok(Phase::Resolution(phase::Resolution {
Ok(Phase::Resolution(Move {
action: Steal,
target: Some(2),
}))
@ -153,16 +157,20 @@ fn test_block() {
{
let mut game = game.clone();
assert_eq!(
game.block(phase::Block {
game.block(Move {
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,
}))
Ok(Phase::BlockChallenge(
Move {
action: ForeignAid,
target: None,
},
Block {
blocker: 2,
card: Duke,
},
)),
);
}
@ -170,11 +178,11 @@ fn test_block() {
{
let mut game = game.clone();
assert_eq!(
game.block(phase::Block {
game.block(Move {
action: ForeignAid,
target: None,
}, &[&non_blocking_agent, &non_blocking_agent, &non_blocking_agent]),
Ok(Phase::Resolution(phase::Resolution {
Ok(Phase::Resolution(Move {
action: ForeignAid,
target: None,
}))
@ -185,14 +193,13 @@ fn test_block() {
{
let mut game = game.clone();
assert!(
game.block(phase::Block {
game.block(Move {
action: ForeignAid,
target: None,
}, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent])
.is_err()
);
}
}
#[test]
@ -217,12 +224,17 @@ fn test_block_challenge() {
{
let mut game = game.clone();
assert_eq!(
game.block_challenge( phase::BlockChallenge {
blocker: 1,
block_card: Contessa,
action: Assassinate,
target: Some(1),
}, &[&non_challenge_agent, &block_agent]),
game.block_challenge(
Move {
action: Assassinate,
target: Some(1),
},
Block {
blocker: 1,
card: Contessa,
},
&[&non_challenge_agent, &block_agent]
),
Ok(Phase::Done)
);
assert!(game.discard.is_empty());
@ -232,12 +244,17 @@ fn test_block_challenge() {
{
let mut game = game.clone();
assert_eq!(
game.block_challenge( phase::BlockChallenge {
blocker: 1,
block_card: Contessa,
action: Assassinate,
target: Some(1),
}, &[&challenge_agent, &block_agent]),
game.block_challenge(
Move {
action: Assassinate,
target: Some(1),
},
Block {
blocker: 1,
card: Contessa,
},
&[&challenge_agent, &block_agent]
),
Ok(Phase::Done)
);
assert!(!game.discard.is_empty());
@ -247,20 +264,24 @@ fn test_block_challenge() {
{
let mut game = game.clone();
assert_eq!(
game.block_challenge( phase::BlockChallenge {
blocker: 1,
block_card: Duke,
action: ForeignAid,
target: None,
}, &[&challenge_agent, &block_agent]),
Ok(Phase::Resolution(phase::Resolution {
game.block_challenge(
Move {
action: ForeignAid,
target: None,
},
Block {
blocker: 1,
card: Duke,
},
&[&challenge_agent, &block_agent]
),
Ok(Phase::Resolution(Move {
action: ForeignAid,
target: None,
}))
);
assert!(!game.discard.is_empty());
}
}
#[test]
@ -283,7 +304,7 @@ fn test_resolution() {
// Test income
{
let mut game = game.clone();
game.resolution(phase::Resolution {
game.resolution(Move {
action: Income,
target: None,
}, &[&dummy_agent, &dummy_agent]).unwrap();
@ -293,7 +314,7 @@ fn test_resolution() {
// Test foreign aid
{
let mut game = game.clone();
game.resolution(phase::Resolution {
game.resolution(Move {
action: ForeignAid,
target: None,
}, &[&dummy_agent, &dummy_agent]).unwrap();
@ -303,7 +324,7 @@ fn test_resolution() {
// Test coup / assassinate
{
let mut game = game.clone();
game.resolution(phase::Resolution {
game.resolution(Move {
action: Coup,
target: Some(1),
}, &[&dummy_agent, &loser_agent]).unwrap();
@ -314,7 +335,7 @@ fn test_resolution() {
// Test steal
{
let mut game = game.clone();
game.resolution(phase::Resolution {
game.resolution(Move {
action: Steal,
target: Some(1),
}, &[&dummy_agent, &dummy_agent]).unwrap();
@ -325,7 +346,7 @@ fn test_resolution() {
// Test exchange
{
let mut game = game.clone();
game.resolution(phase::Resolution {
game.resolution(Move {
action: Exchange,
target: Some(1),
}, &[&dummy_agent, &dummy_agent]).unwrap();