mirror of
https://github.com/Noratrieb/minmax.git
synced 2026-01-16 16:25:07 +01:00
move around
This commit is contained in:
parent
6c3bf8dddb
commit
5c95aa7536
7 changed files with 144 additions and 110 deletions
109
src/lib.rs
109
src/lib.rs
|
|
@ -1,113 +1,16 @@
|
||||||
#![feature(never_type, try_trait_v2)]
|
#![feature(never_type, try_trait_v2)]
|
||||||
|
|
||||||
mod board;
|
|
||||||
pub mod connect4;
|
pub mod connect4;
|
||||||
mod game;
|
pub mod tic_tac_toe;
|
||||||
mod perfect;
|
|
||||||
mod player;
|
mod player;
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
pub use board::Board;
|
|
||||||
pub use perfect::PerfectPlayer;
|
|
||||||
pub use player::{Player, State};
|
pub use player::{Player, State};
|
||||||
|
|
||||||
pub trait GamePlayer: Default {
|
pub trait Game {
|
||||||
fn next_move(&mut self, board: &mut Board, this_player: Player);
|
type Board;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
pub trait GamePlayer<G: Game>: Default {
|
||||||
pub struct GreedyPlayer;
|
fn next_move(&mut self, board: &mut G::Board, this_player: Player);
|
||||||
|
|
||||||
impl GamePlayer for GreedyPlayer {
|
|
||||||
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
|
||||||
let first_free = board.iter().position(|p| p.is_none()).unwrap();
|
|
||||||
board.set(first_free, Some(this_player));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct HumanPlayer;
|
|
||||||
|
|
||||||
impl GamePlayer for HumanPlayer {
|
|
||||||
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
|
||||||
loop {
|
|
||||||
print!("{board}where to put the next {this_player}? (0-8): ");
|
|
||||||
|
|
||||||
std::io::stdout().flush().unwrap();
|
|
||||||
let mut buf = String::new();
|
|
||||||
std::io::stdin().read_line(&mut buf).unwrap();
|
|
||||||
|
|
||||||
match buf.trim().parse() {
|
|
||||||
Ok(number) if number < 9 => match board.get(number) {
|
|
||||||
None => {
|
|
||||||
board.set(number, Some(this_player));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Some(_) => {
|
|
||||||
println!("Field is occupied already.")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Ok(_) | Err(_) => {
|
|
||||||
println!("Invalid input.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct RandomPlayer;
|
|
||||||
|
|
||||||
fn fun_random() -> u64 {
|
|
||||||
use std::hash::{BuildHasher, Hasher};
|
|
||||||
std::collections::hash_map::RandomState::new()
|
|
||||||
.build_hasher()
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GamePlayer for RandomPlayer {
|
|
||||||
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
|
||||||
loop {
|
|
||||||
let next = (fun_random() % 9) as usize;
|
|
||||||
match board.get(next) {
|
|
||||||
Some(_) => {}
|
|
||||||
None => {
|
|
||||||
board.set(next, Some(this_player));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{Board, GamePlayer, GreedyPlayer, PerfectPlayer, Player, RandomPlayer};
|
|
||||||
|
|
||||||
fn assert_win_ratio<X: GamePlayer, O: GamePlayer>(runs: u64, x_win_ratio: f64) {
|
|
||||||
let mut results = [0u64, 0, 0];
|
|
||||||
|
|
||||||
for _ in 0..runs {
|
|
||||||
let result = Board::default_play::<X, O>();
|
|
||||||
let idx = Player::as_u8(result);
|
|
||||||
results[idx as usize] += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let total = results.iter().copied().sum::<u64>();
|
|
||||||
|
|
||||||
let ratio = (total as f64) / (results[0] as f64);
|
|
||||||
println!("{ratio} >= {x_win_ratio}");
|
|
||||||
assert!(ratio >= x_win_ratio);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn perfect_always_beats_greedy() {
|
|
||||||
assert_win_ratio::<PerfectPlayer, GreedyPlayer>(20, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn perfect_beats_random() {
|
|
||||||
assert_win_ratio::<PerfectPlayer, RandomPlayer>(10, 0.95);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use minmax::{Board, GamePlayer, GreedyPlayer, HumanPlayer, PerfectPlayer, Player, RandomPlayer};
|
use minmax::{
|
||||||
|
tic_tac_toe::{Board, GreedyPlayer, HumanPlayer, PerfectPlayer, TicTacToe},
|
||||||
|
GamePlayer, Player,
|
||||||
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut results = [0, 0, 0];
|
let mut results = [0, 0, 0];
|
||||||
|
|
@ -25,7 +28,7 @@ fn main() {
|
||||||
println!("Completed in {}ms", time.as_millis());
|
println!("Completed in {}ms", time.as_millis());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn play_round<X: GamePlayer, O: GamePlayer>(print: bool) -> Option<Player> {
|
fn play_round<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>(print: bool) -> Option<Player> {
|
||||||
let mut board = Board::empty();
|
let mut board = Board::empty();
|
||||||
let result = board.play(&mut X::default(), &mut O::default());
|
let result = board.play(&mut X::default(), &mut O::default());
|
||||||
if print {
|
if print {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use crate::{Board, GamePlayer, Player, State};
|
use crate::{GamePlayer, Player, State};
|
||||||
|
|
||||||
|
use super::{board::Board, TicTacToe};
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
pub fn default_play<X: GamePlayer, O: GamePlayer>() -> Option<Player> {
|
pub fn default_play<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>() -> Option<Player> {
|
||||||
Self::empty().play(&mut X::default(), &mut O::default())
|
Self::empty().play(&mut X::default(), &mut O::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play<A: GamePlayer, B: GamePlayer>(&mut self, x: &mut A, o: &mut B) -> Option<Player> {
|
pub fn play<A: GamePlayer<TicTacToe>, B: GamePlayer<TicTacToe>>(&mut self, x: &mut A, o: &mut B) -> Option<Player> {
|
||||||
let mut current_player = Player::X;
|
let mut current_player = Player::X;
|
||||||
|
|
||||||
for _ in 0..9 {
|
for _ in 0..9 {
|
||||||
54
src/tic_tac_toe/mod.rs
Normal file
54
src/tic_tac_toe/mod.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
use crate::Game;
|
||||||
|
|
||||||
|
mod board;
|
||||||
|
mod game;
|
||||||
|
mod perfect;
|
||||||
|
mod player;
|
||||||
|
|
||||||
|
pub use {board::Board, perfect::PerfectPlayer, player::*};
|
||||||
|
|
||||||
|
pub struct TicTacToe;
|
||||||
|
|
||||||
|
impl Game for TicTacToe {
|
||||||
|
type Board = board::Board;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{tic_tac_toe::board::Board, GamePlayer, Player};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
perfect::PerfectPlayer,
|
||||||
|
player::{GreedyPlayer, RandomPlayer},
|
||||||
|
TicTacToe,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn assert_win_ratio<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>(
|
||||||
|
runs: u64,
|
||||||
|
x_win_ratio: f64,
|
||||||
|
) {
|
||||||
|
let mut results = [0u64, 0, 0];
|
||||||
|
|
||||||
|
for _ in 0..runs {
|
||||||
|
let result = Board::default_play::<X, O>();
|
||||||
|
let idx = Player::as_u8(result);
|
||||||
|
results[idx as usize] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let total = results.iter().copied().sum::<u64>();
|
||||||
|
|
||||||
|
let ratio = (total as f64) / (results[0] as f64);
|
||||||
|
println!("{ratio} >= {x_win_ratio}");
|
||||||
|
assert!(ratio >= x_win_ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn perfect_always_beats_greedy() {
|
||||||
|
assert_win_ratio::<PerfectPlayer, GreedyPlayer>(20, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn perfect_beats_random() {
|
||||||
|
assert_win_ratio::<PerfectPlayer, RandomPlayer>(10, 0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use std::ops::Neg;
|
use std::ops::Neg;
|
||||||
|
|
||||||
use crate::{Board, GamePlayer, Player, State};
|
use crate::{GamePlayer, Player, State};
|
||||||
|
|
||||||
|
use super::{board::Board, TicTacToe};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum Score {
|
enum Score {
|
||||||
|
|
@ -102,7 +104,7 @@ impl PerfectPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GamePlayer for PerfectPlayer {
|
impl GamePlayer<TicTacToe> for PerfectPlayer {
|
||||||
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
||||||
self.best_move = usize::MAX;
|
self.best_move = usize::MAX;
|
||||||
self.minmax(board, this_player, 0);
|
self.minmax(board, this_player, 0);
|
||||||
70
src/tic_tac_toe/player.rs
Normal file
70
src/tic_tac_toe/player.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::{GamePlayer, Player};
|
||||||
|
|
||||||
|
use super::{board::Board, TicTacToe};
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct GreedyPlayer;
|
||||||
|
|
||||||
|
impl GamePlayer<TicTacToe> for GreedyPlayer {
|
||||||
|
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
||||||
|
let first_free = board.iter().position(|p| p.is_none()).unwrap();
|
||||||
|
board.set(first_free, Some(this_player));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct HumanPlayer;
|
||||||
|
|
||||||
|
impl GamePlayer<TicTacToe> for HumanPlayer {
|
||||||
|
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
||||||
|
loop {
|
||||||
|
print!("{board}where to put the next {this_player}? (0-8): ");
|
||||||
|
|
||||||
|
std::io::stdout().flush().unwrap();
|
||||||
|
let mut buf = String::new();
|
||||||
|
std::io::stdin().read_line(&mut buf).unwrap();
|
||||||
|
|
||||||
|
match buf.trim().parse() {
|
||||||
|
Ok(number) if number < 9 => match board.get(number) {
|
||||||
|
None => {
|
||||||
|
board.set(number, Some(this_player));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
println!("Field is occupied already.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(_) | Err(_) => {
|
||||||
|
println!("Invalid input.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct RandomPlayer;
|
||||||
|
|
||||||
|
fn fun_random() -> u64 {
|
||||||
|
use std::hash::{BuildHasher, Hasher};
|
||||||
|
std::collections::hash_map::RandomState::new()
|
||||||
|
.build_hasher()
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GamePlayer<TicTacToe> for RandomPlayer {
|
||||||
|
fn next_move(&mut self, board: &mut Board, this_player: Player) {
|
||||||
|
loop {
|
||||||
|
let next = (fun_random() % 9) as usize;
|
||||||
|
match board.get(next) {
|
||||||
|
Some(_) => {}
|
||||||
|
None => {
|
||||||
|
board.set(next, Some(this_player));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue