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 }