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"
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"
]
[[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"
"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"
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"
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"
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,
player: Turn,
board: &BoardType,
) -> Result<SlotChoice, String> {
+ 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();
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<SlotChoice, String> {
+ let mut pick_some_of_choices = |amount: usize| -> Result<SlotChoice, String> {
let mut maximums: BTreeMap<i64, usize> = BTreeMap::new();
for (idx, utility) in &utilities {
// f64 cannot be used as Key since it doesn't implement Ord.
// 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);
}
// don't use random if only 1 item is to be picked
let random_number: usize = if mod_amount > 1 {
- thread_rng().gen::<usize>() % mod_amount
+ rng.rand_u32() as usize % mod_amount
} else {
0
};
--- /dev/null
+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<BoardState> {
+ 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<BoardState> {
+ 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
+}
mod ai;
mod constants;
+mod game_logic;
+mod random_helper;
mod state;
mod yew_components;
--- /dev/null
+use oorandom::Rand32;
+
+use std::time::{SystemTime, UNIX_EPOCH};
+
+pub fn get_seeded_random() -> Result<Rand32, String> {
+ let now = SystemTime::now();
+ let duration = now
+ .duration_since(UNIX_EPOCH)
+ .map_err(|e| format!("{}", e))?;
+
+ Ok(Rand32::new(duration.as_secs()))
+}