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 ai;
|
||||||
|
mod async_js_helper;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod game_logic;
|
mod game_logic;
|
||||||
mod html_helper;
|
mod html_helper;
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub enum GameState {
|
||||||
MainMenu,
|
MainMenu,
|
||||||
SinglePlayer(Turn, AIDifficulty),
|
SinglePlayer(Turn, AIDifficulty),
|
||||||
LocalMultiplayer,
|
LocalMultiplayer,
|
||||||
NetworkedMultiplayer,
|
NetworkedMultiplayer(Turn),
|
||||||
PostGameResults(BoardState),
|
PostGameResults(BoardState),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ impl From<MainMenuMessage> for GameState {
|
||||||
match msg {
|
match msg {
|
||||||
MainMenuMessage::SinglePlayer(t, ai) => GameState::SinglePlayer(t, ai),
|
MainMenuMessage::SinglePlayer(t, ai) => GameState::SinglePlayer(t, ai),
|
||||||
MainMenuMessage::LocalMultiplayer => GameState::LocalMultiplayer,
|
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::ai::{get_ai_choice, AIDifficulty};
|
||||||
|
use crate::async_js_helper;
|
||||||
use crate::constants::{COLS, INFO_TEXT_MAX_ITEMS, ROWS};
|
use crate::constants::{COLS, INFO_TEXT_MAX_ITEMS, ROWS};
|
||||||
use crate::game_logic::{check_win_draw, WinType};
|
use crate::game_logic::{check_win_draw, WinType};
|
||||||
use crate::html_helper::{
|
use crate::html_helper::{
|
||||||
|
@ -17,7 +18,7 @@ pub struct MainMenu {}
|
||||||
pub enum MainMenuMessage {
|
pub enum MainMenuMessage {
|
||||||
SinglePlayer(Turn, AIDifficulty),
|
SinglePlayer(Turn, AIDifficulty),
|
||||||
LocalMultiplayer,
|
LocalMultiplayer,
|
||||||
NetworkedMultiplayer,
|
NetworkedMultiplayer(Turn),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for MainMenu {
|
impl Component for MainMenu {
|
||||||
|
@ -111,20 +112,32 @@ impl Component for MainMenu {
|
||||||
let info_text_turn = document
|
let info_text_turn = document
|
||||||
.get_element_by_id("info_text1")
|
.get_element_by_id("info_text1")
|
||||||
.expect("info_text1 should exist");
|
.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()
|
shared.game_state.get()
|
||||||
{
|
{
|
||||||
// AI player starts first
|
// AI player starts first
|
||||||
let choice = get_ai_choice(ai_difficulty, Turn::CyanPlayer, &shared.board)
|
|
||||||
.expect("AI should have an available choice");
|
|
||||||
ctx.link()
|
ctx.link()
|
||||||
.get_parent()
|
.get_parent()
|
||||||
.expect("Wrapper should be parent of MainMenu")
|
.expect("Wrapper should be parent of MainMenu")
|
||||||
.clone()
|
.clone()
|
||||||
.downcast::<Wrapper>()
|
.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::MainMenu => return false,
|
||||||
GameState::SinglePlayer(_, _)
|
GameState::SinglePlayer(_, _)
|
||||||
| GameState::LocalMultiplayer
|
| GameState::LocalMultiplayer
|
||||||
| GameState::NetworkedMultiplayer => (),
|
| GameState::NetworkedMultiplayer(_) => (),
|
||||||
GameState::PostGameResults(_) => return false,
|
GameState::PostGameResults(_) => return false,
|
||||||
}
|
}
|
||||||
if shared.game_state.get() == GameState::MainMenu {
|
if shared.game_state.get() == GameState::MainMenu {
|
||||||
|
@ -205,8 +218,18 @@ impl Component for Slot {
|
||||||
|
|
||||||
pub struct Wrapper {}
|
pub struct Wrapper {}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum WrapperMsg {
|
pub enum WrapperMsg {
|
||||||
Pressed(u8),
|
Pressed(u8),
|
||||||
|
AIPressed(u8),
|
||||||
|
AIChoice,
|
||||||
|
AIChoiceImpl,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrapperMsg {
|
||||||
|
fn is_ai_pressed(self) -> bool {
|
||||||
|
matches!(self, WrapperMsg::AIPressed(_))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Wrapper {
|
impl Component for Wrapper {
|
||||||
|
@ -296,15 +319,34 @@ impl Component for Wrapper {
|
||||||
.link()
|
.link()
|
||||||
.context::<SharedState>(Callback::noop())
|
.context::<SharedState>(Callback::noop())
|
||||||
.expect("state to be set");
|
.expect("state to be set");
|
||||||
let (window, document) =
|
let (_window, document) =
|
||||||
get_window_document().expect("Should be able to get Window and Document");
|
get_window_document().expect("Should be able to get Window and Document");
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
WrapperMsg::Pressed(idx) => {
|
WrapperMsg::Pressed(idx) | WrapperMsg::AIPressed(idx) => {
|
||||||
let mut bottom_idx = idx;
|
let mut bottom_idx = idx;
|
||||||
let mut placed = false;
|
let mut placed = false;
|
||||||
let current_player = shared.turn.get();
|
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
|
// check if clicked on empty slot
|
||||||
if shared.board[idx as usize].get().is_empty() {
|
if shared.board[idx as usize].get().is_empty() {
|
||||||
// get bottom-most empty slot
|
// get bottom-most empty slot
|
||||||
|
@ -358,11 +400,28 @@ impl Component for Wrapper {
|
||||||
// info text right of the grid
|
// info text right of the grid
|
||||||
{
|
{
|
||||||
let turn = shared.turn.get();
|
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>",
|
"<b class=\"{}\">It is {}'s turn</b>",
|
||||||
turn.get_color(),
|
turn.get_color(),
|
||||||
turn
|
turn
|
||||||
);
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let text_append_result =
|
let text_append_result =
|
||||||
append_to_info_text(&document, "info_text1", &output_str, 1);
|
append_to_info_text(&document, "info_text1", &output_str, 1);
|
||||||
|
@ -720,18 +779,34 @@ impl Component for Wrapper {
|
||||||
} // if: check for win or draw
|
} // if: check for win or draw
|
||||||
|
|
||||||
// check if it is AI's turn
|
// 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 let GameState::SinglePlayer(player_type, ai_difficulty) = shared.game_state.get()
|
||||||
{
|
{
|
||||||
if shared.turn.get() != player_type {
|
if shared.turn.get() != player_type {
|
||||||
// get AI's choice
|
|
||||||
let choice =
|
let choice =
|
||||||
get_ai_choice(ai_difficulty, player_type.get_opposite(), &shared.board)
|
get_ai_choice(ai_difficulty, player_type.get_opposite(), &shared.board)
|
||||||
.expect("AI should have an available choice");
|
.expect("AI should have an available choice");
|
||||||
ctx.link()
|
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)
|
} // match (msg)
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
Loading…
Reference in a new issue