Fix rand not compilable for wasm, impl game logic

"rand" crate was not compilable for wasm-unknown-unknown target, so an
alternative "oorandom" crate was substituted in.

Basic game win/draw detection logic added.
This commit is contained in:
Stephen Seo 2022-03-09 16:22:01 +09:00
parent 2389441207
commit 9e9bb0758c
6 changed files with 158 additions and 64 deletions

61
front_end/Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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<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();
@ -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<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.
@ -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::<usize>() % mod_amount
rng.rand_u32() as usize % mod_amount
} else {
0
};

132
front_end/src/game_logic.rs Normal file
View file

@ -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<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
}

View file

@ -1,5 +1,7 @@
mod ai;
mod constants;
mod game_logic;
mod random_helper;
mod state;
mod yew_components;

View file

@ -0,0 +1,12 @@
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()))
}