|
|
|
@ -3,6 +3,7 @@ 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]); |
|
|
|
@ -176,7 +177,7 @@ struct Player { |
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
|
|
pub struct State { |
|
|
|
|
move_number : usize, |
|
|
|
|
turn_number: usize, |
|
|
|
|
turn : Color, |
|
|
|
|
board: Board, |
|
|
|
|
roll: Roll, |
|
|
|
@ -186,9 +187,9 @@ pub struct State { |
|
|
|
|
|
|
|
|
|
#[derive(Debug,Clone,Copy)] |
|
|
|
|
pub enum Move { |
|
|
|
|
InBoard(u8, u8), |
|
|
|
|
Enter(u8), |
|
|
|
|
BearOff(u8) |
|
|
|
|
InBoard(/* from */ u8, /* to */ u8), |
|
|
|
|
Enter(/* pos */ u8), |
|
|
|
|
BearOff(/* pos */ u8) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Debug)] |
|
|
|
@ -283,7 +284,7 @@ impl State { |
|
|
|
|
pub fn start(turn : Option<Color>) -> Self { |
|
|
|
|
// The state is laid out for the white player
|
|
|
|
|
let mut state = State { |
|
|
|
|
move_number: 0, |
|
|
|
|
turn_number: 0, |
|
|
|
|
turn: Color::White, |
|
|
|
|
board: Board([ |
|
|
|
|
// 1 .. 12 (bottom row, right to left)
|
|
|
|
@ -325,10 +326,14 @@ impl State { |
|
|
|
|
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.move_number, self.turn) |
|
|
|
|
(self.turn_number, self.turn) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Get reference to own player struct
|
|
|
|
|
fn player(&self) -> &Player { |
|
|
|
|
match self.turn { |
|
|
|
|
Color::Black => &self.black, |
|
|
|
@ -336,6 +341,7 @@ impl State { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Get mutable reference to own player struct
|
|
|
|
|
fn player_mut(&mut self) -> &mut Player { |
|
|
|
|
match self.turn { |
|
|
|
|
Color::Black => &mut self.black, |
|
|
|
@ -343,6 +349,7 @@ impl State { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Get reference to the other player's struct
|
|
|
|
|
fn other(&self) -> &Player { |
|
|
|
|
match self.turn { |
|
|
|
|
Color::Black => &self.white, |
|
|
|
@ -350,6 +357,7 @@ impl State { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Get mutable reference to the other player's struct
|
|
|
|
|
fn other_mut(&mut self) -> &mut Player { |
|
|
|
|
match self.turn { |
|
|
|
|
Color::Black => &mut self.white, |
|
|
|
@ -357,19 +365,21 @@ impl State { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 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<Self, Error> { |
|
|
|
|
if turn != self.turn { |
|
|
|
|
return Err(Error::NotYourTurn); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
print!("{} plays move: ", turn); |
|
|
|
|
// print!("{} plays move: ", turn);
|
|
|
|
|
|
|
|
|
|
let mut next = self.clone(); |
|
|
|
|
|
|
|
|
|
match mv { |
|
|
|
|
Move::InBoard(from, to) => { |
|
|
|
|
println!("In-board {} -> {}", from, to); |
|
|
|
|
// println!("In-board {} -> {}", from, to);
|
|
|
|
|
|
|
|
|
|
if (self.turn == Color::White && to >= from) || (self.turn == Color::Black && to <= from) { |
|
|
|
|
return Err(Error::MalformedMove); |
|
|
|
@ -397,7 +407,7 @@ impl State { |
|
|
|
|
|
|
|
|
|
if let Some(c) = old_color { |
|
|
|
|
if c != self.turn { |
|
|
|
|
println!("{} stone at position {} is hit.", c, to); |
|
|
|
|
// println!("{} stone at position {} is hit.", c, to);
|
|
|
|
|
|
|
|
|
|
// hit opposite color
|
|
|
|
|
next.other_mut().to_place += 1; |
|
|
|
@ -405,7 +415,7 @@ impl State { |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
Move::Enter(pos) => { |
|
|
|
|
println!("Enter -> {}", pos); |
|
|
|
|
// println!("Enter -> {}", pos);
|
|
|
|
|
|
|
|
|
|
if self.player().to_place == 0 { |
|
|
|
|
return Err(Error::NothingToPlace); |
|
|
|
@ -436,14 +446,14 @@ impl State { |
|
|
|
|
|
|
|
|
|
if let Some(c) = old_color { |
|
|
|
|
if c != self.turn { |
|
|
|
|
println!("{} stone at position {} is hit.", c, pos); |
|
|
|
|
// println!("{} stone at position {} is hit.", c, pos);
|
|
|
|
|
// hit opposite color
|
|
|
|
|
next.other_mut().to_place += 1; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
Move::BearOff(pos) => { |
|
|
|
|
println!("Bear off -> {}", pos); |
|
|
|
|
// println!("Bear off -> {}", pos);
|
|
|
|
|
|
|
|
|
|
if (self.turn == Color::White && pos > HOME_MAX) || (self.turn == Color::Black && pos < 18) { |
|
|
|
|
return Err(Error::MalformedMove); |
|
|
|
@ -469,25 +479,128 @@ impl State { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
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<Move> { |
|
|
|
|
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 { |
|
|
|
@ -505,11 +618,17 @@ impl Display for State { |
|
|
|
|
self.player().to_place |
|
|
|
|
)?; |
|
|
|
|
|
|
|
|
|
f.write_str("White <- ")?; |
|
|
|
|
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)?; |
|
|
|
|
} |
|
|
|
|
f.write_str(" -> Black\n")?; |
|
|
|
|
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 { |
|
|
|
|