Incorporate game AI into game
Can select from three difficulties, and the AI makes their move when it is their turn. AI probably still needs some tweaking..
This commit is contained in:
parent
e35870b240
commit
89a12623b4
7 changed files with 91 additions and 17 deletions
1
front_end/Cargo.lock
generated
1
front_end/Cargo.lock
generated
|
@ -40,6 +40,7 @@ dependencies = [
|
|||
name = "four_line_dropper_frontend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"oorandom",
|
||||
"wasm-logger",
|
||||
|
|
|
@ -10,4 +10,5 @@ yew = "0.19"
|
|||
log = "0.4.6"
|
||||
wasm-logger = "0.2.0"
|
||||
web-sys = { version = "0.3.56", features = ["Window", "Document", "Element"] }
|
||||
js-sys = "0.3.56"
|
||||
oorandom = "11.1.3"
|
||||
|
|
|
@ -20,9 +20,23 @@
|
|||
gap: 8px;
|
||||
grid-auto-rows: 33%;
|
||||
}
|
||||
button.menuSinglePlayer {
|
||||
div.singlePlayerMenu {
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
|
||||
display: grid;
|
||||
}
|
||||
button.menuSinglePlayerEasy {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
button.menuSinglePlayerNormal {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
}
|
||||
button.menuSinglePlayerHard {
|
||||
grid-row: 3;
|
||||
grid-column: 1;
|
||||
}
|
||||
button.menuLocalMultiplayer {
|
||||
grid-row: 2;
|
||||
|
|
|
@ -148,7 +148,9 @@ fn get_utility_for_slot(player: Turn, slot: SlotChoice, board: &BoardType) -> Op
|
|||
// slot is full, cannot place in slot
|
||||
return None;
|
||||
}
|
||||
while idx < (ROWS * COLS) as usize && board[idx + COLS as usize].get() == BoardState::Empty {
|
||||
while idx < ((ROWS - 1) * COLS) as usize
|
||||
&& board[idx + COLS as usize].get() == BoardState::Empty
|
||||
{
|
||||
idx += COLS as usize;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
use js_sys::Math::random;
|
||||
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()))
|
||||
Ok(Rand32::new((random() * u64::MAX as f64) as u64))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::ai::AIDifficulty;
|
||||
use crate::yew_components::MainMenuMessage;
|
||||
use std::cell::Cell;
|
||||
use std::fmt::Display;
|
||||
|
@ -6,7 +7,7 @@ use std::rc::Rc;
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum GameState {
|
||||
MainMenu,
|
||||
SinglePlayer,
|
||||
SinglePlayer(Turn, AIDifficulty),
|
||||
LocalMultiplayer,
|
||||
NetworkedMultiplayer,
|
||||
PostGameResults(BoardState),
|
||||
|
@ -21,7 +22,7 @@ impl Default for GameState {
|
|||
impl From<MainMenuMessage> for GameState {
|
||||
fn from(msg: MainMenuMessage) -> Self {
|
||||
match msg {
|
||||
MainMenuMessage::SinglePlayer => GameState::SinglePlayer,
|
||||
MainMenuMessage::SinglePlayer(t, ai) => GameState::SinglePlayer(t, ai),
|
||||
MainMenuMessage::LocalMultiplayer => GameState::LocalMultiplayer,
|
||||
MainMenuMessage::NetworkedMultiplayer => GameState::NetworkedMultiplayer,
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use crate::ai::{get_ai_choice, AIDifficulty};
|
||||
use crate::constants::{COLS, INFO_TEXT_MAX_ITEMS, ROWS};
|
||||
use crate::game_logic::{check_win_draw, WinType};
|
||||
use crate::html_helper::{
|
||||
append_to_info_text, element_append_class, element_remove_class, get_window_document,
|
||||
};
|
||||
use crate::random_helper::get_seeded_random;
|
||||
use crate::state::{BoardState, GameState, SharedState, Turn};
|
||||
|
||||
use std::cell::Cell;
|
||||
|
@ -13,7 +15,7 @@ use yew::prelude::*;
|
|||
pub struct MainMenu {}
|
||||
|
||||
pub enum MainMenuMessage {
|
||||
SinglePlayer,
|
||||
SinglePlayer(Turn, AIDifficulty),
|
||||
LocalMultiplayer,
|
||||
NetworkedMultiplayer,
|
||||
}
|
||||
|
@ -33,14 +35,47 @@ impl Component for MainMenu {
|
|||
.expect("state to be set");
|
||||
match shared.game_state.get() {
|
||||
GameState::MainMenu => {
|
||||
let player_type: Turn;
|
||||
{
|
||||
let mut rng = get_seeded_random().expect("Random should be available");
|
||||
player_type = if rng.rand_range(0..2) == 0 {
|
||||
Turn::CyanPlayer
|
||||
} else {
|
||||
Turn::MagentaPlayer
|
||||
};
|
||||
}
|
||||
|
||||
let easy_player_type = player_type;
|
||||
let normal_player_type = player_type;
|
||||
let hard_player_type = player_type;
|
||||
|
||||
let onclick_singleplayer_easy = ctx.link().callback(move |_| {
|
||||
MainMenuMessage::SinglePlayer(easy_player_type, AIDifficulty::Easy)
|
||||
});
|
||||
let onclick_singleplayer_normal = ctx.link().callback(move |_| {
|
||||
MainMenuMessage::SinglePlayer(normal_player_type, AIDifficulty::Normal)
|
||||
});
|
||||
let onclick_singleplayer_hard = ctx.link().callback(move |_| {
|
||||
MainMenuMessage::SinglePlayer(hard_player_type, AIDifficulty::Hard)
|
||||
});
|
||||
|
||||
let onclick_local_multiplayer =
|
||||
ctx.link().callback(|_| MainMenuMessage::LocalMultiplayer);
|
||||
|
||||
html! {
|
||||
<div class={"menu"} id={"mainmenu"}>
|
||||
<b class={"menuText"}>{"Please pick a game mode."}</b>
|
||||
<button class={"menuSinglePlayer"}>
|
||||
{"Singleplayer"}
|
||||
<div class={"singlePlayerMenu"}>
|
||||
<button class={"menuSinglePlayerEasy"} onclick={onclick_singleplayer_easy}>
|
||||
{"Singleplayer Easy"}
|
||||
</button>
|
||||
<button class={"menuSinglePlayerNormal"} onclick={onclick_singleplayer_normal}>
|
||||
{"Singleplayer Normal"}
|
||||
</button>
|
||||
<button class={"menuSinglePlayerHard"} onclick={onclick_singleplayer_hard}>
|
||||
{"Singleplayer Hard"}
|
||||
</button>
|
||||
</div>
|
||||
<button class={"menuLocalMultiplayer"} onclick={onclick_local_multiplayer}>
|
||||
{"Local Multiplayer"}
|
||||
</button>
|
||||
|
@ -77,6 +112,20 @@ impl Component for MainMenu {
|
|||
.get_element_by_id("info_text1")
|
||||
.expect("info_text1 should exist");
|
||||
info_text_turn.set_inner_html("<p><b class=\"cyan\">It is CyanPlayer's Turn</b></p>");
|
||||
|
||||
if let GameState::SinglePlayer(Turn::MagentaPlayer, ai_difficulty) =
|
||||
shared.game_state.get()
|
||||
{
|
||||
// AI player starts first
|
||||
let choice = get_ai_choice(ai_difficulty, Turn::CyanPlayer, &shared.board)
|
||||
.expect("AI should have an available choice");
|
||||
ctx.link()
|
||||
.get_parent()
|
||||
.expect("Wrapper should be parent of MainMenu")
|
||||
.clone()
|
||||
.downcast::<Wrapper>()
|
||||
.send_message(WrapperMsg::Pressed(usize::from(choice) as u8));
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
|
@ -131,7 +180,7 @@ impl Component for Slot {
|
|||
|
||||
match shared.game_state.get() {
|
||||
GameState::MainMenu => return false,
|
||||
GameState::SinglePlayer
|
||||
GameState::SinglePlayer(_, _)
|
||||
| GameState::LocalMultiplayer
|
||||
| GameState::NetworkedMultiplayer => (),
|
||||
GameState::PostGameResults(_) => return false,
|
||||
|
@ -664,6 +713,18 @@ impl Component for Wrapper {
|
|||
}
|
||||
}
|
||||
} // else: game is still ongoing after logic check
|
||||
|
||||
// check if it is AI's turn
|
||||
if let GameState::SinglePlayer(player_type, ai_difficulty) = shared.game_state.get()
|
||||
{
|
||||
if shared.turn.get() != player_type {
|
||||
// get AI's choice
|
||||
let choice = get_ai_choice(ai_difficulty, Turn::CyanPlayer, &shared.board)
|
||||
.expect("AI should have an available choice");
|
||||
ctx.link()
|
||||
.send_message(WrapperMsg::Pressed(usize::from(choice) as u8));
|
||||
}
|
||||
}
|
||||
} // WrapperMsg::Pressed(idx) =>
|
||||
} // match (msg)
|
||||
|
||||
|
|
Loading…
Reference in a new issue