use std::fmt::{self, Display, Formatter};
pub type Bowl = usize;
pub type Stones = u8;
pub type Score = i8;
pub struct GameBuilder {
bowls: u8,
stones: Stones,
}
impl GameBuilder {
pub fn new() -> Self {
GameBuilder {
bowls: 6,
stones: 4,
}
}
pub fn bowls(self, bowls: u8) -> Self {
GameBuilder { bowls, ..self }
}
pub fn stones(self, stones: Stones) -> Self {
GameBuilder { stones, ..self }
}
pub fn build(self) -> Game {
let current = Position::new(self.bowls, self.stones);
Game {
current,
history: vec![],
}
}
}
impl Default for GameBuilder {
fn default() -> Self {
GameBuilder::new()
}
}
#[derive(Debug, PartialEq)]
pub struct Game {
pub current: Position,
history: Vec<(Player, Bowl)>,
}
impl Game {
pub fn finished(&self) -> bool {
self.current.finished()
}
pub fn options(&self) -> Vec<Bowl> {
self.current.options()
}
pub fn play(&mut self, bowl: Bowl) -> Result<(), FoulPlay> {
match self.current.play(bowl) {
Some(position) => {
self.history.push((self.current.player, bowl));
self.current = position;
Ok(())
}
None => Err(FoulPlay::NoStonesInBowl),
}
}
pub fn score(&self) -> Option<Score> {
self.current.score()
}
pub fn turn(&self) -> Player {
self.current.turn()
}
}
#[derive(Debug)]
pub enum FoulPlay {
NoStonesInBowl,
}
#[derive(Debug, PartialEq)]
pub struct Position {
player: Player,
size: usize,
capture: [Stones; 2],
bowls: Vec<Stones>,
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Player {
Red,
Blue,
}
impl Player {
pub fn other(self) -> Self {
match self {
Player::Red => Player::Blue,
Player::Blue => Player::Red,
}
}
}
impl Position {
pub fn new(bowls: u8, stones: Stones) -> Self {
let size = bowls as usize;
let bowls = vec![stones; 2 * size];
Position {
player: Player::Red,
size,
capture: [0, 0],
bowls,
}
}
pub fn options(&self) -> Vec<Bowl> {
self.bowls[0..self.size]
.iter()
.cloned()
.enumerate()
.filter_map(|(bowl, stones)| if stones > 0 { Some(bowl) } else { None })
.collect()
}
pub fn play(&self, bowl: Bowl) -> Option<Self> {
if self.bowls[bowl] > 0 {
Some(self.sow(bowl))
} else {
None
}
}
fn sow(&self, bowl: Bowl) -> Self {
let mut player = self.player;
let mut bowls = self.bowls.clone();
let mut stones = bowls[bowl];
let mut index = bowl;
let mut store_offset = 0;
let mut stored = 0;
let mut change_player = true;
bowls[index] = 0;
while stones > 0 {
index += 1;
if index - store_offset == 2 * self.size {
index = 0;
store_offset = 0;
}
if index == self.size {
stored += 1;
store_offset = 1;
} else {
bowls[index - store_offset] += 1;
}
if stones == 1 && index == self.size {
change_player = false
}
if stones == 1 && store_offset == 0 && bowls[index] == 1 {
let capture_index = 2 * self.size - 1 - index;
stored += bowls[capture_index];
bowls[capture_index] = 0;
}
stones -= 1;
}
let mut capture = [self.capture[0] + stored as Stones, self.capture[1]];
if change_player {
player = player.other();
capture = [capture[1], capture[0]];
bowls.rotate_left(self.size);
}
Position {
player,
size: self.size,
capture,
bowls,
}
}
pub fn finished(&self) -> bool {
self.bowls[0..self.size].iter().all(|&stones| stones == 0)
|| self.bowls[self.size..(2 * self.size)]
.iter()
.all(|&stones| stones == 0)
}
pub fn active_player(&self) -> Player {
self.player
}
pub fn score(&self) -> Option<Score> {
if self.finished() {
let mut first: Stones = self.bowls[0..self.size].iter().cloned().sum();
first += self.capture[0];
let mut second: Stones = self.bowls[self.size..2 * self.size].iter().cloned().sum();
second += self.capture[1];
let score = first as Score - second as Score;
Some(score)
} else {
None
}
}
pub fn delta(&self) -> Score {
self.capture[0] as Score - self.capture[1] as Score
}
pub fn turn(&self) -> Player {
self.player
}
}
impl Display for Position {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let current_side = &self.bowls[0..self.size];
let opposite_side: &Vec<Stones> = &self.bowls[self.size..].iter().cloned().rev().collect();
write!(f, "{:<3}", self.capture[1])?;
for stones in opposite_side {
write!(f, "| {:<3} ", stones)?
}
writeln!(f, "|")?;
write!(f, "{:<3}", "")?;
for stones in current_side {
write!(f, "| {:<3} ", stones)?
}
writeln!(f, "| {:<3}", self.capture[0])
}
}
#[cfg(test)]
mod tests {
use super::*;
fn from_position<P>(position: P) -> PlayedGameBuilder
where
P: Into<Position>,
{
PlayedGameBuilder {
current: position.into(),
}
}
struct PlayedGameBuilder {
current: Position,
}
impl PlayedGameBuilder {
fn with_history(self, history: Vec<(Player, Bowl)>) -> Game {
Game {
current: self.current,
history,
}
}
}
#[test]
fn fresh_game_is_not_finished() {
let game = GameBuilder::new().bowls(6).stones(4).build();
assert!(!game.finished());
}
#[test]
fn game_knows_options_to_play() {
let game = GameBuilder::new().bowls(3).stones(2).build();
let options = game.options();
assert_eq!(options, vec!(0, 1, 2));
}
#[test]
fn game_records_history_of_what_is_played() -> Result<(), FoulPlay> {
let mut actual = GameBuilder::new().bowls(3).stones(2).build();
actual.play(0)?;
let position = (Player::Blue, [2, 2, 2, 0, 3, 3]);
let expected = from_position(position).with_history(vec![(Player::Red, 0)]);
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn play_that_goes_over_store_should_capture_stone() {
let start = Position::from([2, 2, 2, 2]);
let actual = start.play(1);
let expected = Position::from((Player::Blue, 0, 1, [3, 2, 2, 0]));
assert_eq!(actual, Some(expected))
}
#[test]
fn play_that_cycles_should_start_over() {
let start = Position::from([6, 6, 6, 6]);
let actual = start.play(0);
let expected = Position::from((Player::Blue, 0, 1, [7, 7, 1, 8]));
assert_eq!(actual, Some(expected))
}
#[test]
fn play_into_your_store_allows_an_other_turn() {
let start = Position::from([2, 2, 2, 2, 2, 2]);
let actual = start.play(1);
let expected = Position::from((1, 0, [2, 0, 3, 2, 2, 2]));
assert_eq!(actual, Some(expected))
}
#[test]
fn play_into_empty_bowl_captures_opposite_bowl() {
let start = Position::from([2, 2, 0, 2, 2, 2, 2, 2]);
let actual = start.play(0);
let expected = Position::from((Player::Blue, 0, 2, [2, 0, 2, 2, 0, 3, 1, 2]));
assert_eq!(actual, Some(expected))
}
#[test]
fn positions_with_no_stones_on_one_side_is_finished() {
let start = Position::from([0, 0, 2, 2]);
assert!(start.finished());
assert_eq!(start.score(), Some(-4));
}
#[test]
fn play_changes_player() {
let start = Position::from([1, 0, 1, 0]);
let actual = start.play(0).unwrap();
let expected = Position::from((Player::Blue, 0, 1, [0, 0, 0, 1]));
assert_eq!(actual, expected);
assert_eq!(expected.score(), Some(-2));
}
}
macro_rules! position_from_array_for_sizes {
( $($n : expr),* ) => {
$(
impl From<[Stones; $n]> for Position {
fn from(bowls: [Stones; $n]) -> Self {
Position {
player: Player::Red,
size: $n/2,
capture: [0, 0],
bowls: bowls.to_vec(),
}
}
}
)*
}
}
macro_rules! position_with_player_from_array_for_sizes {
( $($n : expr),* ) => {
$(
impl From<(Player, [Stones; $n])> for Position {
fn from(data: (Player, [Stones; $n])) -> Self {
Position {
player: data.0,
size: $n/2,
capture: [0, 0],
bowls: data.1.to_vec(),
}
}
}
)*
}
}
macro_rules! position_with_capture_from_array_for_sizes {
( $($n : expr),* ) => {
$(
impl From<(Stones, Stones, [Stones; $n])> for Position {
fn from(data: (Stones, Stones, [Stones; $n])) -> Self {
Position {
player: Player::Red,
size: $n/2,
capture: [data.0, data.1],
bowls: data.2.to_vec(),
}
}
}
)*
}
}
macro_rules! position_with_player_with_capture_from_array_for_sizes {
( $($n : expr),* ) => {
$(
impl From<(Player, Stones, Stones, [Stones; $n])> for Position {
fn from(data: (Player, Stones, Stones, [Stones; $n])) -> Self {
Position {
player: data.0,
size: $n/2,
capture: [data.1, data.2],
bowls: data.3.to_vec(),
}
}
}
)*
}
}
position_from_array_for_sizes!(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32);
position_with_player_from_array_for_sizes!(
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32
);
position_with_capture_from_array_for_sizes!(
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32
);
position_with_player_with_capture_from_array_for_sizes!(
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32
);