Impl async delay on AI choice
This commit is also a stepping-stone towards handling http requests which will require deferred callbacks on Yew Components. By figuring out how to delay callbacks in this commit, it should be easier to figure out how to handle http requests that may require a deferred callback.
This commit is contained in:
parent
02e0d5b7e2
commit
ebf0cb5bb8
5 changed files with 113 additions and 19 deletions
15
front_end/src/async_js_helper.rs
Normal file
15
front_end/src/async_js_helper.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use js_sys::Promise;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
#[wasm_bindgen(module = "/src/deferred_helper.js")]
|
||||
extern "C" {
|
||||
fn async_sleep(ms: u32) -> Promise;
|
||||
}
|
||||
|
||||
pub async fn rust_async_sleep(ms: u32) -> Result<(), JsValue> {
|
||||
let promise = async_sleep(ms);
|
||||
let js_fut = JsFuture::from(promise);
|
||||
js_fut.await?;
|
||||
Ok(())
|
||||
}
|
3
front_end/src/deferred_helper.js
Normal file
3
front_end/src/deferred_helper.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function async_sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod ai;
|
||||
mod async_js_helper;
|
||||
mod constants;
|
||||
mod game_logic;
|
||||
mod html_helper;
|
||||
|
|
|
@ -9,7 +9,7 @@ pub enum GameState {
|
|||
MainMenu,
|
||||
SinglePlayer(Turn, AIDifficulty),
|
||||
LocalMultiplayer,
|
||||
NetworkedMultiplayer,
|
||||
NetworkedMultiplayer(Turn),
|
||||
PostGameResults(BoardState),
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ impl From<MainMenuMessage> for GameState {
|
|||
match msg {
|
||||
MainMenuMessage::SinglePlayer(t, ai) => GameState::SinglePlayer(t, ai),
|
||||
MainMenuMessage::LocalMultiplayer => GameState::LocalMultiplayer,
|
||||
MainMenuMessage::NetworkedMultiplayer => GameState::NetworkedMultiplayer,
|
||||
MainMenuMessage::NetworkedMultiplayer(t) => GameState::NetworkedMultiplayer(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::ai::{get_ai_choice, AIDifficulty};
|
||||
use crate::async_js_helper;
|
||||
use crate::constants::{COLS, INFO_TEXT_MAX_ITEMS, ROWS};
|
||||
use crate::game_logic::{check_win_draw, WinType};
|
||||
use crate::html_helper::{
|
||||
|
@ -17,7 +18,7 @@ pub struct MainMenu {}
|
|||
pub enum MainMenuMessage {
|
||||
SinglePlayer(Turn, AIDifficulty),
|
||||
LocalMultiplayer,
|
||||
NetworkedMultiplayer,
|
||||
NetworkedMultiplayer(Turn),
|
||||
}
|
||||
|
||||
impl Component for MainMenu {
|
||||
|
@ -111,20 +112,32 @@ impl Component for MainMenu {
|
|||
let info_text_turn = document
|
||||
.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) =
|
||||
if let GameState::SinglePlayer(turn, _) = shared.game_state.get() {
|
||||
if shared.turn.get() == turn {
|
||||
info_text_turn.set_inner_html(
|
||||
"<p><b class=\"cyan\">It is CyanPlayer's (player) Turn</b></p>",
|
||||
);
|
||||
} else {
|
||||
info_text_turn.set_inner_html(
|
||||
"<p><b class=\"cyan\">It is CyanPlayer's (ai) Turn</b></p>",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
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));
|
||||
.send_message(WrapperMsg::AIChoice);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +195,7 @@ impl Component for Slot {
|
|||
GameState::MainMenu => return false,
|
||||
GameState::SinglePlayer(_, _)
|
||||
| GameState::LocalMultiplayer
|
||||
| GameState::NetworkedMultiplayer => (),
|
||||
| GameState::NetworkedMultiplayer(_) => (),
|
||||
GameState::PostGameResults(_) => return false,
|
||||
}
|
||||
if shared.game_state.get() == GameState::MainMenu {
|
||||
|
@ -205,8 +218,18 @@ impl Component for Slot {
|
|||
|
||||
pub struct Wrapper {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum WrapperMsg {
|
||||
Pressed(u8),
|
||||
AIPressed(u8),
|
||||
AIChoice,
|
||||
AIChoiceImpl,
|
||||
}
|
||||
|
||||
impl WrapperMsg {
|
||||
fn is_ai_pressed(self) -> bool {
|
||||
matches!(self, WrapperMsg::AIPressed(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Wrapper {
|
||||
|
@ -296,15 +319,34 @@ impl Component for Wrapper {
|
|||
.link()
|
||||
.context::<SharedState>(Callback::noop())
|
||||
.expect("state to be set");
|
||||
let (window, document) =
|
||||
let (_window, document) =
|
||||
get_window_document().expect("Should be able to get Window and Document");
|
||||
|
||||
match msg {
|
||||
WrapperMsg::Pressed(idx) => {
|
||||
WrapperMsg::Pressed(idx) | WrapperMsg::AIPressed(idx) => {
|
||||
let mut bottom_idx = idx;
|
||||
let mut placed = false;
|
||||
let current_player = shared.turn.get();
|
||||
|
||||
// check if player can make a move
|
||||
if !msg.is_ai_pressed() {
|
||||
match shared.game_state.get() {
|
||||
GameState::MainMenu => (),
|
||||
GameState::SinglePlayer(turn, _) => {
|
||||
if current_player != turn {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
GameState::LocalMultiplayer => (),
|
||||
GameState::NetworkedMultiplayer(turn) => {
|
||||
if current_player != turn {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
GameState::PostGameResults(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
// check if clicked on empty slot
|
||||
if shared.board[idx as usize].get().is_empty() {
|
||||
// get bottom-most empty slot
|
||||
|
@ -358,11 +400,28 @@ impl Component for Wrapper {
|
|||
// info text right of the grid
|
||||
{
|
||||
let turn = shared.turn.get();
|
||||
let output_str = format!(
|
||||
let output_str =
|
||||
if let GameState::SinglePlayer(player_turn, _) = shared.game_state.get() {
|
||||
if shared.turn.get() == player_turn {
|
||||
format!(
|
||||
"<b class=\"{}\">It is {}'s (player) turn</b>",
|
||||
turn.get_color(),
|
||||
turn
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"<b class=\"{}\">It is {}'s (ai) turn</b>",
|
||||
turn.get_color(),
|
||||
turn
|
||||
)
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"<b class=\"{}\">It is {}'s turn</b>",
|
||||
turn.get_color(),
|
||||
turn
|
||||
);
|
||||
)
|
||||
};
|
||||
|
||||
let text_append_result =
|
||||
append_to_info_text(&document, "info_text1", &output_str, 1);
|
||||
|
@ -720,18 +779,34 @@ impl Component for Wrapper {
|
|||
} // if: check for win or draw
|
||||
|
||||
// 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 {
|
||||
ctx.link().send_message(WrapperMsg::AIChoice);
|
||||
}
|
||||
}
|
||||
} // WrapperMsg::Pressed(idx) =>
|
||||
WrapperMsg::AIChoice => {
|
||||
// defer by 1 second
|
||||
ctx.link().send_future(async {
|
||||
async_js_helper::rust_async_sleep(1000).await.unwrap();
|
||||
WrapperMsg::AIChoiceImpl
|
||||
});
|
||||
}
|
||||
WrapperMsg::AIChoiceImpl => {
|
||||
// get AI's choice
|
||||
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, player_type.get_opposite(), &shared.board)
|
||||
.expect("AI should have an available choice");
|
||||
ctx.link()
|
||||
.send_message(WrapperMsg::Pressed(usize::from(choice) as u8));
|
||||
.send_message(WrapperMsg::AIPressed(usize::from(choice) as u8));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // WrapperMsg::Pressed(idx) =>
|
||||
} // match (msg)
|
||||
|
||||
true
|
||||
|
|
Loading…
Reference in a new issue