[position, perft] Move Perft into the position crate
Move the Perft trait into the position crate, and let the perft binary call into that. Amend Position::make_move to return a bool in the Ok case that indicates whether the position has been seen before. Use this to decide whether to continue recursing during the Perft run. I haven't seen that this makes a difference in the counts returned by Perft yet.
This commit is contained in:
parent
f0b6cb5f08
commit
7744dd06f0
4 changed files with 82 additions and 39 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use chessfriend_position::{fen::FromFenStr, GeneratedMove, Position, ValidateMove};
|
use chessfriend_position::{fen::FromFenStr, perft::Perft, Position};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
|
@ -10,38 +10,11 @@ struct Arguments {
|
||||||
fen: Option<String>,
|
fen: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Perft {
|
|
||||||
fn perft(&mut self, depth: usize) -> u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Perft for Position {
|
|
||||||
fn perft(&mut self, depth: usize) -> u64 {
|
|
||||||
if depth == 0 {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut nodes_counted: u64 = 0;
|
|
||||||
|
|
||||||
let legal_moves: Vec<GeneratedMove> = self.all_legal_moves(None).collect();
|
|
||||||
|
|
||||||
for generated_ply in legal_moves {
|
|
||||||
self.make_move(generated_ply.into(), ValidateMove::No)
|
|
||||||
.expect("unable to make generated move");
|
|
||||||
|
|
||||||
nodes_counted += self.perft(depth - 1);
|
|
||||||
|
|
||||||
self.unmake_last_move().expect("unable to unmake last move");
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes_counted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let args = Arguments::parse();
|
let args = Arguments::parse();
|
||||||
let depth = args.depth;
|
let depth = args.depth;
|
||||||
|
|
||||||
println!("Searching to depth {depth}");
|
println!("depth={depth}");
|
||||||
|
|
||||||
let mut position = if let Some(fen) = args.fen {
|
let mut position = if let Some(fen) = args.fen {
|
||||||
Position::from_fen_str(&fen)?
|
Position::from_fen_str(&fen)?
|
||||||
|
|
@ -51,7 +24,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
let nodes_searched = position.perft(depth);
|
let nodes_searched = position.perft(depth);
|
||||||
|
|
||||||
println!("Nodes searched: {nodes_searched}");
|
println!("nodes={nodes_searched}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,6 @@ pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy};
|
||||||
pub use chessfriend_moves::{GeneratedMove, ValidateMove};
|
pub use chessfriend_moves::{GeneratedMove, ValidateMove};
|
||||||
pub use position::Position;
|
pub use position::Position;
|
||||||
|
|
||||||
|
pub mod perft;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod testing;
|
pub mod testing;
|
||||||
|
|
|
||||||
67
position/src/perft.rs
Normal file
67
position/src/perft.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::{GeneratedMove, Position, ValidateMove};
|
||||||
|
|
||||||
|
pub trait Perft {
|
||||||
|
fn perft(&mut self, depth: usize) -> u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Perft for Position {
|
||||||
|
fn perft(&mut self, depth: usize) -> u64 {
|
||||||
|
if depth == 0 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut total_nodes_counted = 0u64;
|
||||||
|
|
||||||
|
let legal_moves: Vec<GeneratedMove> = self.all_legal_moves(None).collect();
|
||||||
|
|
||||||
|
for generated_ply in legal_moves {
|
||||||
|
let ply = generated_ply.ply();
|
||||||
|
|
||||||
|
let has_seen_position = self
|
||||||
|
.make_move(ply, ValidateMove::No)
|
||||||
|
.expect("unable to make generated move");
|
||||||
|
|
||||||
|
// Do not recursive into trees where board positions repeat.
|
||||||
|
let nodes_counted = if has_seen_position {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
self.perft(depth - 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
total_nodes_counted += nodes_counted;
|
||||||
|
|
||||||
|
self.unmake_last_move().expect("unable to unmake last move");
|
||||||
|
}
|
||||||
|
|
||||||
|
total_nodes_counted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Position {
|
||||||
|
fn perft_recursive(&mut self, depth: usize) -> u64 {
|
||||||
|
if depth == 0 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut total_nodes_counted: u64 = 0;
|
||||||
|
|
||||||
|
let legal_moves: Vec<GeneratedMove> = self.all_legal_moves(None).collect();
|
||||||
|
|
||||||
|
for generated_ply in legal_moves {
|
||||||
|
let ply = generated_ply.ply();
|
||||||
|
|
||||||
|
self.make_move(ply, ValidateMove::No)
|
||||||
|
.expect("unable to make generated move");
|
||||||
|
|
||||||
|
let nodes_counted = self.perft_recursive(depth - 1);
|
||||||
|
|
||||||
|
total_nodes_counted += nodes_counted;
|
||||||
|
|
||||||
|
self.unmake_last_move().expect("unable to unmake last move");
|
||||||
|
}
|
||||||
|
|
||||||
|
total_nodes_counted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -169,29 +169,31 @@ impl Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Position {
|
impl Position {
|
||||||
/// Make a move on the board and record it in the move list.
|
/// Make a move on the board and record it in the move list. Returns `true`
|
||||||
|
/// if the board position has been seen before (i.e. it's a repetition).
|
||||||
///
|
///
|
||||||
/// ## Errors
|
/// ## Errors
|
||||||
///
|
///
|
||||||
/// Returns one of [`MakeMoveError`] if the move cannot be made.
|
/// Returns one of [`MakeMoveError`] if the move cannot be made.
|
||||||
///
|
///
|
||||||
pub fn make_move(&mut self, ply: Move, validate: ValidateMove) -> Result<(), MakeMoveError> {
|
pub fn make_move(&mut self, ply: Move, validate: ValidateMove) -> Result<bool, MakeMoveError> {
|
||||||
let record = self.board.make_move(ply, validate)?;
|
let record = self.board.make_move(ply, validate)?;
|
||||||
|
|
||||||
if let Some(captured_piece) = record.captured_piece {
|
if let Some(captured_piece) = record.captured_piece {
|
||||||
self.captures.push(record.color, captured_piece);
|
self.captures.push(record.color, captured_piece);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hash) = self.board.zobrist_hash() {
|
let has_seen = if let Some(hash) = self.board.zobrist_hash() {
|
||||||
// TODO: If the hash already exists here, it's a duplicate position
|
// HashSet::insert() returns true if the value does not exist in the
|
||||||
// and this move results in a draw. Find a way to indicate that,
|
// set when it's called.
|
||||||
// in either Board or Position.
|
!self.boards_seen.insert(hash)
|
||||||
self.boards_seen.insert(hash);
|
} else {
|
||||||
}
|
false
|
||||||
|
};
|
||||||
|
|
||||||
self.moves.push(record.clone());
|
self.moves.push(record.clone());
|
||||||
|
|
||||||
Ok(())
|
Ok(has_seen)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unmake the last move made on the board and remove its record from the
|
/// Unmake the last move made on the board and remove its record from the
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue