From e5e066bdccea73017e6359c8c67ca5ac0d5b1216 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Mon, 8 Aug 2022 16:49:58 +0000 Subject: [PATCH] proper minimax --- wasm/o-x-rust/Cargo.lock | 2 +- wasm/o-x-rust/Cargo.toml | 2 +- wasm/o-x-rust/src/lib.rs | 88 +++++++++++++++++++++++++++++----------- 3 files changed, 67 insertions(+), 25 deletions(-) diff --git a/wasm/o-x-rust/Cargo.lock b/wasm/o-x-rust/Cargo.lock index ea9afe9..b9fd77a 100644 --- a/wasm/o-x-rust/Cargo.lock +++ b/wasm/o-x-rust/Cargo.lock @@ -25,7 +25,7 @@ dependencies = [ [[package]] name = "o-x-rust" -version = "0.0.8" +version = "0.0.9" dependencies = [ "wasm-bindgen", ] diff --git a/wasm/o-x-rust/Cargo.toml b/wasm/o-x-rust/Cargo.toml index 081c12b..cad6eaf 100644 --- a/wasm/o-x-rust/Cargo.toml +++ b/wasm/o-x-rust/Cargo.toml @@ -4,7 +4,7 @@ description = "Noughts and crosses WASM algorithms" repository = "https://git.koval.net/cyclane/game-algorithms/src/branch/main/wasm/o-x-rust" license = "GNU GPLv3" readme = "README.md" -version = "0.0.8" +version = "0.0.9" edition = "2021" [lib] diff --git a/wasm/o-x-rust/src/lib.rs b/wasm/o-x-rust/src/lib.rs index a3ef908..acc40cf 100644 --- a/wasm/o-x-rust/src/lib.rs +++ b/wasm/o-x-rust/src/lib.rs @@ -35,7 +35,7 @@ pub fn get_turn(me: u8, other: u8, first: bool, empty: usize) -> u8 { } #[wasm_bindgen] -pub fn o0_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { +pub fn sa_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { let winner = find_winner(board); if winner != 0 { return if winner == me { 1.0 } else { -1.0 }; @@ -52,20 +52,20 @@ pub fn o0_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { let copy = &mut [0u8; 9]; copy.copy_from_slice(board); copy[i] = get_turn(me, other, first, empty); - score += o0_get_score(me, other, first, copy); + score += sa_get_score(me, other, first, copy); } score } #[wasm_bindgen] -pub fn o1d_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { - let (outcomes, score) = o1_sub_get_score(me, other, first, board, 2, 2, 1); +pub fn sa_rd_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { + let (outcomes, score) = sa_r_sub_get_score(me, other, first, board, 2, 2, 1); score as f64 / outcomes as f64 } #[wasm_bindgen] -pub fn o1nd_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { - let (outcomes, score) = o1_sub_get_score(me, other, first, board, 1, 1, 0); +pub fn sa_r_d_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { + let (outcomes, score) = sa_r_sub_get_score(me, other, first, board, 1, 1, 0); score as f64 / outcomes as f64 } @@ -74,7 +74,7 @@ pub fn o1nd_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { // w: Win winning outcome score // d: Draw winning outcome score // (loose = 0) -pub fn o1_sub_get_score(me: u8, other: u8, first: bool, board: &[u8], m: i32, w: i32, d: i32) -> (i32, i32) { +pub fn sa_r_sub_get_score(me: u8, other: u8, first: bool, board: &[u8], m: i32, w: i32, d: i32) -> (i32, i32) { let winner = find_winner(board); if winner != 0 { return if winner == me { (m, w) } else { (m, 0) }; @@ -92,33 +92,75 @@ pub fn o1_sub_get_score(me: u8, other: u8, first: bool, board: &[u8], m: i32, w: let copy = &mut [0u8; 9]; copy.copy_from_slice(board); copy[i] = get_turn(me, other, first, empty); - let (sub_outcomes, sub_score) = o1_sub_get_score(me, other, first, copy, m, w, d); + let (sub_outcomes, sub_score) = sa_r_sub_get_score(me, other, first, copy, m, w, d); outcomes += sub_outcomes; score += sub_score; } (outcomes, score) } -// Optimization levels: o0, o1d, o1nd, o2 -// o0: minmax without optimizations -// o1d: minmax with ratio optimization including draws -// o1nd: minmax with ratio optimization excluding draws (draw = loose) #[wasm_bindgen] -pub fn get_score(me: u8, other: u8, first: bool, board: &[u8], optimization_level: &str) -> f64 { - match optimization_level { - "o0" => o0_get_score(me, other, first, board), - "o1d" => o1d_get_score(me, other, first, board), - "o1nd" => o1nd_get_score(me, other, first, board), +pub fn mm_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { + mm_sub_get_score(me, other, first, board, 10.0, 0.0, 0.0) +} + +#[wasm_bindgen] +pub fn mm_d_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { + mm_sub_get_score(me, other, first, board, 10.0, 0.0, 1.0) +} + +// m = score (without depth) multiplier +// dm = depth multiplier +pub fn mm_sub_get_score(me: u8, other: u8, first: bool, board: &[u8], m: f64, depth: f64, dm: f64) -> f64 { + let winner = find_winner(board); + if winner != 0 { + return if winner == me { m - depth*dm } else { -m + depth*dm }; + } + let empty = count_empty(board); + if empty == 0 { + return 0.0; + } + let turn = get_turn(me, other, first, empty); + let mut best = if turn == me { f64::MIN } else { f64::MAX }; + for i in 0..9 { + if board[i] != 0 { + continue; + } + let copy = &mut [0u8; 9]; + copy.copy_from_slice(board); + copy[i] = get_turn(me, other, first, empty); + let value = mm_sub_get_score(me, other, first, copy, m, depth + 1.0, dm); + best = if turn == me { best.max(value) } else { best.min(value) }; + } + best +} + +// Algorithms: +// sa: score adding without optimizations +// sa+rd: score adding with ratio optimization including draws +// sa+r-d: score adding with ratio optimization excluding draws (draw = loose) +// mm: minimax algorithm +// mm+d: minmax algorithm with depth +#[wasm_bindgen] +pub fn get_score(me: u8, other: u8, first: bool, board: &[u8], algorithm: &str) -> f64 { + match algorithm { + "sa" => sa_get_score(me, other, first, board), + "sa+rd" => sa_rd_get_score(me, other, first, board), + "sa+r-d" => sa_r_d_get_score(me, other, first, board), + "mm" => mm_get_score(me, other, first, board), + "mm+d" => mm_d_get_score(me, other, first, board), _ => 0.0 } } -// Optimization levels: o0, o1d, o1nd, o2 -// o0: minmax without optimizations -// o1d: minmax with ratio optimization including draws -// o1nd: minmax with ratio optimization excluding draws (draw = loose) +// Algorithms: +// sa: score adding without optimizations +// sa+rd: score adding with ratio optimization including draws +// sa+r-d: score adding with ratio optimization excluding draws (draw = loose) +// mm: minimax algorithm +// mm+d: minmax algorithm with depth #[wasm_bindgen] -pub fn predict(me: u8, other: u8, first: bool, board: &[u8], optimization_level: &str) -> usize { +pub fn predict(me: u8, other: u8, first: bool, board: &[u8], algorithm: &str) -> usize { let (mut max_p, mut max_s) = (0, f64::MIN); let empty = count_empty(board); if empty == 0 { @@ -131,7 +173,7 @@ pub fn predict(me: u8, other: u8, first: bool, board: &[u8], optimization_level: let copy = &mut [0u8; 9]; copy.copy_from_slice(board); copy[i] = get_turn(me, other, first, empty); - let score = get_score(me, other, first, copy, optimization_level); + let score = get_score(me, other, first, copy, algorithm); if score > max_s { (max_p, max_s) = (i, score); }