extern crate minimax;
use crate::minimax::{Game, Strategy};
use std::fmt;
#[derive(Copy, Clone)]
struct Board {
pits: [[u8; 7]; 2],
skipped: bool,
to_move: bool,
}
impl Default for Board {
fn default() -> Board {
Board { pits: [[0, 4, 4, 4, 4, 4, 4]; 2], skipped: false, to_move: false }
}
}
type Move = u8;
struct Mancala;
impl minimax::Game for Mancala {
type S = Board;
type M = Move;
fn generate_moves(board: &Board, moves: &mut Vec<Move>) -> Option<minimax::Winner> {
if let Some(winner) = Self::get_winner(board) {
return Some(winner);
}
if board.skipped {
moves.push(0);
return None;
}
for i in 1..7 {
if board.pits[board.to_move as usize][i] > 0 {
moves.push(i as Move);
}
}
None
}
fn apply(board: &mut Board, m: Move) -> Option<Board> {
let mut board = board.clone();
if board.skipped {
board.skipped = false;
board.to_move = !board.to_move;
return Some(board);
}
let mut player = board.to_move as usize;
let mut i = m as usize;
let mut stones = board.pits[player][i];
board.pits[player][i] = 0;
while stones > 0 {
if player == board.to_move as usize && i == 0 {
i = 6;
player ^= 1;
} else if player != board.to_move as usize && i == 1 {
i = 6;
player ^= 1;
} else {
i -= 1;
}
board.pits[player][i] += 1;
stones -= 1;
}
if player == board.to_move as usize {
if i == 0 {
board.skipped = true;
} else if board.pits[player][i] == 1 {
let captured = board.pits[player][i] + board.pits[player ^ 1][7 - i];
board.pits[player][i] = 0;
board.pits[player ^ 1][7 - i] = 0;
board.pits[player][0] += captured;
}
}
board.to_move = !board.to_move;
Some(board)
}
fn get_winner(board: &Board) -> Option<minimax::Winner> {
if board.pits[0][1..].iter().sum::<u8>() == 0 || board.pits[1][1..].iter().sum::<u8>() == 0
{
let to_move_total = board.pits[board.to_move as usize].iter().sum::<u8>();
Some(if to_move_total == 24 {
minimax::Winner::Draw
} else if to_move_total > 24 {
minimax::Winner::PlayerToMove
} else {
minimax::Winner::PlayerJustMoved
})
} else {
None
}
}
fn zobrist_hash(board: &Board) -> u64 {
let mut hash = board.to_move as u64;
for i in 0..7 {
hash ^= HASHES[i].wrapping_mul(board.pits[0][i] as u64);
hash ^= HASHES[i + 7].wrapping_mul(board.pits[1][i] as u64);
}
hash
}
fn null_move(_: &Board) -> Option<Move> {
Some(0)
}
fn notation(_: &Board, m: Move) -> Option<String> {
Some(if m == 0 { "skipped".to_owned() } else { format!("pit {}", m) })
}
fn table_index(m: Move) -> u16 {
m as u16
}
fn max_table_index() -> u16 {
6
}
}
impl fmt::Display for Board {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "+-----------------------+\n| |")?;
for pit in &self.pits[1][1..] {
write!(f, "{:>2}|", pit)?;
}
write!(f, " |\n+{:>2}+--+--+--+--+--+--+{:>2}+\n| ", self.pits[1][0], self.pits[0][0])?;
for pit in self.pits[0][1..].iter().rev() {
write!(f, "|{:>2}", pit)?;
}
write!(f, "| |\n+-----------------------+\n")
}
}
#[derive(Default)]
struct Evaluator;
impl minimax::Evaluator for Evaluator {
type G = Mancala;
fn evaluate(&self, board: &Board) -> minimax::Evaluation {
board.pits[board.to_move as usize].iter().sum::<u8>() as minimax::Evaluation - 24
}
}
fn main() {
let mut board = Board::default();
let opts = minimax::IterativeOptions::new().verbose();
let mut strategy = minimax::IterativeSearch::new(Evaluator::default(), opts);
strategy.set_timeout(std::time::Duration::from_secs(1));
while Mancala::get_winner(&board).is_none() {
println!("{}", board);
match strategy.choose_move(&board) {
Some(m) => board = Mancala::apply(&mut board, m).unwrap(),
None => break,
}
}
println!("Winner player {:?}", board.to_move as u8 + 1);
}
const HASHES: [u64; 14] = [
0x73399349585d196e,
0xe512dc15f0da3dd1,
0x4fbc1b81c6197db2,
0x16b5034810111a66,
0xa9a9d0183e33c311,
0xbb9d7bdea0dad2d6,
0x089d9205c11ca5c7,
0x18d9db91aa689617,
0x1336123120681e34,
0xc902e6c0bd6ef6bf,
0x16985ba0916238c1,
0x6144c3f2ab9f6dc4,
0xf24b4842de919a02,
0xdd6dd35ba0c150a1,
];