use std::default::Default; use std::{fmt, mem}; use std::fmt::{Display, Formatter, Write}; use rand::{Rng, rngs::OsRng}; use std::borrow::Borrow; #[derive(Debug,Clone)] struct Board([Bin; 24]); impl Board { fn can_bear_off(&self, color : Color) -> bool { // this range covers the non-home area of the board let range = if color == Color::White { // White's home is low indices 6..=23 } else { // Black's home is high indices 0..=17 }; for i in range { match (self.0)[i as usize].as_color() { Some(c) if c == color => { return false; }, _ => { // empty or the other color } } } true } fn can_place(&self, pos: u8, color : Color) -> bool { if pos > BOARD_MAX { false } else { let c = (self.0)[pos as usize].as_color(); c.is_none() || c.unwrap() == color || (self.0)[pos as usize].as_count() == 1 } } fn can_move_from(&self, pos: u8, color : Color) -> bool { if pos > BOARD_MAX { false } else { let c = (self.0)[pos as usize].as_color(); c.is_some() && c.unwrap() == color } } #[must_use] fn apply_move_mut(&mut self, from : u8, to : u8) -> Option { let old_color = self.0[to as usize].as_color(); let color = self.0[from as usize].subtract_mut(); self.0[to as usize].add_mut(color); old_color } #[must_use] fn apply_enter_mut(&mut self, pos : u8, color : Color) -> Option { let old_color = self.0[pos as usize].as_color(); self.0[pos as usize].add_mut(color); old_color } fn apply_bearoff_mut(&mut self, pos : u8) { self.0[pos as usize].subtract_mut(); } } #[derive(Debug,Clone,Copy,PartialEq,Eq)] pub enum Color { /// Black, home area 18..=23 Black, /// White, home area 0..=5 White, } impl Display for Color { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) // re-use the debug renderer } } impl Color { fn opposite(self) -> Color { match self { Color::Black => Color::White, Color::White => Color::Black, } } } #[derive(Debug,Clone,Copy)] pub enum Bin { Empty, Black(u8), White(u8), } impl Bin { pub fn as_color(self) -> Option { match self { Bin::Empty => None, Bin::Black(_) => Some(Color::Black), Bin::White(_) => Some(Color::White), } } pub fn as_count(self) -> u8 { match self { Bin::Empty => 0, Bin::Black(n) | Bin::White(n) => n, } } fn add_mut(&mut self, color : Color) { mem::replace(self, match self.borrow() { Bin::Empty => { match color { Color::Black => Bin::Black(1), Color::White => Bin::White(1), } }, Bin::Black(n) => { if color == Color::Black { Bin::Black(*n + 1) } else { Bin::White(1) } }, Bin::White(n) => { if color == Color::White { Bin::White(*n + 1) } else { Bin::Black(1) } } }); } fn subtract_mut(&mut self) -> Color { match self { Bin::Empty => { panic!("Subtract from empty bin"); }, Bin::Black(n) => { *n -= 1u8; if *n == 0 { mem::replace(self, Bin::Empty); } Color::Black }, Bin::White(n) => { *n -= 1u8; if *n == 0 { mem::replace(self, Bin::Empty); } Color::White }, } } } #[derive(Debug, Clone)] struct Player { born_off : u8, to_place : u8, } #[derive(Debug, Clone)] pub struct State { move_number : usize, turn : Color, board: Board, roll: Roll, black : Player, white : Player, } #[derive(Debug,Clone,Copy)] pub enum Move { InBoard(u8, u8), Enter(u8), BearOff(u8) } #[derive(Debug)] pub enum Error { Internal, MalformedMove, NoSourceStone, TargetOccupied, NotBearingOff, NotAllPlaced, NothingToPlace, NoMatchingRoll, NoValidMoves, NotYourTurn, } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { // TODO write!(f, "{:?}", self) } } impl std::error::Error for Error {} #[derive(Debug, Clone)] pub struct Roll { /// The dice as rolled dice: (u8, u8), /// Remaining moves based on the dice and steps already performed remaining_moves: Vec, } impl Roll { pub fn new() -> Self { Self::from_dice(( OsRng.gen_range(1, 7), OsRng.gen_range(1, 7) )) } pub fn from_dice(dice: (u8,u8)) -> Self { let remaining_moves; if dice.0 == dice.1 { remaining_moves = vec![dice.0, dice.0, dice.0, dice.0]; } else { remaining_moves = vec![dice.0, dice.1]; } Roll { dice, remaining_moves } } pub fn have_equal(&self, eq : u8) -> bool { self.remaining_moves.iter() .any(|v| *v == eq) } pub fn have_equal_or_greater(&self, mineq : u8) -> bool { self.remaining_moves.iter() .any(|v| *v >= mineq) } pub fn get_equal_or_greater(&self, mineq : u8) -> Option { self.remaining_moves.iter() .find(|v| *v >= &mineq) .cloned() } fn remove_mut(&mut self, needed : u8) { let nth = self.remaining_moves.iter().position(|v| *v == needed).unwrap(); self.remaining_moves.remove(nth); } /// # Panics /// if not available pub fn remove(&mut self, needed : u8) -> Roll { let mut next = self.clone(); next.remove_mut(needed); next } } // The board is reversed based on the active player's color to make the algorithm unified. // The movement is always to lower positions, with home at 5-0 const BOARD_MAX : u8 = 23; const HOME_MAX : u8 = 5; impl State { pub fn start(turn : Option) -> Self { // The state is laid out for the white player let mut state = State { move_number: 0, turn: Color::White, board: Board([ // 1 .. 12 (bottom row, right to left) Bin::Black(2), // white player's home side Bin::Empty, Bin::Empty, Bin::Empty, Bin::Empty, Bin::White(5), // 5 in 0-based Bin::Empty, Bin::White(3), Bin::Empty, Bin::Empty, Bin::Empty, Bin::Black(5), // 13 .. 24 (top row, left to right) Bin::White(5), // 12 in 0-based Bin::Empty, Bin::Empty, Bin::Empty, Bin::Black(3), Bin::Empty, Bin::Black(5), // 18 in 0-based Bin::Empty, Bin::Empty, Bin::Empty, Bin::Empty, Bin::White(2), // black player's home side ]), roll: Roll::new(), black: Player { born_off: 0, to_place: 0 }, white: Player { born_off: 0, to_place: 0 } }; if turn.map(|c| c == Color::Black).unwrap_or_else(|| OsRng.gen()) { // flip the board so black starts state.turn = Color::Black; } state } pub fn turn(&self) -> (usize, Color) { (self.move_number, self.turn) } fn player(&self) -> &Player { match self.turn { Color::Black => &self.black, Color::White => &self.white, } } fn player_mut(&mut self) -> &mut Player { match self.turn { Color::Black => &mut self.black, Color::White => &mut self.white, } } fn other(&self) -> &Player { match self.turn { Color::Black => &self.white, Color::White => &self.black, } } fn other_mut(&mut self) -> &mut Player { match self.turn { Color::Black => &mut self.white, Color::White => &mut self.black, } } pub fn apply_move(&self, turn : Color, mv : Move) -> Result { if turn != self.turn { return Err(Error::NotYourTurn); } print!("{} plays move: ", turn); let mut next = self.clone(); match mv { Move::InBoard(from, to) => { println!("In-board {} -> {}", from, to); if (self.turn == Color::White && to >= from) || (self.turn == Color::Black && to <= from) { return Err(Error::MalformedMove); } if self.player().to_place != 0 { return Err(Error::NotAllPlaced); } if !self.board.can_move_from(from, self.turn) { return Err(Error::NoSourceStone); } if !self.board.can_place(to, self.turn) { return Err(Error::TargetOccupied); } let needed = (from as i8 - to as i8).abs() as u8; if !self.roll.have_equal(needed) { return Err(Error::NoMatchingRoll); } let old_color = next.board.apply_move_mut(from, to); next.roll.remove_mut(needed); if let Some(c) = old_color { if c != self.turn { println!("{} stone at position {} is hit.", c, to); // hit opposite color next.other_mut().to_place += 1; } } }, Move::Enter(pos) => { println!("Enter -> {}", pos); if self.player().to_place == 0 { return Err(Error::NothingToPlace); } if pos > BOARD_MAX { return Err(Error::MalformedMove); } let needed = if self.turn == Color::White { 24 - pos } else { pos + 1 }; if !self.roll.have_equal(needed) { return Err(Error::NoMatchingRoll); } if !self.board.can_place(pos, self.turn) { return Err(Error::TargetOccupied); } let old_color = next.board.apply_enter_mut(pos, self.turn); next.roll.remove_mut(needed); next.player_mut().to_place -= 1; if let Some(c) = old_color { if c != self.turn { println!("{} stone at position {} is hit.", c, pos); // hit opposite color next.other_mut().to_place += 1; } } }, Move::BearOff(pos) => { println!("Bear off -> {}", pos); if (self.turn == Color::White && pos > HOME_MAX) || (self.turn == Color::Black && pos < 18) { return Err(Error::MalformedMove); } if self.player().to_place != 0 { return Err(Error::NotAllPlaced); } if !self.board.can_bear_off(self.turn) { // still have checkers in higher positions return Err(Error::NotBearingOff); } if !self.roll.have_equal_or_greater(pos + 1) { return Err(Error::NoMatchingRoll); // TODO must always bear off the highest possible, if multiple checkers are available } let needed = next.roll.get_equal_or_greater(pos + 1).unwrap(); next.roll.remove_mut(needed); }, } if next.roll.remaining_moves.is_empty() { next.turn = self.turn.opposite(); next.move_number += 1; next.roll_mut(); // TODO check if any moves are possible with this roll, if not, auto-switch again } Ok(next) } pub fn spoof_roll(&self, roll : Roll) -> Self { let mut next = self.clone(); next.roll = roll; next } fn roll_mut(&mut self) { self.roll = Roll::new(); } } impl Display for State { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "\n# {} turn, roll ({},{}), moves to play: [{}]\n", self.turn, self.roll.dice.0, self.roll.dice.1, self.roll.remaining_moves.iter().map(ToString::to_string).collect::>().join(",") )?; write!(f, "# bearing off? {}, born off: {}, to place: {}\n", if self.board.can_bear_off(self.turn) { "YES" } else { "no" }, self.player().born_off, self.player().to_place )?; f.write_str("White <- ")?; for n in 0..=23 { write!(f, "{:02} ", n+1)?; } f.write_str(" -> Black\n")?; f.write_str(" ")?; for n in 0..=23 { match self.board.0[n] { Bin::Empty => { write!(f, "--- ", )?; }, Bin::Black(i) => { write!(f, "K{:<3} ", i)?; }, Bin::White(i) => { write!(f, "W{:<3} ", i)?; }, } } f.write_str("\n")?; Ok(()) } } // impl Display for Game { // fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result { // write!(f, "{}'s turn, roll {}+{}", self.turn, self.roll.0, self.roll.1)?; // // let pl = self.cur_player(); // // if pl.bearing_off { // write!(f, ", born off {}/15", pl.born_off)?; // } // // if pl.on_bar > 0 { // write!(f, ", {} on bar!", pl.on_bar)?; // } // else { // write!(f, ", normal move")?; // } // // f.write_str("\n")?; // // for n in (13..=24) { // write!(f, "{} ", ('m' as u8 + (n - 13) as u8) as char)?; // } // write!(f, "\n")?; // // for (i, b) in (&self.bins[12..=23]).iter().enumerate() { // match *b { // Bin::Empty => { // if i%2==0 { // write!(f, "░░")? // } else { // write!(f, "▒▒")? // } // }, // Bin::Black(n) => write!(f, "{:X}#", n)?, // Bin::White(n) => write!(f, "{:X}:", n)? // } // } // write!(f, "|:{}\n", self.players[Side::Black as usize].born_off)?; // // for (i, b) in (&self.bins[0..=11]).iter().rev().enumerate() { // match *b { // Bin::Empty => { // if i%2==0 { // write!(f, "░░")? // } else { // write!(f, "▒▒")? // } // }, // Bin::Black(n) => write!(f, "{:X}#", n)?, //█ // Bin::White(n) => write!(f, "{:X}:", n)? //░ // } // } // // write!(f, "|#{}\n", self.players[Side::Black as usize].born_off)?; // for n in (1..=12).rev() { // write!(f, "{} ", ('a' as u8 + (n-1) as u8) as char)?; // } // write!(f, "\n")?; // // Ok(()) // } // }