mirror of
https://github.com/Noratrieb/minmax.git
synced 2026-01-14 15:25:08 +01:00
minmax
This commit is contained in:
parent
5c95aa7536
commit
7f3a0e5ad2
3 changed files with 127 additions and 3 deletions
|
|
@ -1,14 +1,17 @@
|
||||||
#![feature(never_type, try_trait_v2)]
|
#![feature(never_type, try_trait_v2, return_position_impl_trait_in_trait)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
pub mod connect4;
|
pub mod connect4;
|
||||||
|
mod minmax;
|
||||||
pub mod tic_tac_toe;
|
pub mod tic_tac_toe;
|
||||||
|
|
||||||
mod player;
|
mod player;
|
||||||
|
|
||||||
|
use self::minmax::GameBoard;
|
||||||
pub use player::{Player, State};
|
pub use player::{Player, State};
|
||||||
|
|
||||||
pub trait Game {
|
pub trait Game {
|
||||||
type Board;
|
type Board: GameBoard;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait GamePlayer<G: Game>: Default {
|
pub trait GamePlayer<G: Game>: Default {
|
||||||
|
|
|
||||||
93
src/minmax.rs
Normal file
93
src/minmax.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
use std::ops::Neg;
|
||||||
|
|
||||||
|
use crate::{Game, GamePlayer, Player, State};
|
||||||
|
|
||||||
|
pub trait GameBoard {
|
||||||
|
type Move: Copy;
|
||||||
|
|
||||||
|
fn possible_moves(&self) -> impl Iterator<Item = Self::Move>;
|
||||||
|
|
||||||
|
fn result(&self) -> State;
|
||||||
|
|
||||||
|
fn make_move(&mut self, position: Self::Move, player: Player);
|
||||||
|
|
||||||
|
fn undo_move(&mut self, position: Self::Move);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Score(i8);
|
||||||
|
|
||||||
|
impl Score {
|
||||||
|
const LOST: Self = Self(i8::MIN);
|
||||||
|
const TIE: Self = Self(0);
|
||||||
|
const WON: Self = Self(i8::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neg for Score {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Self(-self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PerfectPlayer<B: GameBoard> {
|
||||||
|
best_move: Option<B::Move>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: GameBoard> Default for PerfectPlayer<B> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: GameBoard> PerfectPlayer<B> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { best_move: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn minmax(&mut self, board: &mut B, player: Player, depth: usize) -> Score {
|
||||||
|
if depth < 2 {
|
||||||
|
//print!("{board}{}| playing {player}: ", " ".repeat(depth));
|
||||||
|
}
|
||||||
|
match board.result() {
|
||||||
|
State::Winner(winner) => {
|
||||||
|
if winner == player {
|
||||||
|
Score::WON
|
||||||
|
} else {
|
||||||
|
Score::LOST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Draw => Score::TIE,
|
||||||
|
State::InProgress => {
|
||||||
|
let mut max_value = Score::LOST;
|
||||||
|
|
||||||
|
for pos in board.possible_moves() {
|
||||||
|
board.make_move(pos, player);
|
||||||
|
let value = -self.minmax(board, player.opponent(), depth + 1);
|
||||||
|
|
||||||
|
board.undo_move(pos);
|
||||||
|
|
||||||
|
if value > max_value {
|
||||||
|
max_value = value;
|
||||||
|
if depth == 0 {
|
||||||
|
self.best_move = Some(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
max_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<G: Game> GamePlayer<G> for PerfectPlayer<G::Board> {
|
||||||
|
fn next_move(&mut self, board: &mut G::Board, this_player: Player) {
|
||||||
|
self.best_move = None;
|
||||||
|
self.minmax(board, this_player, 0);
|
||||||
|
|
||||||
|
board.make_move(self.best_move.expect("could not make move"), this_player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt::{Display, Write};
|
use std::fmt::{Display, Write};
|
||||||
|
|
||||||
use crate::{Player, State};
|
use crate::{minmax::GameBoard, Player, State};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Board(u32);
|
pub struct Board(u32);
|
||||||
|
|
@ -113,6 +113,34 @@ impl Display for Board {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GameBoard for Board {
|
||||||
|
type Move = usize;
|
||||||
|
|
||||||
|
fn possible_moves(&self) -> impl Iterator<Item = Self::Move> {
|
||||||
|
debug_assert!(
|
||||||
|
!self.iter().all(|x| x.is_some()),
|
||||||
|
"the board is full but state is InProgress"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, position)| position.is_none())
|
||||||
|
.map(|(pos, _)| pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn result(&self) -> State {
|
||||||
|
Board::result(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_move(&mut self, position: Self::Move, player: Player) {
|
||||||
|
self.set(position, Some(player));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn undo_move(&mut self, position: Self::Move) {
|
||||||
|
self.set(position, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Board, Player};
|
use super::{Board, Player};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue