[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; | ||||
| 
 | ||||
| #[derive(Parser, Debug)] | ||||
|  | @ -10,38 +10,11 @@ struct Arguments { | |||
|     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<()> { | ||||
|     let args = Arguments::parse(); | ||||
|     let depth = args.depth; | ||||
| 
 | ||||
|     println!("Searching to depth {depth}"); | ||||
|     println!("depth={depth}"); | ||||
| 
 | ||||
|     let mut position = if let Some(fen) = args.fen { | ||||
|         Position::from_fen_str(&fen)? | ||||
|  | @ -51,7 +24,7 @@ fn main() -> anyhow::Result<()> { | |||
| 
 | ||||
|     let nodes_searched = position.perft(depth); | ||||
| 
 | ||||
|     println!("Nodes searched: {nodes_searched}"); | ||||
|     println!("nodes={nodes_searched}"); | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
|  |  | |||
|  | @ -9,5 +9,6 @@ pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy}; | |||
| pub use chessfriend_moves::{GeneratedMove, ValidateMove}; | ||||
| pub use position::Position; | ||||
| 
 | ||||
| pub mod perft; | ||||
| #[macro_use] | ||||
| 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 { | ||||
|     /// 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
 | ||||
|     ///
 | ||||
|     /// 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)?; | ||||
| 
 | ||||
|         if let Some(captured_piece) = record.captured_piece { | ||||
|             self.captures.push(record.color, captured_piece); | ||||
|         } | ||||
| 
 | ||||
|         if let Some(hash) = self.board.zobrist_hash() { | ||||
|             // TODO: If the hash already exists here, it's a duplicate position
 | ||||
|             // and this move results in a draw. Find a way to indicate that,
 | ||||
|             // in either Board or Position.
 | ||||
|             self.boards_seen.insert(hash); | ||||
|         } | ||||
|         let has_seen = if let Some(hash) = self.board.zobrist_hash() { | ||||
|             // HashSet::insert() returns true if the value does not exist in the
 | ||||
|             // set when it's called.
 | ||||
|             !self.boards_seen.insert(hash) | ||||
|         } else { | ||||
|             false | ||||
|         }; | ||||
| 
 | ||||
|         self.moves.push(record.clone()); | ||||
| 
 | ||||
|         Ok(()) | ||||
|         Ok(has_seen) | ||||
|     } | ||||
| 
 | ||||
|     /// 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