use std::{collections::HashMap, sync::Arc};
use rand::{seq::SliceRandom, SeedableRng};
use rand_xorshift::XorShiftRng;
use shakmaty::{fen::Epd, Chess, EnPassantMode, Move, Outcome, Position};
use crate::{hash::zobrist_hash, polyglot::to_move, polyglotbook::PolyGlotBook, tr};
#[derive(Copy, Clone, Debug)]
pub enum MoveSelection {
BestMove,
UniformRandom,
WeightedRandom,
}
pub fn play_random_game(position: &mut Chess, initial_move: &Move) -> Outcome {
let mut rep = HashMap::<u64, u8>::new();
rep.insert(zobrist_hash(position), 1);
position.play_unchecked(initial_move);
let mut rng = XorShiftRng::from_entropy();
loop {
match position.outcome() {
Some(outcome) => return outcome,
None => {
if position.halfmoves() >= 50 {
return Outcome::Draw;
}
let key = zobrist_hash(position);
let val = rep.entry(key).and_modify(|v| *v += 1).or_insert(1);
if *val >= 3 {
return Outcome::Draw;
}
}
}
let legal_moves: Vec<Move> = position.legal_moves().as_slice().to_vec();
let random_move = legal_moves.choose(&mut rng).unwrap();
position.play_unchecked(random_move);
}
}
pub fn play_random_book_game(
position: &Chess,
book1: Arc<PolyGlotBook>,
book2: Arc<PolyGlotBook>,
move_selection: &MoveSelection,
prefer_irreversible: bool,
) -> Result<Outcome, Box<dyn std::error::Error>> {
let mut pos = position.clone();
let mut is_book1_turn = true;
let mut book1_miss = false;
let mut book2_miss = false;
let mut rng = XorShiftRng::from_entropy();
let mut rep = HashMap::<u64, u8>::new();
rep.insert(zobrist_hash(position), 1);
loop {
let book = if is_book1_turn { &book1 } else { &book2 };
let move_ = if (is_book1_turn && !book1_miss) || (!is_book1_turn && !book2_miss) {
match book.lookup_moves(zobrist_hash(&pos)) {
Some(mut entries) => {
let chosen = match move_selection {
MoveSelection::BestMove => {
entries.sort_by(|a, b| {
let aw = a.weight;
let bw = b.weight;
bw.cmp(&aw)
});
&entries[0]
}
MoveSelection::UniformRandom => entries.choose(&mut rng).unwrap(),
MoveSelection::WeightedRandom => {
entries.choose_weighted(&mut rng, |entry| {
u32::from(entry.weight).saturating_add(1)
})?
}
};
to_move(&pos, chosen.mov).ok_or_else(|| {
let epd = format!(
"{}",
Epd::from_position(pos.clone(), EnPassantMode::PseudoLegal)
);
std::io::Error::new(
std::io::ErrorKind::InvalidData,
tr!(
"Failed to convert the chosen entry `{:?}' in position `{}'.",
chosen,
epd
),
)
})?
}
_ => {
if is_book1_turn {
book1_miss = true;
} else {
book2_miss = true;
}
let legal_moves = pos.legal_moves().as_slice().to_vec();
let irreversible_moves: Option<Vec<_>> = if prefer_irreversible {
Some(
legal_moves
.iter()
.filter(|mov| pos.is_irreversible(mov))
.cloned()
.collect(),
)
} else {
None
};
if let Some(irreversible_moves) = &irreversible_moves {
if !irreversible_moves.is_empty() {
irreversible_moves.choose(&mut rng).unwrap().clone()
} else {
legal_moves.choose(&mut rng).unwrap().clone()
}
} else {
legal_moves.choose(&mut rng).unwrap().clone()
}
}
}
} else {
let legal_moves = pos.legal_moves().as_slice().to_vec();
legal_moves.choose(&mut rng).unwrap().clone()
};
pos.play_unchecked(&move_);
match position.outcome() {
Some(outcome) => return Ok(outcome),
None => {
if position.halfmoves() >= 50 {
return Ok(Outcome::Draw);
}
let key = zobrist_hash(position);
let val = rep.entry(key).and_modify(|v| *v += 1).or_insert(1);
if *val >= 3 {
return Ok(Outcome::Draw);
}
}
}
is_book1_turn = !is_book1_turn;
}
}