proper minimax
ci/woodpecker/push/tools-wasm-pack-plugin Pipeline was successful Details
ci/woodpecker/push/wasm-o-x-rust Pipeline was successful Details

This commit is contained in:
Gleb Koval 2022-08-08 16:49:58 +00:00
parent 881e595f46
commit e5e066bdcc
Signed by: cyclane
GPG Key ID: 15E168A8B332382C
3 changed files with 67 additions and 25 deletions

View File

@ -25,7 +25,7 @@ dependencies = [
[[package]] [[package]]
name = "o-x-rust" name = "o-x-rust"
version = "0.0.8" version = "0.0.9"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]

View File

@ -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" repository = "https://git.koval.net/cyclane/game-algorithms/src/branch/main/wasm/o-x-rust"
license = "GNU GPLv3" license = "GNU GPLv3"
readme = "README.md" readme = "README.md"
version = "0.0.8" version = "0.0.9"
edition = "2021" edition = "2021"
[lib] [lib]

View File

@ -35,7 +35,7 @@ pub fn get_turn(me: u8, other: u8, first: bool, empty: usize) -> u8 {
} }
#[wasm_bindgen] #[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); let winner = find_winner(board);
if winner != 0 { if winner != 0 {
return if winner == me { 1.0 } else { -1.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]; let copy = &mut [0u8; 9];
copy.copy_from_slice(board); copy.copy_from_slice(board);
copy[i] = get_turn(me, other, first, empty); 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 score
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn o1d_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { pub fn sa_rd_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); let (outcomes, score) = sa_r_sub_get_score(me, other, first, board, 2, 2, 1);
score as f64 / outcomes as f64 score as f64 / outcomes as f64
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn o1nd_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 { pub fn sa_r_d_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); let (outcomes, score) = sa_r_sub_get_score(me, other, first, board, 1, 1, 0);
score as f64 / outcomes as f64 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 // w: Win winning outcome score
// d: Draw winning outcome score // d: Draw winning outcome score
// (loose = 0) // (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); let winner = find_winner(board);
if winner != 0 { if winner != 0 {
return if winner == me { (m, w) } else { (m, 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]; let copy = &mut [0u8; 9];
copy.copy_from_slice(board); copy.copy_from_slice(board);
copy[i] = get_turn(me, other, first, empty); 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; outcomes += sub_outcomes;
score += sub_score; score += sub_score;
} }
(outcomes, 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] #[wasm_bindgen]
pub fn get_score(me: u8, other: u8, first: bool, board: &[u8], optimization_level: &str) -> f64 { pub fn mm_get_score(me: u8, other: u8, first: bool, board: &[u8]) -> f64 {
match optimization_level { mm_sub_get_score(me, other, first, board, 10.0, 0.0, 0.0)
"o0" => o0_get_score(me, other, first, board), }
"o1d" => o1d_get_score(me, other, first, board),
"o1nd" => o1nd_get_score(me, other, first, board), #[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 _ => 0.0
} }
} }
// Optimization levels: o0, o1d, o1nd, o2 // Algorithms:
// o0: minmax without optimizations // sa: score adding without optimizations
// o1d: minmax with ratio optimization including draws // sa+rd: score adding with ratio optimization including draws
// o1nd: minmax with ratio optimization excluding draws (draw = loose) // sa+r-d: score adding with ratio optimization excluding draws (draw = loose)
// mm: minimax algorithm
// mm+d: minmax algorithm with depth
#[wasm_bindgen] #[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 (mut max_p, mut max_s) = (0, f64::MIN);
let empty = count_empty(board); let empty = count_empty(board);
if empty == 0 { 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]; let copy = &mut [0u8; 9];
copy.copy_from_slice(board); copy.copy_from_slice(board);
copy[i] = get_turn(me, other, first, empty); 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 { if score > max_s {
(max_p, max_s) = (i, score); (max_p, max_s) = (i, score);
} }