use std::default::Default; use std::{fmt, mem}; use std::fmt::{Display, Formatter, Write}; use rand::{Rng, rngs::OsRng}; use std::borrow::Borrow; use permutator::copy::k_permutation; #[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 { turn_number: usize, turn : Color, board: Board, roll: Roll, black : Player, white : Player, } #[derive(Debug,Clone,Copy)] pub enum Move { InBoard(/* from */ u8, /* to */ u8), Enter(/* pos */ u8), BearOff(/* pos */ 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 { turn_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 } /// Get turn info. Returns a tuple of `(turn_number, turn_color)`, where /// `turn_number` is incremented after each turn ends (can be used to detect /// automatic turn skips that result in the same player playing again. pub fn turn(&self) -> (usize, Color) { (self.turn_number, self.turn) } /// Get reference to own player struct fn player(&self) -> &Player { match self.turn { Color::Black => &self.black, Color::White => &self.white, } } /// Get mutable reference to own player struct fn player_mut(&mut self) -> &mut Player { match self.turn { Color::Black => &mut self.black, Color::White => &mut self.white, } } /// Get reference to the other player's struct fn other(&self) -> &Player { match self.turn { Color::Black => &self.white, Color::White => &self.black, } } /// Get mutable reference to the other player's struct fn other_mut(&mut self) -> &mut Player { match self.turn { Color::Black => &mut self.white, Color::White => &mut self.black, } } /// Apply a move to the board. /// /// turn must match the current turn's color, move must be valid for that player. 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 = next.yield_turn(); } Ok(next) } pub fn yield_turn(&self) -> Self { let mut next = self.clone(); next.turn = self.turn.opposite(); next.turn_number += 1; next.roll_mut(); next } /// Inject a faked dice roll pub fn spoof_roll(&self, roll : Roll) -> Self { let mut next = self.clone(); next.roll = roll; next } /// Rull dice (in place) fn roll_mut(&mut self) { self.roll = Roll::new(); } pub fn get_max_number_of_possible_moves_with_current_roll(&self) -> usize { let mut max = 0; let color = self.turn; let turn_number = self.turn_number; k_permutation(&self.roll.remaining_moves, self.roll.remaining_moves.len(), |permuted| { if max == self.roll.remaining_moves.len() { // There is nothing more to look for, discard the remaining iterations (sadly, can't break;) return; } let mut to_try = vec![self.clone()]; let mut to_try_next_roll = vec![]; for (n, roll) in permuted.iter().copied().enumerate() { let depth = n + 1; to_try_next_roll.clear(); for state in to_try.drain(..) { for mv in PossibleMovesOfLen::new(&state, roll) { if let Ok(next) = state.apply_move(color, mv) { max = max.max(depth); if next.turn_number == turn_number { to_try_next_roll.push(next); } } } } std::mem::swap(&mut to_try, &mut to_try_next_roll) } }); max } } struct PossibleMovesOfLen<'a> { state : &'a State, pos : i8, step: i8, len : u8 } impl<'a> PossibleMovesOfLen<'a> { pub fn new(state : &'a State, len : u8) -> Self { Self { state, pos: -1, step: if state.turn == Color::White { -(len as i8) } else { len as i8 }, len } } } impl<'a> Iterator for PossibleMovesOfLen<'a> { type Item = Move; fn next(&mut self) -> Option { if self.pos > 23 { return None; } let state = self.state; if state.player().to_place > 0 { let p = if state.turn == Color::White { 24 - self.len } else { self.len }; if state.board.can_place(p, state.turn) { self.pos = 24; // no more moves return Some(Move::Enter(p)); } } 'next: loop { self.pos += 1; if self.pos > 23 { return None; } if state.board.can_move_from(self.pos as u8, state.turn) { let dest = self.pos + self.step; if dest < 0 || dest > 23 { return Some(Move::BearOff(self.pos as u8)); } else { if state.board.can_place(dest as u8, state.turn) { return Some(Move::InBoard(self.pos as u8, dest as u8)); } } } } } } 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 )?; if self.turn == Color::White { f.write_str("\x1b[1m")?; } f.write_str("White <- \x1b[m")?; for n in 0..=23 { write!(f, "{:02} ", n+1)?; } if self.turn == Color::Black { f.write_str("\x1b[1m")?; } f.write_str(" -> Black\x1b[m\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(()) // } // }