use std::fmt;
use crate::Error;
use crate::rules::Roll;
use crate::rules::{Board, Move, Position};
use crate::rules::{BoardDisplay, Player};
use crate::stats::Stats;
#[derive(Debug, Clone, PartialEq)]
pub struct Game {
board: Board,
state: GameState,
available_moves: Vec<Vec<(Position, Position)>>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum GameState {
Rolling(Player),
Moving(Player),
GameEnd(GameOutcome),
}
#[derive(Debug, Clone, PartialEq)]
pub enum GameOutcome {
Regular(Player),
Gammon(Player),
Backgammon(Player),
}
impl fmt::Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = String::new();
s.push_str(&format!("Board: {:?}\n", self.get_board()));
s.push_str(&format!("State: {:?}\n", self.state));
write!(f, "{}", s)
}
}
impl Game {
pub fn new() -> Self {
Game::default()
}
pub fn get_state(&self) -> &GameState {
&self.state
}
pub fn set_player(&mut self, player: Player) -> Result<(), Error> {
match &self.state {
GameState::Rolling(_) => {
if self.get_player() != &Player::Nobody && player == Player::Nobody {
return Err(Error::GameStarted);
}
self.state = GameState::Rolling(player);
Ok(())
}
GameState::Moving(_) => {
if self.get_player() != &Player::Nobody && player == Player::Nobody {
return Err(Error::GameStarted);
}
self.state = GameState::Moving(player);
Ok(())
}
GameState::GameEnd(_) => Err(Error::GameEnded),
}
}
pub fn get_player(&self) -> &Player {
match &self.state {
GameState::Rolling(p) => p,
GameState::Moving(p) => p,
GameState::GameEnd(_) => &Player::Nobody,
}
}
}
impl Default for Game {
fn default() -> Self {
Game {
board: Board::default(),
state: GameState::Rolling(Player::Nobody),
available_moves: vec![],
}
}
}
fn dice_operation<D>(g: &mut Game, player: Player, operation: D) -> Result<(), Error>
where
D: FnOnce(&mut Game) -> Result<(), Error>,
{
match g.state {
GameState::Rolling(p) => {
if p != player {
return Err(Error::NotYourTurn);
}
}
GameState::Moving(p) => {
if p != player {
return Err(Error::NotYourTurn);
} else {
return Err(Error::MoveFirst);
}
}
GameState::GameEnd(_) => {
return Err(Error::GameEnded);
}
}
operation(g)?;
if g.get_player() == &Player::Nobody {
let (die1, die2) = g.get_dice();
g.state = match die1.cmp(&die2) {
std::cmp::Ordering::Equal => GameState::Rolling(Player::Nobody),
std::cmp::Ordering::Greater => GameState::Moving(Player::Player0),
std::cmp::Ordering::Less => GameState::Moving(Player::Player1),
};
} else {
g.state = GameState::Moving(player)
}
if let GameState::Moving(p) = g.state {
g.available_moves = g.board.available_moves(p)?.to_vec();
}
Ok(())
}
impl Roll for Game {
fn roll(&mut self, player: Player) -> Result<(), Error> {
dice_operation(self, player, |g| g.board.roll(player))
}
fn get_dice(&self) -> (u8, u8) {
self.board.get_dice()
}
fn set_dice(&mut self, player: Player, v: (u8, u8)) -> Result<(), Error> {
dice_operation(self, player, |g| g.board.set_dice(player, v))
}
fn dice_available(&self) -> &Vec<u8> {
self.board.dice_available()
}
fn dice_consumed(&self) -> bool {
self.board.dice_consumed()
}
}
impl Move for Game {
fn get_board(&self) -> BoardDisplay {
self.board.get_board()
}
fn move_checkers(
&mut self,
player: Player,
moves: Vec<(Position, Position)>,
) -> Result<(), Error> {
match self.state {
GameState::Rolling(_) => {
return Err(Error::RollFirst);
}
GameState::Moving(p) => {
if p != player {
return Err(Error::NotYourTurn);
}
}
GameState::GameEnd(_) => {
return Err(Error::GameEnded);
}
}
let move_valid = self.available_moves.iter().any(|m| m.starts_with(&moves));
if move_valid {
self.board.move_checkers(player, moves)?;
} else {
return Err(Error::MoveInvalid);
}
self.available_moves = self.board.available_moves(player)?.to_vec();
if self.available_moves.is_empty() {
if self.board.all_checkers_off(player)? {
if self.board.get_off(player.other())? != 0 {
let outcome = GameOutcome::Regular(player);
self.state = GameState::GameEnd(outcome);
}
if self.board.get_off(player.other())? == 0 {
let outcome = GameOutcome::Gammon(player);
self.state = GameState::GameEnd(outcome);
}
if self.board.get_off(player.other())? == 0
&& self.board.backgammon_loss_position(player.other())?
{
let outcome = GameOutcome::Backgammon(player);
self.state = GameState::GameEnd(outcome);
}
} else {
self.state = GameState::Rolling(player.other());
}
};
Ok(())
}
fn set_checkers(&mut self, player: Player, to: Position, amount: i8) -> Result<(), Error> {
self.board.set_checkers(player, to, amount)
}
fn empty_board(&mut self) -> Result<(), Error> {
self.board.empty_board()
}
fn available_moves(&self, _player: Player) -> Result<Vec<Vec<(Position, Position)>>, Error> {
Ok(self.available_moves.clone())
}
}
impl Stats for Game {
fn pip(&self, player: Player) -> Result<u8, Error> {
self.board.pip(player)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_display() {
let g = Game::new();
let display = format!("{}", g);
assert!(display.contains("State: Rolling(Nobody)"));
assert!(display.contains("Board:"));
}
#[test]
fn test_new_game() {
let g = Game::new();
assert_eq!(g.get_state(), &GameState::Rolling(Player::Nobody));
assert_eq!(g.get_dice(), (0, 0));
}
#[test]
fn test_initial_roll_determines_player() -> Result<(), Error> {
let mut g = Game::new();
assert_eq!(g.get_player(), &Player::Nobody);
while g.get_player() == &Player::Nobody {
g.roll(Player::Nobody)?;
}
let (d0, d1) = g.get_dice();
if d0 > d1 {
assert_eq!(g.get_player(), &Player::Player0);
} else if d1 > d0 {
assert_eq!(g.get_player(), &Player::Player1);
} else {
return Err(Error::PlayerInvalid);
}
assert!(matches!(
g.state,
GameState::Moving(Player::Player0 | Player::Player1)
));
Ok(())
}
#[test]
fn test_roll_before_consuming_dice() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::Rolling(Player::Player0);
assert_eq!(g.get_dice(), (0, 0));
assert_eq!(g.state, GameState::Rolling(Player::Player0));
g.roll(Player::Player0)?;
assert_eq!(g.state, GameState::Moving(Player::Player0));
let result = g.roll(Player::Player0);
assert_eq!(result, Err(Error::MoveFirst));
Ok(())
}
#[test]
fn test_selection_higher_first_die_selects_player0() -> Result<(), Error> {
let mut found_case = false;
for _ in 0..100 {
let mut game = Game::new();
game.roll(Player::Nobody)?;
let (d1, d2) = game.board.get_dice();
if d1 > d2 {
found_case = true;
assert_eq!(
game.get_player(),
&Player::Player0,
"Player0 should be selected when d1 ({}) > d2 ({})",
d1,
d2
);
}
}
assert!(
found_case,
"Should have found at least one case where d1 > d2"
);
Ok(())
}
#[test]
fn test_selection_higher_second_die_selects_player1() -> Result<(), Error> {
let mut found_case = false;
for _ in 0..100 {
let mut game = Game::new();
game.roll(Player::Nobody)?;
let (d1, d2) = game.board.get_dice();
if d2 > d1 {
found_case = true;
assert_eq!(
game.get_player(),
&Player::Player1,
"Player1 should be selected when d2 ({}) > d1 ({})",
d2,
d1
);
}
}
assert!(
found_case,
"Should have found at least one case where d2 > d1"
);
Ok(())
}
#[test]
fn test_selection_reroll_on_equal_dice() -> Result<(), Error> {
let mut g = Game::new();
let mut roll_count = 0;
while g.get_player() == &Player::Nobody && roll_count < 100 {
g.roll(Player::Nobody)?;
roll_count += 1;
}
assert_ne!(
g.get_player(),
&Player::Nobody,
"A player should eventually be selected after multiple rolls"
);
assert!(
matches!(g.get_player(), &Player::Player0 | &Player::Player1),
"Selected player should be either Player0 or Player1"
);
Ok(())
}
#[test]
fn test_pip() -> Result<(), Error> {
let g = Game::new();
let pip0 = g.pip(Player::Player0)?;
assert_eq!(pip0, 167, "Player0 should start with 167 pips");
let pip1 = g.pip(Player::Player1)?;
assert_eq!(pip1, 167, "Player1 should start with 167 pips");
Ok(())
}
#[test]
fn test_all_available_moves() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::Rolling(Player::Player0);
g.set_dice(Player::Player0, (6, 5))?;
assert!(!g.dice_consumed());
assert_eq!(g.dice_available(), &vec![6, 5]);
let moves = g.available_moves(Player::Player0)?;
assert_eq!(
moves,
vec![
[
(Position::Board(8), Position::Board(2)),
(Position::Board(8), Position::Board(3))
],
[
(Position::Board(8), Position::Board(2)),
(Position::Board(13), Position::Board(8))
],
[
(Position::Board(13), Position::Board(7)),
(Position::Board(7), Position::Board(2))
],
[
(Position::Board(13), Position::Board(7)),
(Position::Board(8), Position::Board(3))
],
[
(Position::Board(13), Position::Board(7)),
(Position::Board(13), Position::Board(8))
],
[
(Position::Board(24), Position::Board(18)),
(Position::Board(8), Position::Board(3))
],
[
(Position::Board(24), Position::Board(18)),
(Position::Board(13), Position::Board(8))
],
[
(Position::Board(24), Position::Board(18)),
(Position::Board(18), Position::Board(13))
],
[
(Position::Board(8), Position::Board(3)),
(Position::Board(8), Position::Board(2))
],
[
(Position::Board(8), Position::Board(3)),
(Position::Board(13), Position::Board(7))
],
[
(Position::Board(8), Position::Board(3)),
(Position::Board(24), Position::Board(18))
],
[
(Position::Board(13), Position::Board(8)),
(Position::Board(8), Position::Board(2))
],
[
(Position::Board(13), Position::Board(8)),
(Position::Board(13), Position::Board(7))
],
[
(Position::Board(13), Position::Board(8)),
(Position::Board(24), Position::Board(18))
]
]
);
Ok(())
}
#[test]
fn test_set_player() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::Rolling(Player::Nobody);
g.set_player(Player::Player0)?;
assert_eq!(g.state, GameState::Rolling(Player::Player0));
g.state = GameState::Moving(Player::Player0);
g.set_player(Player::Player1)?;
assert_eq!(g.state, GameState::Moving(Player::Player1));
g.state = GameState::GameEnd(GameOutcome::Regular(Player::Player0));
let result = g.set_player(Player::Player1);
assert_eq!(result, Err(Error::GameEnded));
assert_eq!(
g.get_player(),
&Player::Nobody,
"Nobody plays after game ends."
);
Ok(())
}
#[test]
fn test_set_player_nobody_after_game_started() {
let mut g = Game::new();
g.state = GameState::Rolling(Player::Player0);
let result = g.set_player(Player::Nobody);
assert_eq!(result, Err(Error::GameStarted));
assert_eq!(g.state, GameState::Rolling(Player::Player0));
}
#[test]
fn test_set_player_nobody_after_game_started2() {
let mut g = Game::new();
g.state = GameState::Moving(Player::Player0);
let result = g.set_player(Player::Nobody);
assert_eq!(result, Err(Error::GameStarted));
assert_eq!(g.state, GameState::Moving(Player::Player0));
}
#[test]
fn test_dice_operation_not_your_turn() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::Rolling(Player::Player0);
let result = g.roll(Player::Player1);
assert_eq!(result, Err(Error::NotYourTurn));
g.state = GameState::Moving(Player::Player0);
let result = g.roll(Player::Player1);
assert_eq!(result, Err(Error::NotYourTurn));
Ok(())
}
#[test]
fn test_dice_operation_move_first() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::Moving(Player::Player0);
let result = g.roll(Player::Player0);
assert_eq!(result, Err(Error::MoveFirst));
Ok(())
}
#[test]
fn test_dice_operation_game_ended() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::GameEnd(GameOutcome::Regular(Player::Player0));
let result = g.set_dice(Player::Player1, (1, 4));
assert_eq!(result, Err(Error::GameEnded));
g.state = GameState::GameEnd(GameOutcome::Gammon(Player::Player1));
let result = g.move_checkers(
Player::Player1,
vec![(Position::Board(3), Position::Board(1))],
);
assert_eq!(result, Err(Error::GameEnded));
g.state = GameState::GameEnd(GameOutcome::Regular(Player::Player1));
let result = g.set_dice(Player::Player1, (1, 4));
assert_eq!(result, Err(Error::GameEnded));
g.state = GameState::GameEnd(GameOutcome::Gammon(Player::Player0));
let result = g.move_checkers(
Player::Player1,
vec![(Position::Board(3), Position::Board(1))],
);
assert_eq!(result, Err(Error::GameEnded));
g.state = GameState::GameEnd(GameOutcome::Backgammon(Player::Player0));
let result = g.set_dice(Player::Player1, (1, 4));
assert_eq!(result, Err(Error::GameEnded));
g.state = GameState::GameEnd(GameOutcome::Backgammon(Player::Player1));
let result = g.move_checkers(
Player::Player1,
vec![(Position::Board(3), Position::Board(1))],
);
assert_eq!(result, Err(Error::GameEnded));
Ok(())
}
#[test]
fn test_move_checkers_roll_first_error() {
let mut g = Game::new();
g.state = GameState::Rolling(Player::Player0);
let result = g.move_checkers(Player::Player0, vec![]);
assert_eq!(result, Err(Error::RollFirst));
}
#[test]
fn test_move_checkers_not_your_turn_error() {
let mut g = Game::new();
g.state = GameState::Moving(Player::Player0);
let result = g.move_checkers(Player::Player1, vec![]);
assert_eq!(result, Err(Error::NotYourTurn));
}
#[test]
fn test_move_checkers_game_ended_error() {
let mut g = Game::new();
g.state = GameState::GameEnd(GameOutcome::Regular(Player::Player0));
let result = g.move_checkers(Player::Player0, vec![]);
assert_eq!(result, Err(Error::GameEnded));
}
#[test]
fn test_move_checkers_transitions_to_other_player() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::Rolling(Player::Player0);
g.set_dice(Player::Player0, (1, 2))?;
g.move_checkers(
Player::Player0,
vec![
(Position::Board(24), Position::Board(23)),
(Position::Board(24), Position::Board(22)),
],
)?;
assert_eq!(g.state, GameState::Rolling(Player::Player1));
Ok(())
}
#[test]
fn test_move_checkers_state_unchanged_with_available_moves() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::Rolling(Player::Player0);
g.set_dice(Player::Player0, (6, 5))?;
g.move_checkers(
Player::Player0,
vec![(Position::Board(24), Position::Board(18))],
)?;
assert_eq!(g.state, GameState::Moving(Player::Player0));
Ok(())
}
#[test]
fn test_move_checkers_wins_normal() -> Result<(), Error> {
let mut g = Game::new();
g.board.empty_board()?;
g.board
.set_checkers(Player::Player0, Position::Board(1), 2)?;
g.board.set_checkers(Player::Player0, Position::Off, 13)?;
g.board
.set_checkers(Player::Player1, Position::Board(22), 2)?;
g.board.set_checkers(Player::Player1, Position::Off, 13)?;
g.state = GameState::Rolling(Player::Player0);
g.set_dice(Player::Player0, (1, 3))?;
g.move_checkers(
Player::Player0,
vec![
(Position::Board(1), Position::Off),
(Position::Board(1), Position::Off),
],
)?;
assert_eq!(
g.state,
GameState::GameEnd(GameOutcome::Regular(Player::Player0))
);
Ok(())
}
#[test]
fn test_move_checkers_wins_gammon() -> Result<(), Error> {
let mut g = Game::new();
g.board.empty_board()?;
g.board
.set_checkers(Player::Player0, Position::Board(1), 2)?;
g.board.set_checkers(Player::Player0, Position::Off, 13)?;
g.board
.set_checkers(Player::Player1, Position::Board(1), 15)?;
g.state = GameState::Rolling(Player::Player0);
g.set_dice(Player::Player0, (1, 3))?;
g.move_checkers(
Player::Player0,
vec![
(Position::Board(1), Position::Off),
(Position::Board(1), Position::Off),
],
)?;
assert_eq!(
g.state,
GameState::GameEnd(GameOutcome::Gammon(Player::Player0))
);
Ok(())
}
#[test]
fn test_move_checkers_wins_backgammon_bar() -> Result<(), Error> {
let mut g = Game::new();
g.board.empty_board()?;
g.board
.set_checkers(Player::Player0, Position::Board(1), 2)?;
g.board.set_checkers(Player::Player0, Position::Off, 13)?;
g.board
.set_checkers(Player::Player1, Position::Board(1), 14)?;
g.board.set_checkers(Player::Player1, Position::Bar, 14)?;
g.state = GameState::Rolling(Player::Player0);
g.set_dice(Player::Player0, (1, 3))?;
g.move_checkers(
Player::Player0,
vec![
(Position::Board(1), Position::Off),
(Position::Board(1), Position::Off),
],
)?;
assert_eq!(
g.state,
GameState::GameEnd(GameOutcome::Backgammon(Player::Player0))
);
Ok(())
}
#[test]
fn test_move_checkers_wins_backgammon_home() -> Result<(), Error> {
let mut g = Game::new();
g.board.empty_board()?;
g.board
.set_checkers(Player::Player0, Position::Board(1), 2)?;
g.board.set_checkers(Player::Player0, Position::Off, 13)?;
g.board
.set_checkers(Player::Player1, Position::Board(23), 15)?;
g.state = GameState::Rolling(Player::Player0);
g.set_dice(Player::Player0, (1, 3))?;
g.move_checkers(
Player::Player0,
vec![
(Position::Board(1), Position::Off),
(Position::Board(1), Position::Off),
],
)?;
assert_eq!(
g.state,
GameState::GameEnd(GameOutcome::Backgammon(Player::Player0))
);
Ok(())
}
#[test]
fn test_set_checkers() -> Result<(), Error> {
let mut g = Game::new();
g.set_checkers(Player::Player0, Position::Board(6), 3)?;
let board = g.board.get_board();
assert_eq!(
board.board[5], 8,
"Position 6 should have 3 checkers for Player0"
);
g.set_checkers(Player::Player1, Position::Board(10), 2)?;
let board = g.board.get_board();
assert_eq!(
board.board[12], 5,
"Position 10 should have 2 checkers for Player1"
);
Ok(())
}
#[test]
fn move_checkers_fails_with_invalid_moves() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::Rolling(Player::Player0);
g.set_dice(Player::Player0, (6, 5))?;
let result = g.move_checkers(
Player::Player0,
vec![(Position::Board(24), Position::Board(20))],
);
assert_eq!(result, Err(Error::MoveInvalid));
Ok(())
}
#[test]
fn test_roll_on_game_end_returns_error() {
let mut g = Game::new();
g.state = GameState::GameEnd(GameOutcome::Regular(Player::Player0));
let result = g.roll(Player::Player0);
assert_eq!(result, Err(Error::GameEnded));
}
#[test]
fn test_set_player_game_ended_regular_player0() {
let mut g = Game::new();
g.state = GameState::GameEnd(GameOutcome::Regular(Player::Player0));
assert_eq!(g.set_player(Player::Player0), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Player1), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Nobody), Err(Error::GameEnded));
}
#[test]
fn test_set_player_game_ended_regular_player1() {
let mut g = Game::new();
g.state = GameState::GameEnd(GameOutcome::Regular(Player::Player1));
assert_eq!(g.set_player(Player::Player0), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Player1), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Nobody), Err(Error::GameEnded));
}
#[test]
fn test_set_player_game_ended_gammon_player0() {
let mut g = Game::new();
g.state = GameState::GameEnd(GameOutcome::Gammon(Player::Player0));
assert_eq!(g.set_player(Player::Player0), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Player1), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Nobody), Err(Error::GameEnded));
}
#[test]
fn test_set_player_game_ended_gammon_player1() {
let mut g = Game::new();
g.state = GameState::GameEnd(GameOutcome::Gammon(Player::Player1));
assert_eq!(g.set_player(Player::Player0), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Player1), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Nobody), Err(Error::GameEnded));
}
#[test]
fn test_set_player_game_ended_backgammon_player0() {
let mut g = Game::new();
g.state = GameState::GameEnd(GameOutcome::Backgammon(Player::Player0));
assert_eq!(g.set_player(Player::Player0), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Player1), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Nobody), Err(Error::GameEnded));
}
#[test]
fn test_set_player_game_ended_backgammon_player1() {
let mut g = Game::new();
g.state = GameState::GameEnd(GameOutcome::Backgammon(Player::Player1));
assert_eq!(g.set_player(Player::Player0), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Player1), Err(Error::GameEnded));
assert_eq!(g.set_player(Player::Nobody), Err(Error::GameEnded));
}
#[test]
fn test_set_player_game_ended_state_unchanged() {
let mut g = Game::new();
let outcome = GameOutcome::Gammon(Player::Player1);
g.state = GameState::GameEnd(outcome.clone());
let _ = g.set_player(Player::Player0);
assert_eq!(g.state, GameState::GameEnd(outcome));
assert_eq!(g.get_player(), &Player::Nobody);
}
#[test]
fn test_set_dice_move_first() -> Result<(), Error> {
let mut g = Game::new();
g.state = GameState::Moving(Player::Player0);
let result = g.set_dice(Player::Player0, (3, 4));
assert_eq!(result, Err(Error::MoveFirst));
Ok(())
}
#[test]
fn test_set_dice_not_your_turn_in_moving_state() {
let mut g = Game::new();
g.state = GameState::Moving(Player::Player0);
let result = g.set_dice(Player::Player1, (3, 4));
assert_eq!(result, Err(Error::NotYourTurn));
}
}