This commit is contained in:
nora 2022-11-21 15:46:49 +01:00
parent 5c95aa7536
commit 7f3a0e5ad2
No known key found for this signature in database
3 changed files with 127 additions and 3 deletions

View file

@ -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
View 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);
}
}

View file

@ -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};