Implement minmax that is ugly as fuck

This commit is contained in:
2025-03-06 21:45:15 +01:00
parent f4ac92ff89
commit 9f7fbc8ef9
3 changed files with 76 additions and 32 deletions

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Eq, PartialEq, Clone)] #[derive(Eq, PartialEq, Clone, Debug)]
pub enum Color { pub enum Color {
Black, Black,
White, White,
@@ -19,7 +19,7 @@ impl Color {
Color::White => Rank::_2, Color::White => Rank::_2,
} }
} }
fn other(&self) -> Color { pub fn other(&self) -> Color {
match self { match self {
Color::Black => Color::White, Color::Black => Color::White,
Color::White => Color::Black, Color::White => Color::Black,
@@ -27,7 +27,7 @@ impl Color {
} }
} }
#[derive(PartialEq, Eq, Hash, Clone)] #[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Position { pub struct Position {
pub file: File, pub file: File,
pub rank: Rank, pub rank: Rank,
@@ -64,7 +64,7 @@ impl Position {
} }
} }
#[derive(PartialEq, Eq, Hash, Clone, Copy)] #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub enum Rank { pub enum Rank {
_1, _1,
_2, _2,
@@ -76,7 +76,7 @@ pub enum Rank {
_8, _8,
} }
#[derive(PartialEq, Eq, Hash, Clone, Copy)] #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub enum File { pub enum File {
A, A,
B, B,

View File

@@ -10,13 +10,14 @@ impl board::PieceType {
} }
} }
#[derive(Debug)]
pub struct Move_ { pub struct Move_ {
pub source: board::Position, pub source: board::Position,
pub target: board::Position, pub target: board::Position,
} }
pub struct Engine { pub struct Engine {
board: board::Board, pub board: board::Board,
} }
impl Engine { impl Engine {
@@ -36,28 +37,27 @@ impl Engine {
pub fn set_state(&mut self, board: board::Board) { pub fn set_state(&mut self, board: board::Board) {
self.board = board; self.board = board;
} }
pub fn get_legal_moves( pub fn get_legal_moves<'a>(
&self, board: &'a board::Board,
position: &board::Position, position: &board::Position,
) -> Result<impl Iterator<Item = board::Position> + '_, ()> { ) -> Result<impl Iterator<Item = board::Position> + 'a, ()> {
Ok(self Ok(board.find_moves(position)?.chain(board.find_captures(position)?))
.board
.find_moves(position)?
.chain(self.board.find_captures(position)?))
} }
pub fn make_move( pub fn make_move(
&mut self, &mut self,
source: &board::Position, source: &board::Position,
target: board::Position, target: board::Position,
) -> Result<(), ()> { ) -> Result<(), ()> {
if !self.get_legal_moves(source)?.any(|pos| pos == target) { if !Engine::get_legal_moves(&self.board, source)?
.any(|pos| pos == target)
{
Err(()) Err(())
} else { } else {
// We checked that there is a piece at source in get_legal_moves // We checked that there is a piece at source in get_legal_moves
self.board.relocate(source, target) self.board.relocate(source, target)
} }
} }
pub fn evaluate_position(&self, board: &board::Board) -> f32 { pub fn evaluate_position(board: &board::Board) -> f32 {
board board
.iter() .iter()
.map(|(_, piece)| match piece.color { .map(|(_, piece)| match piece.color {
@@ -67,24 +67,29 @@ impl Engine {
.sum() .sum()
} }
pub fn evaluate_move( pub fn evaluate_move(
&self,
color: &board::Color,
move_: &Move_, move_: &Move_,
board: &board::Board,
color: &board::Color,
) -> Result<f32, ()> { ) -> Result<f32, ()> {
let mut board = self.board.clone(); let mut board = board.clone();
board.relocate(&move_.source, move_.target.clone())?; board.relocate(&move_.source, move_.target.clone())?;
Ok(match color { Ok(match color {
board::Color::White => 1.0, board::Color::White => 1.0,
board::Color::Black => -1.0, board::Color::Black => -1.0,
} * self.evaluate_position(&board)) } * Engine::evaluate_position(&board))
} }
pub fn choose_move(&self, color: &board::Color) -> Option<Move_> { fn minmax(
let all_moves = self depth: i8,
.board board: &board::Board,
color: &board::Color,
) -> Option<(f32, Move_)> {
eprintln!("Entering minmax for {} at depth {}", color, depth);
let all_moves = board
.iter() .iter()
.filter_map(|(source, piece)| { .filter_map(|(source, piece)| {
if &piece.color == color { if &piece.color == color {
if let Ok(targets) = self.get_legal_moves(source) { if let Ok(targets) = Engine::get_legal_moves(board, source)
{
Some(targets.map(|target| Move_ { Some(targets.map(|target| Move_ {
source: source.clone(), source: source.clone(),
target, target,
@@ -97,20 +102,56 @@ impl Engine {
} }
}) })
.flatten(); .flatten();
if depth == 0 {
Engine::best_immediate_move(board, color, all_moves)
} else if depth > 0 {
let best_given_opponent = all_moves.map(|move_| {
eprintln!("Finding opponent moves for {:?}", move_);
let mut board = board.clone();
board.relocate(&move_.source, move_.target.clone()).unwrap();
let result = if let Some((opponent_score, _)) =
Engine::minmax(depth - 1, &board, &color.other())
{
(-opponent_score, move_)
} else {
(0.0, move_)
};
eprintln!(
"Move {:?} got opponent minmax score {} for {:?}",
result.1, result.0, color
);
result
});
best_given_opponent
.max_by(|(s1, _), (s2, _)| s1.partial_cmp(s2).unwrap())
} else {
panic!();
}
}
fn best_immediate_move(
board: &board::Board,
color: &board::Color,
moves: impl Iterator<Item = Move_>,
) -> Option<(f32, Move_)> {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
Some( Some(
all_moves moves
.map(|move_| { .map(|move_| {
( let score = Engine::evaluate_move(&move_, board, color)
self.evaluate_move(color, &move_).unwrap() .unwrap()
+ rng.gen::<f32>() * 0.1, + rng.gen::<f32>() * 0.05;
move_, eprintln!(
) "Move {:?} got immediate score {} for {:?}",
move_, score, color
);
(score, move_)
}) })
.max_by(|(score1, _), (score2, _)| { .max_by(|(score1, _), (score2, _)| {
score1.partial_cmp(score2).unwrap() score1.partial_cmp(score2).unwrap()
})? })?,
.1,
) )
} }
pub fn choose_move(&self, color: &board::Color) -> Option<Move_> {
Some(Engine::minmax(1, &self.board, color)?.1)
}
} }

View File

@@ -219,7 +219,10 @@ impl Ui {
board::Position::parse(position_str).map_err(|_| { board::Position::parse(position_str).map_err(|_| {
format!("Error parsing position {}", position_str) format!("Error parsing position {}", position_str)
})?; })?;
match self.engine.get_legal_moves(&position) { match engine::Engine::get_legal_moves(
&self.engine.board,
&position,
) {
Err(_) => { Err(_) => {
let error = format!("No moves possible from {}", position); let error = format!("No moves possible from {}", position);
Err(error) Err(error)