diff --git a/front_end/Cargo.lock b/front_end/Cargo.lock index 4a61525..e89084f 100644 --- a/front_end/Cargo.lock +++ b/front_end/Cargo.lock @@ -41,23 +41,12 @@ name = "four_line_dropper_frontend" version = "0.1.0" dependencies = [ "log", - "rand", + "oorandom", "wasm-logger", "web-sys", "yew", ] -[[package]] -name = "getrandom" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "gloo" version = "0.4.2" @@ -201,12 +190,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "libc" -version = "0.2.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" - [[package]] name = "log" version = "0.4.14" @@ -217,10 +200,10 @@ dependencies = [ ] [[package]] -name = "ppv-lite86" -version = "0.2.16" +name = "oorandom" +version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "proc-macro-error" @@ -264,36 +247,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - [[package]] name = "ryu" version = "1.0.9" @@ -386,12 +339,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - [[package]] name = "wasm-bindgen" version = "0.2.79" diff --git a/front_end/Cargo.toml b/front_end/Cargo.toml index a089c9a..f27b539 100644 --- a/front_end/Cargo.toml +++ b/front_end/Cargo.toml @@ -10,4 +10,4 @@ yew = "0.19" log = "0.4.6" wasm-logger = "0.2.0" web-sys = { version = "0.3.56", features = ["Window", "Document", "Element"] } -rand = "0.8.5" +oorandom = "11.1.3" diff --git a/front_end/src/ai.rs b/front_end/src/ai.rs index 7718217..05476e8 100644 --- a/front_end/src/ai.rs +++ b/front_end/src/ai.rs @@ -1,10 +1,9 @@ use std::collections::BTreeMap; use crate::constants::{AI_EASY_MAX_CHOICES, AI_NORMAL_MAX_CHOICES, COLS, ROWS}; +use crate::random_helper::get_seeded_random; use crate::state::{BoardState, BoardType, Turn}; -use rand::{thread_rng, Rng}; - #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum AIDifficulty { Easy, @@ -63,6 +62,8 @@ pub fn get_ai_choice( player: Turn, board: &BoardType, ) -> Result { + let mut rng = get_seeded_random()?; + let mut utilities = Vec::with_capacity(COLS as usize); for i in 0..(COLS as usize) { let slot = i.into(); @@ -82,11 +83,11 @@ pub fn get_ai_choice( let mut utilities: Vec<(usize, f64)> = utilities.into_iter().enumerate().collect(); if utilities.len() > 1 { for i in 1..utilities.len() { - utilities.swap(i, thread_rng().gen_range(0..=i)); + utilities.swap(i, rng.rand_range(0..((i + 1) as u32)) as usize); } } - let pick_some_of_choices = |amount: usize| -> Result { + let mut pick_some_of_choices = |amount: usize| -> Result { let mut maximums: BTreeMap = BTreeMap::new(); for (idx, utility) in &utilities { // f64 cannot be used as Key since it doesn't implement Ord. @@ -94,7 +95,7 @@ pub fn get_ai_choice( // order. let mut utility_value = (utility * 10000.0) as i64; while maximums.contains_key(&utility_value) { - utility_value += thread_rng().gen_range(-3..=3); + utility_value += rng.rand_range(0..7) as i64 - 3; } maximums.insert(utility_value, *idx); } @@ -108,7 +109,7 @@ pub fn get_ai_choice( // don't use random if only 1 item is to be picked let random_number: usize = if mod_amount > 1 { - thread_rng().gen::() % mod_amount + rng.rand_u32() as usize % mod_amount } else { 0 }; diff --git a/front_end/src/game_logic.rs b/front_end/src/game_logic.rs new file mode 100644 index 0000000..98ab872 --- /dev/null +++ b/front_end/src/game_logic.rs @@ -0,0 +1,132 @@ +use crate::constants::{COLS, ROWS}; +use crate::state::{BoardState, BoardType}; + +/// Returns a BoardState if win/draw, None if game is still going +pub fn check_win_draw(board: &BoardType) -> Option { + let mut has_empty_slot = false; + for slot in board { + match slot.get() { + BoardState::Empty => { + has_empty_slot = true; + break; + } + BoardState::Cyan | BoardState::Magenta => (), + } + } + + if has_empty_slot { + return None; + } + + let check_result = |state| -> Option { + match state { + BoardState::Empty => None, + BoardState::Cyan => Some(BoardState::Cyan), + BoardState::Magenta => Some(BoardState::Magenta), + } + }; + + // check horizontals + for y in 0..(ROWS as usize) { + for x in 0..((COLS - 3) as usize) { + let result = check_result(has_right_horizontal_at_idx(x + y * (COLS as usize), board)); + if result.is_some() { + return result; + } + } + } + + // check verticals + for y in 0..((ROWS - 3) as usize) { + for x in 0..(COLS as usize) { + let result = check_result(has_down_vertical_at_idx(x + y * (COLS as usize), board)); + if result.is_some() { + return result; + } + } + } + + // check up diagonals + for y in 3..(ROWS as usize) { + for x in 0..((COLS - 3) as usize) { + let result = check_result(has_right_up_diagonal_at_idx(x + y * (COLS as usize), board)); + if result.is_some() { + return result; + } + } + } + + // check down diagonals + for y in 0..((ROWS - 3) as usize) { + for x in 0..((COLS - 3) as usize) { + let result = check_result(has_right_down_diagonal_at_idx( + x + y * (COLS as usize), + board, + )); + if result.is_some() { + return result; + } + } + } + + None +} + +fn has_right_horizontal_at_idx(idx: usize, board: &BoardType) -> BoardState { + let state_at_idx = board[idx].get(); + if idx % (COLS as usize) < (COLS as usize) - 3 { + for x in 0..=3 { + if board[idx + x].get() != state_at_idx { + break; + } else if x == 3 { + return state_at_idx; + } + } + } + + BoardState::Empty +} + +fn has_down_vertical_at_idx(idx: usize, board: &BoardType) -> BoardState { + let state_at_idx = board[idx].get(); + if idx / (COLS as usize) < (ROWS as usize) - 3 { + for y in 0..=3 { + if board[idx + y * (COLS as usize)].get() != state_at_idx { + break; + } else if y == 3 { + return state_at_idx; + } + } + } + + BoardState::Empty +} + +fn has_right_up_diagonal_at_idx(idx: usize, board: &BoardType) -> BoardState { + let state_at_idx = board[idx].get(); + if idx % (COLS as usize) < (COLS as usize) - 3 && idx / (COLS as usize) > 2 { + for i in 0..=3 { + if board[idx + i - i * (COLS as usize)].get() != state_at_idx { + break; + } else if i == 3 { + return state_at_idx; + } + } + } + + BoardState::Empty +} + +fn has_right_down_diagonal_at_idx(idx: usize, board: &BoardType) -> BoardState { + let state_at_idx = board[idx].get(); + if idx % (COLS as usize) < (COLS as usize) - 3 && idx / (COLS as usize) < (ROWS as usize) - 3 { + for i in 0..=3 { + if board[idx + i + i * (COLS as usize)].get() != state_at_idx { + break; + } else if i == 3 { + return state_at_idx; + } + } + } + BoardState::Empty +} diff --git a/front_end/src/main.rs b/front_end/src/main.rs index 947ed49..eeca10f 100644 --- a/front_end/src/main.rs +++ b/front_end/src/main.rs @@ -1,5 +1,7 @@ mod ai; mod constants; +mod game_logic; +mod random_helper; mod state; mod yew_components; diff --git a/front_end/src/random_helper.rs b/front_end/src/random_helper.rs new file mode 100644 index 0000000..17bde78 --- /dev/null +++ b/front_end/src/random_helper.rs @@ -0,0 +1,12 @@ +use oorandom::Rand32; + +use std::time::{SystemTime, UNIX_EPOCH}; + +pub fn get_seeded_random() -> Result { + let now = SystemTime::now(); + let duration = now + .duration_since(UNIX_EPOCH) + .map_err(|e| format!("{}", e))?; + + Ok(Rand32::new(duration.as_secs())) +}