183 lines
4.9 KiB
Rust
183 lines
4.9 KiB
Rust
use wasm_bindgen::prelude::wasm_bindgen;
|
|
|
|
#[wasm_bindgen]
|
|
pub fn find_winner(board: &[u8]) -> u8 {
|
|
for i in 0..3 {
|
|
let h = i*3;
|
|
if board[h] != 0 && board[h] == board[h + 1] && board[h + 1] == board[h + 2] {
|
|
return board[h];
|
|
}
|
|
if board[i] != 0 && board[i] == board[i + 3] && board[i + 3] == board[i + 6] {
|
|
return board[i];
|
|
}
|
|
}
|
|
if board[0] != 0 && board[0] == board[4] && board[4] == board[8] {
|
|
return board[0];
|
|
}
|
|
if board[2] != 0 && board[2] == board[4] && board[4] == board[6] {
|
|
return board[2];
|
|
}
|
|
0
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn count_empty(board: &[u8]) -> usize {
|
|
board.iter().filter(|&n| *n == 0).count()
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn get_turn(me: u8, other: u8, first: bool, empty: usize) -> u8 {
|
|
if empty % 2 == if first { 1 } else { 0 } {
|
|
me
|
|
} else {
|
|
other
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
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 };
|
|
}
|
|
let empty = count_empty(board);
|
|
if empty == 0 {
|
|
return 0.0;
|
|
}
|
|
let mut score = 0.0;
|
|
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);
|
|
score += sa_get_score(me, other, first, copy);
|
|
}
|
|
score
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
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 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
|
|
}
|
|
|
|
// outcomes, winning outcomes
|
|
// m: Total outcomes multiplier
|
|
// w: Win winning outcome score
|
|
// d: Draw winning outcome score
|
|
// (loose = 0)
|
|
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) };
|
|
}
|
|
let empty = count_empty(board);
|
|
if empty == 0 {
|
|
return (m, d);
|
|
}
|
|
let mut score = 0;
|
|
let mut outcomes = 0;
|
|
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 (sub_outcomes, sub_score) = sa_r_sub_get_score(me, other, first, copy, m, w, d);
|
|
outcomes += sub_outcomes;
|
|
score += sub_score;
|
|
}
|
|
(outcomes, score)
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
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
|
|
}
|
|
}
|
|
|
|
// 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], algorithm: &str) -> usize {
|
|
let (mut max_p, mut max_s) = (0, f64::MIN);
|
|
let empty = count_empty(board);
|
|
if empty == 0 {
|
|
return 0;
|
|
}
|
|
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 score = get_score(me, other, first, copy, algorithm);
|
|
if score > max_s {
|
|
(max_p, max_s) = (i, score);
|
|
}
|
|
}
|
|
max_p
|
|
}
|