Implement minmax that is ugly as fuck
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user