2022-04-06 11:49:54 +00:00
//Four Line Dropper Frontend - A webapp that allows one to play a game of Four Line Dropper
//Copyright (C) 2022 Stephen Seo
//
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
2022-03-10 07:17:16 +00:00
use crate ::ai ::{ get_ai_choice , AIDifficulty } ;
2022-04-04 09:25:17 +00:00
use crate ::constants ::{
AI_CHOICE_DURATION_MILLIS , BACKEND_TICK_DURATION_MILLIS , BACKEND_URL , COLS ,
INFO_TEXT_MAX_ITEMS , ROWS ,
} ;
2022-03-09 09:10:13 +00:00
use crate ::game_logic ::{ check_win_draw , WinType } ;
2022-03-10 06:43:03 +00:00
use crate ::html_helper ::{
2022-04-27 03:47:45 +00:00
append_to_info_text , element_append_class , element_has_class , element_remove_class ,
get_window_document , send_to_backend ,
2022-03-10 06:43:03 +00:00
} ;
2022-03-10 07:17:16 +00:00
use crate ::random_helper ::get_seeded_random ;
2022-04-06 04:39:12 +00:00
use crate ::state ::{
2022-04-29 09:30:41 +00:00
board_from_string , BoardState , EmoteEnum , GameState , GameStateResponse , MainMenuMessage ,
2022-04-06 09:43:17 +00:00
NetworkedGameState , PairingRequestResponse , PairingStatusResponse , PlaceTokenResponse ,
2022-04-29 09:30:41 +00:00
PlacedEnum , SendEmoteRequestResponse , SharedState , Turn ,
2022-04-06 04:39:12 +00:00
} ;
2022-03-09 08:29:53 +00:00
2022-04-29 02:08:54 +00:00
use std ::cell ::{ Cell , RefCell } ;
2022-04-06 04:39:12 +00:00
use std ::collections ::HashMap ;
2022-03-02 06:18:10 +00:00
use std ::rc ::Rc ;
2022-03-09 08:29:53 +00:00
2022-04-05 09:16:04 +00:00
use js_sys ::{ Function , Promise } ;
2022-04-29 02:08:54 +00:00
use web_sys ::{ AddEventListenerOptions , Document , EventListenerOptions , HtmlInputElement } ;
2022-03-23 07:56:25 +00:00
use wasm_bindgen_futures ::JsFuture ;
2022-03-02 06:38:24 +00:00
use yew ::prelude ::* ;
2022-03-02 06:18:10 +00:00
2022-03-04 07:22:30 +00:00
pub struct MainMenu { }
impl Component for MainMenu {
type Message = MainMenuMessage ;
type Properties = ( ) ;
fn create ( _ctx : & Context < Self > ) -> Self {
Self { }
}
fn view ( & self , ctx : & Context < Self > ) -> Html {
let ( shared , _ ) = ctx
. link ( )
. context ::< SharedState > ( Callback ::noop ( ) )
. expect ( " state to be set " ) ;
2022-04-25 06:02:18 +00:00
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
} ;
}
2022-03-10 07:17:16 +00:00
2022-04-25 06:02:18 +00:00
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 ) ;
let onclick_networked_multiplayer = ctx
. link ( )
2022-04-27 03:47:45 +00:00
. callback ( | _ | MainMenuMessage ::NetworkedMultiplayer ( None ) ) ;
2022-04-25 06:02:18 +00:00
2022-04-27 03:47:45 +00:00
let menu_class = if shared . game_state . borrow ( ) . eq ( & GameState ::MainMenu ) {
2022-04-25 06:02:18 +00:00
" menu "
} else {
" hidden_menu "
} ;
html! {
< div class = { menu_class } id = { " mainmenu " } >
< b class = { " menuText " } > { " Please pick a game mode. " } < / b >
< 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 >
2022-03-04 07:22:30 +00:00
< / div >
2022-04-25 06:02:18 +00:00
< button class = { " menuLocalMultiplayer " } onclick = { onclick_local_multiplayer } >
{ " Local Multiplayer " }
< / button >
2022-04-27 03:47:45 +00:00
< div class = { " multiplayerMenu " } >
< button class = { " networkedMultiplayer " } onclick = { onclick_networked_multiplayer } >
{ " Networked Multiplayer " }
< / button >
< button class = { " NMPhrase " } onclick = { ctx . link ( ) . callback ( | _ | {
let ( _window , document ) = get_window_document ( ) . expect ( " Should be able to get window/document " ) ;
let input = HtmlInputElement ::from ( wasm_bindgen ::JsValue ::from ( document . get_element_by_id ( " NMPhraseText " ) . expect ( " Should be able to get NMPhrase input element " ) ) ) ;
MainMenuMessage ::NetworkedMultiplayer ( Some ( input . value ( ) ) )
} ) } >
{ " NMultiplayer with Phrase " }
< / button >
< input class = { " NMPhrase " } id = { " NMPhraseText " } placeholder = { " input phrase here " } autofocus = true / >
< / div >
2022-04-25 06:02:18 +00:00
< / div >
2022-03-04 07:22:30 +00:00
}
}
fn update ( & mut self , ctx : & Context < Self > , msg : Self ::Message ) -> bool {
let ( shared , _ ) = ctx
. link ( )
. context ::< SharedState > ( Callback ::noop ( ) )
. expect ( " state to be set " ) ;
let window = web_sys ::window ( ) . expect ( " no window exists " ) ;
let document = window . document ( ) . expect ( " window should have a document " ) ;
shared . game_state . replace ( msg . into ( ) ) ;
2022-04-27 03:47:45 +00:00
if ! shared . game_state . borrow ( ) . eq ( & GameState ::MainMenu ) {
2022-03-04 07:22:30 +00:00
let mainmenu = document
. get_element_by_id ( " mainmenu " )
. expect ( " mainmenu should exist " ) ;
mainmenu . set_class_name ( " hidden_menu " ) ;
2022-04-27 03:47:45 +00:00
match shared . game_state . borrow ( ) . clone ( ) {
2022-04-04 09:25:17 +00:00
GameState ::SinglePlayer ( turn , _ ) = > {
if shared . turn . get ( ) = = turn {
2022-04-06 09:43:17 +00:00
append_to_info_text (
& document ,
" info_text1 " ,
" <b class= \" cyan \" >It is CyanPlayer's (player) Turn</b> " ,
1 ,
)
. ok ( ) ;
2022-04-04 09:25:17 +00:00
} else {
2022-04-06 09:43:17 +00:00
append_to_info_text (
& document ,
" info_text1 " ,
" <b class= \" cyan \" >It is CyanPlayer's (ai) Turn</b> " ,
1 ,
)
. ok ( ) ;
2022-04-04 09:25:17 +00:00
// AI player starts first
ctx . link ( )
. get_parent ( )
. expect ( " Wrapper should be parent of MainMenu " )
. clone ( )
. downcast ::< Wrapper > ( )
. send_message ( WrapperMsg ::AIChoice ) ;
}
}
GameState ::NetworkedMultiplayer {
paired : _ ,
current_side : _ ,
current_turn : _ ,
2022-04-27 03:47:45 +00:00
phrase : _ ,
2022-04-04 09:25:17 +00:00
} = > {
2022-04-06 09:43:17 +00:00
append_to_info_text (
& document ,
" info_text1 " ,
" <b>Waiting to pair with another player...</b> " ,
1 ,
)
. ok ( ) ;
2022-04-04 09:25:17 +00:00
// start the Wrapper Tick loop
ctx . link ( )
. get_parent ( )
. expect ( " Wrapper should be a parent of MainMenu " )
. clone ( )
. downcast ::< Wrapper > ( )
2022-04-25 06:02:18 +00:00
. send_message ( WrapperMsg ::StartBackendTick ) ;
2022-04-04 09:25:17 +00:00
}
_ = > {
2022-04-06 09:43:17 +00:00
append_to_info_text (
& document ,
" info_text1 " ,
" <b class= \" cyan \" >It is CyanPlayer's Turn</b> " ,
1 ,
)
. ok ( ) ;
2022-03-15 04:16:09 +00:00
}
2022-03-10 07:17:16 +00:00
}
2022-03-04 07:22:30 +00:00
}
true
}
}
2022-04-25 06:02:18 +00:00
struct ResetButton { }
impl Component for ResetButton {
type Message = ( ) ;
type Properties = ( ) ;
fn create ( _ctx : & Context < Self > ) -> Self {
Self { }
}
fn view ( & self , ctx : & Context < Self > ) -> Html {
let onclick_reset = ctx . link ( ) . callback ( | _ | ( ) ) ;
html! {
< button class = { " resetbutton " } id = { " resetbutton " } onclick = { onclick_reset } >
{ " Reset " }
< / button >
}
}
fn update ( & mut self , ctx : & Context < Self > , _msg : Self ::Message ) -> bool {
ctx . link ( )
. get_parent ( )
. expect ( " Wrapper should be parent of ResetButton " )
. clone ( )
. downcast ::< Wrapper > ( )
. send_message ( WrapperMsg ::Reset ) ;
true
}
}
2022-04-29 09:30:41 +00:00
struct EmoteButton { }
enum EmoteButtonMsg {
Pressed ,
}
#[ derive(Copy, Clone, Debug, PartialEq, Eq, Properties) ]
struct EmoteButtonProperties {
emote : EmoteEnum ,
}
impl Component for EmoteButton {
type Message = EmoteButtonMsg ;
type Properties = EmoteButtonProperties ;
fn create ( _ctx : & Context < Self > ) -> Self {
Self { }
}
fn view ( & self , ctx : & Context < Self > ) -> Html {
let onclick = ctx . link ( ) . callback ( | _ | EmoteButtonMsg ::Pressed ) ;
let emote_id = format! ( " emote_ {} " , ctx . props ( ) . emote ) ;
html! {
< button class = { " emote " } id = { emote_id } onclick = { onclick } >
{ ctx . props ( ) . emote . get_unicode ( ) }
< / button >
}
}
2022-04-29 10:21:59 +00:00
fn update ( & mut self , ctx : & Context < Self > , _msg : Self ::Message ) -> bool {
let ( shared , _ ) = ctx
. link ( )
. context ::< SharedState > ( Callback ::noop ( ) )
. expect ( " state to be set " ) ;
let ( _window , document ) =
get_window_document ( ) . expect ( " Should be able to get Window and Document " ) ;
if shared . game_state . borrow ( ) . is_networked_multiplayer ( ) {
ctx . link ( )
. get_parent ( )
. expect ( " Wrapper should be parent of EmoteButton " )
. clone ( )
. downcast ::< Wrapper > ( )
. send_message ( WrapperMsg ::SendEmote ( ctx . props ( ) . emote ) ) ;
} else if let Some ( side ) = shared . game_state . borrow ( ) . get_singleplayer_current_side ( ) {
append_to_info_text (
& document ,
" info_text0 " ,
& format! (
" <b class= \" {} \" >{} emoted with <b class= \" emote \" >{}</b></b> " ,
side . get_color ( ) ,
side ,
ctx . props ( ) . emote . get_unicode ( )
) ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
} else {
append_to_info_text (
& document ,
" info_text0 " ,
" <b>Cannot use emotes at this time</b> " ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
}
2022-04-29 09:30:41 +00:00
true
}
}
2022-03-02 06:18:10 +00:00
pub struct Slot { }
pub enum SlotMessage {
2022-03-10 06:43:03 +00:00
Press ,
2022-03-02 06:18:10 +00:00
}
#[ derive(Clone, PartialEq, Properties) ]
pub struct SlotProperties {
idx : u8 ,
state : Rc < Cell < BoardState > > ,
2022-03-10 06:43:03 +00:00
placed : Rc < Cell < bool > > ,
2022-03-02 06:18:10 +00:00
}
impl Component for Slot {
type Message = SlotMessage ;
type Properties = SlotProperties ;
fn create ( _ctx : & Context < Self > ) -> Self {
Self { }
}
fn view ( & self , ctx : & Context < Self > ) -> Html {
let idx = ctx . props ( ) . idx ;
2022-03-02 08:51:14 +00:00
let state = ctx . props ( ) . state . as_ref ( ) . get ( ) ;
2022-03-10 06:43:03 +00:00
let onclick = ctx . link ( ) . callback ( move | _ | SlotMessage ::Press ) ;
2022-03-02 06:18:10 +00:00
let col = idx % COLS ;
let row = idx / COLS ;
2022-03-10 06:43:03 +00:00
let place = if ctx . props ( ) . placed . get ( ) & & ! state . is_win ( ) {
" placed "
} else {
" "
} ;
ctx . props ( ) . placed . replace ( false ) ;
2022-03-02 06:18:10 +00:00
html! {
2022-03-10 06:43:03 +00:00
< button class = { format! ( " slot {} r {} c {} {} " , state , row , col , place ) } id = { format! ( " slot {} " , idx ) } onclick = { onclick } >
2022-03-02 08:51:14 +00:00
{ idx }
2022-03-02 06:18:10 +00:00
< / button >
}
}
fn update ( & mut self , ctx : & Context < Self > , msg : Self ::Message ) -> bool {
2022-03-04 07:22:30 +00:00
let ( shared , _ ) = ctx
. link ( )
. context ::< SharedState > ( Callback ::noop ( ) )
. expect ( " state to be set " ) ;
2022-04-27 03:47:45 +00:00
match shared . game_state . borrow ( ) . clone ( ) {
2022-03-09 08:29:53 +00:00
GameState ::MainMenu = > return false ,
2022-04-04 09:25:17 +00:00
GameState ::SinglePlayer ( _ , _ ) | GameState ::LocalMultiplayer = > ( ) ,
GameState ::NetworkedMultiplayer {
paired ,
current_side ,
current_turn ,
2022-04-27 03:47:45 +00:00
phrase : _ ,
2022-04-04 09:25:17 +00:00
} = > {
2022-04-27 07:51:57 +00:00
if paired
& & current_side . is_some ( )
& & current_side . as_ref ( ) . unwrap ( ) = = & current_turn
{
// notify Wrapper with picked slot
if let Some ( p ) = ctx . link ( ) . get_parent ( ) {
p . clone ( )
. downcast ::< Wrapper > ( )
. send_message ( WrapperMsg ::BackendRequest {
place : ctx . props ( ) . idx ,
} ) ;
return false ;
2022-04-06 09:43:17 +00:00
}
2022-04-04 09:25:17 +00:00
}
}
2022-03-09 08:29:53 +00:00
GameState ::PostGameResults ( _ ) = > return false ,
}
2022-04-27 03:47:45 +00:00
if shared . game_state . borrow ( ) . eq ( & GameState ::MainMenu ) {
2022-03-04 07:22:30 +00:00
return false ;
}
2022-03-02 06:18:10 +00:00
match msg {
2022-03-10 06:43:03 +00:00
SlotMessage ::Press = > {
2022-03-02 07:25:01 +00:00
// notify Wrapper with message
2022-03-10 06:43:03 +00:00
let msg = WrapperMsg ::Pressed ( ctx . props ( ) . idx ) ;
2022-03-02 07:25:01 +00:00
if let Some ( p ) = ctx . link ( ) . get_parent ( ) {
p . clone ( ) . downcast ::< Wrapper > ( ) . send_message ( msg ) ;
2022-03-02 06:18:10 +00:00
}
}
}
true
}
}
2022-04-04 09:25:17 +00:00
pub struct Wrapper {
player_id : Option < u32 > ,
place_request : Option < u8 > ,
do_backend_tick : bool ,
2022-04-29 02:08:54 +00:00
cleanup_id_callback : Rc < RefCell < Option < Function > > > ,
2022-04-30 07:27:43 +00:00
board_updated_time : Option < String > ,
2022-04-04 09:25:17 +00:00
}
impl Wrapper {
fn defer_message (
& self ,
ctx : & Context < Self > ,
msg : < Wrapper as Component > ::Message ,
millis : i32 ,
) {
ctx . link ( ) . send_future ( async move {
let promise = Promise ::new ( & mut | resolve : js_sys ::Function , _reject | {
let window = web_sys ::window ( ) ;
if window . is_none ( ) {
resolve . call0 ( & resolve ) . ok ( ) ;
return ;
}
let window = window . unwrap ( ) ;
if window
. set_timeout_with_callback_and_timeout_and_arguments_0 ( & resolve , millis )
. is_err ( )
{
resolve . call0 ( & resolve ) . ok ( ) ;
}
} ) ;
let js_fut = JsFuture ::from ( promise ) ;
js_fut . await . ok ( ) ;
msg
} ) ;
}
fn get_networked_player_id ( & mut self , ctx : & Context < Self > ) {
2022-04-27 03:47:45 +00:00
let ( shared , _ ) = ctx
. link ( )
. context ::< SharedState > ( Callback ::noop ( ) )
. expect ( " state to be set " ) ;
let phrase_clone : Option < String > = shared . game_state . borrow ( ) . get_phrase ( ) ;
2022-04-04 09:25:17 +00:00
// make a request to get the player_id
2022-04-27 03:47:45 +00:00
ctx . link ( ) . send_future ( async move {
2022-04-06 04:39:12 +00:00
let mut json_entries = HashMap ::new ( ) ;
json_entries . insert ( " type " . into ( ) , " pairing_request " . into ( ) ) ;
2022-04-27 03:47:45 +00:00
if let Some ( phrase_string ) = phrase_clone {
json_entries . insert ( " phrase " . into ( ) , phrase_string ) ;
}
2022-04-04 09:25:17 +00:00
2022-04-06 04:39:12 +00:00
let send_to_backend_result = send_to_backend ( json_entries ) . await ;
if let Err ( e ) = send_to_backend_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
2022-04-04 09:25:17 +00:00
}
2022-03-02 06:18:10 +00:00
2022-04-06 04:39:12 +00:00
let request_result : Result < PairingRequestResponse , _ > =
serde_json ::from_str ( & send_to_backend_result . unwrap ( ) ) ;
if let Err ( e ) = request_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
2022-04-04 09:25:17 +00:00
}
2022-04-06 04:39:12 +00:00
let request = request_result . unwrap ( ) ;
2022-04-04 09:25:17 +00:00
2022-04-06 04:39:12 +00:00
if request . r#type ! = " pairing_response " {
2022-04-04 09:25:17 +00:00
return WrapperMsg ::BackendResponse ( BREnum ::Error (
2022-04-06 04:39:12 +00:00
" Backend returned invalid type for pairing_request " . into ( ) ,
2022-04-04 09:25:17 +00:00
) ) ;
}
2022-04-06 04:39:12 +00:00
if let Some ( color ) = request . color {
WrapperMsg ::BackendResponse ( BREnum ::GotID (
request . id ,
if color = = " cyan " {
Some ( Turn ::CyanPlayer )
} else {
Some ( Turn ::MagentaPlayer )
} ,
) )
2022-04-04 09:25:17 +00:00
} else {
2022-04-06 04:39:12 +00:00
WrapperMsg ::BackendResponse ( BREnum ::GotID ( request . id , None ) )
2022-04-04 09:25:17 +00:00
}
} ) ;
}
2022-04-06 09:43:17 +00:00
fn get_networked_player_type ( & mut self , ctx : & Context < Self > ) {
// make a request to get the pairing status
if self . player_id . is_none ( ) {
log ::warn! ( " Cannot request pairing status if ID is unknown " ) ;
return ;
}
let player_id : u32 = self . player_id . unwrap ( ) ;
ctx . link ( ) . send_future ( async move {
let mut json_entries = HashMap ::new ( ) ;
json_entries . insert ( " type " . into ( ) , " check_pairing " . into ( ) ) ;
json_entries . insert ( " id " . into ( ) , format! ( " {} " , player_id ) ) ;
let send_to_backend_result = send_to_backend ( json_entries ) . await ;
if let Err ( e ) = send_to_backend_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
}
let request_result : Result < PairingStatusResponse , _ > =
serde_json ::from_str ( & send_to_backend_result . unwrap ( ) ) ;
if let Err ( e ) = request_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
}
let response = request_result . unwrap ( ) ;
if response . r#type ! = " pairing_status " {
return WrapperMsg ::BackendResponse ( BREnum ::Error (
" Invalid response type when check_pairing " . into ( ) ,
) ) ;
}
if response . status = = " paired " & & response . color . is_some ( ) {
WrapperMsg ::BackendResponse ( BREnum ::GotPairing ( response . color . map ( | string | {
if string = = " cyan " {
Turn ::CyanPlayer
} else {
Turn ::MagentaPlayer
}
} ) ) )
} else {
WrapperMsg ::BackendResponse ( BREnum ::Error ( " Not paired " . into ( ) ) )
}
} ) ;
}
fn get_game_status ( & mut self , ctx : & Context < Self > ) {
if self . player_id . is_none ( ) {
log ::warn! ( " Cannot request pairing status if ID is unknown " ) ;
return ;
}
let player_id : u32 = self . player_id . unwrap ( ) ;
ctx . link ( ) . send_future ( async move {
let mut json_entries = HashMap ::new ( ) ;
json_entries . insert ( " id " . into ( ) , format! ( " {} " , player_id ) ) ;
json_entries . insert ( " type " . into ( ) , " game_state " . into ( ) ) ;
let send_to_backend_result = send_to_backend ( json_entries ) . await ;
if let Err ( e ) = send_to_backend_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
}
let response_result : Result < GameStateResponse , _ > =
serde_json ::from_str ( & send_to_backend_result . unwrap ( ) ) ;
if let Err ( e ) = response_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
}
let response = response_result . unwrap ( ) ;
if response . r#type ! = " game_state " {
return WrapperMsg ::BackendResponse ( BREnum ::Error (
" Invalid state when checking game_state " . into ( ) ,
) ) ;
}
let networked_game_state = match response . status . as_str ( ) {
" not_paired " = > NetworkedGameState ::NotPaired ,
" unknown_id " = > NetworkedGameState ::UnknownID ,
" cyan_turn " = > NetworkedGameState ::CyanTurn ,
" magenta_turn " = > NetworkedGameState ::MagentaTurn ,
" cyan_won " = > NetworkedGameState ::CyanWon ,
" magenta_won " = > NetworkedGameState ::MagentaWon ,
" draw " = > NetworkedGameState ::Draw ,
" opponent_disconnected " = > NetworkedGameState ::Disconnected ,
_ = > NetworkedGameState ::InternalError ,
} ;
2022-04-30 07:27:43 +00:00
WrapperMsg ::BackendResponse ( BREnum ::GotStatus {
2022-04-29 09:30:41 +00:00
networked_game_state ,
2022-04-30 07:27:43 +00:00
board_string : response . board ,
received_emote : response . peer_emote ,
updated_time : response . updated_time ,
} )
2022-04-06 09:43:17 +00:00
} ) ;
}
fn send_place_request ( & mut self , ctx : & Context < Self > , placement : u8 ) {
if self . player_id . is_none ( ) {
log ::warn! ( " Cannot request pairing status if ID is unknown " ) ;
return ;
}
let player_id : u32 = self . player_id . unwrap ( ) ;
ctx . link ( ) . send_future ( async move {
let mut json_entries = HashMap ::new ( ) ;
json_entries . insert ( " id " . into ( ) , format! ( " {} " , player_id ) ) ;
json_entries . insert ( " position " . into ( ) , format! ( " {} " , placement ) ) ;
json_entries . insert ( " type " . into ( ) , " place_token " . into ( ) ) ;
let send_to_backend_result = send_to_backend ( json_entries ) . await ;
if let Err ( e ) = send_to_backend_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
}
let response_result : Result < PlaceTokenResponse , _ > =
serde_json ::from_str ( & send_to_backend_result . unwrap ( ) ) ;
if let Err ( e ) = response_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
}
let response = response_result . unwrap ( ) ;
if response . r#type ! = " place_token " {
return WrapperMsg ::BackendResponse ( BREnum ::Error (
" Invalid state when place_token " . into ( ) ,
) ) ;
}
let placed_enum = match response . status . as_str ( ) {
" accepted " = > PlacedEnum ::Accepted ,
" illegal " = > PlacedEnum ::Illegal ,
" not_your_turn " = > PlacedEnum ::NotYourTurn ,
" game_ended_draw " = > PlacedEnum ::Other ( NetworkedGameState ::Draw ) ,
" game_ended_cyan_won " = > PlacedEnum ::Other ( NetworkedGameState ::CyanWon ) ,
" game_ended_magenta_won " = > PlacedEnum ::Other ( NetworkedGameState ::MagentaWon ) ,
" unknown_id " = > PlacedEnum ::Other ( NetworkedGameState ::UnknownID ) ,
" not_paired_yet " = > PlacedEnum ::Other ( NetworkedGameState ::NotPaired ) ,
_ = > PlacedEnum ::Other ( NetworkedGameState ::InternalError ) ,
} ;
WrapperMsg ::BackendResponse ( BREnum ::GotPlaced ( placed_enum , response . board ) )
} ) ;
}
fn update_board_from_string (
& mut self ,
shared : & SharedState ,
document : & Document ,
board_string : String ,
) {
let board = board_from_string ( board_string . clone ( ) ) ;
for ( idx , slot ) in board . iter ( ) . enumerate ( ) {
let was_open =
2022-04-27 07:51:57 +00:00
element_has_class ( document , & format! ( " slot {} " , idx ) , " open " ) . unwrap_or ( false ) ;
element_remove_class ( document , & format! ( " slot {} " , idx ) , " open " ) . ok ( ) ;
element_remove_class ( document , & format! ( " slot {} " , idx ) , " placed " ) . ok ( ) ;
element_remove_class ( document , & format! ( " slot {} " , idx ) , " win " ) . ok ( ) ;
element_remove_class ( document , & format! ( " slot {} " , idx ) , " cyan " ) . ok ( ) ;
element_remove_class ( document , & format! ( " slot {} " , idx ) , " magenta " ) . ok ( ) ;
2022-04-06 09:43:17 +00:00
match slot . get ( ) {
BoardState ::Empty = > {
2022-04-27 07:51:57 +00:00
element_append_class ( document , & format! ( " slot {} " , idx ) , " open " ) . ok ( ) ;
2022-04-06 09:43:17 +00:00
}
BoardState ::Cyan = > {
2022-04-27 07:51:57 +00:00
element_append_class ( document , & format! ( " slot {} " , idx ) , " cyan " ) . ok ( ) ;
2022-04-06 09:43:17 +00:00
}
BoardState ::CyanWin = > {
2022-04-27 07:51:57 +00:00
element_append_class ( document , & format! ( " slot {} " , idx ) , " cyan " ) . ok ( ) ;
element_append_class ( document , & format! ( " slot {} " , idx ) , " win " ) . ok ( ) ;
2022-04-06 09:43:17 +00:00
}
BoardState ::Magenta = > {
2022-04-27 07:51:57 +00:00
element_append_class ( document , & format! ( " slot {} " , idx ) , " magenta " ) . ok ( ) ;
2022-04-06 09:43:17 +00:00
}
BoardState ::MagentaWin = > {
2022-04-27 07:51:57 +00:00
element_append_class ( document , & format! ( " slot {} " , idx ) , " magenta " ) . ok ( ) ;
element_append_class ( document , & format! ( " slot {} " , idx ) , " win " ) . ok ( ) ;
2022-04-06 09:43:17 +00:00
}
}
let char_at_idx = board_string
. chars ( )
. nth ( idx )
. expect ( " idx into board_string should be in range " ) ;
2022-04-06 11:09:22 +00:00
if char_at_idx = = 'f' | | char_at_idx = = 'h' {
if char_at_idx = = 'f' {
2022-04-27 07:51:57 +00:00
element_append_class ( document , & format! ( " slot {} " , idx ) , " placed " ) . ok ( ) ;
2022-04-06 11:09:22 +00:00
}
2022-04-06 09:43:17 +00:00
if was_open {
append_to_info_text (
2022-04-27 07:51:57 +00:00
document ,
2022-04-06 09:43:17 +00:00
" info_text0 " ,
& format! ( " <b class= \" cyan \" >CyanPlayer placed at {} </b> " , idx ) ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
}
2022-04-06 11:09:22 +00:00
} else if char_at_idx = = 'g' | | char_at_idx = = 'i' {
if char_at_idx = = 'g' {
2022-04-27 07:51:57 +00:00
element_append_class ( document , & format! ( " slot {} " , idx ) , " placed " ) . ok ( ) ;
2022-04-06 11:09:22 +00:00
}
2022-04-06 09:43:17 +00:00
if was_open {
append_to_info_text (
2022-04-27 07:51:57 +00:00
document ,
2022-04-06 09:43:17 +00:00
" info_text0 " ,
& format! ( " <b class= \" magenta \" >MagentaPlayer placed at {} </b> " , idx ) ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
}
}
shared . board [ idx ] . set ( slot . get ( ) ) ;
}
}
2022-04-29 02:08:54 +00:00
fn cleanup_disconnect_callbacks ( & mut self ) {
// if previously set disconnect callback is set, unset it
2022-04-29 02:23:03 +00:00
if let Some ( callback ) = self . cleanup_id_callback . take ( ) {
2022-04-29 03:11:49 +00:00
let window = web_sys ::window ( ) . expect ( " Should be able to get window " ) ;
2022-04-29 02:08:54 +00:00
let mut options = EventListenerOptions ::new ( ) ;
options . capture ( true ) ;
if window
. remove_event_listener_with_callback_and_event_listener_options (
" pagehide " , & callback , & options ,
)
. is_err ( )
{
log ::warn! ( " Failed to remove event listener for disconnect ID on pagehide " ) ;
}
if window
. remove_event_listener_with_callback_and_event_listener_options (
" beforeunload " ,
& callback ,
& options ,
)
. is_err ( )
{
log ::warn! ( " Failed to remove event listener for disconnect ID on beforeunload " ) ;
}
}
}
2022-04-29 02:37:15 +00:00
fn send_disconnect ( & mut self ) {
if let Some ( id ) = self . player_id . take ( ) {
let function = Function ::new_no_args ( & format! (
"
let xhr = new XMLHttpRequest ( ) ;
xhr . open ( ' POST ' , ' { } ' ) ;
xhr . send ( ' { { \ " type \" : \" disconnect \" , \" id \" : {}}}');
" ,
BACKEND_URL , id
) ) ;
function . call0 ( & function ) . ok ( ) ;
}
}
2022-04-04 09:25:17 +00:00
}
#[ derive(Clone, Debug, PartialEq, Eq) ]
pub enum BREnum {
Error ( String ) ,
GotID ( u32 , Option < Turn > ) ,
2022-04-06 09:43:17 +00:00
GotPairing ( Option < Turn > ) ,
2022-04-30 07:27:43 +00:00
/// Second opt string is board_str, third opt string is received emote,
/// fourth opt string is updated_time
GotStatus {
networked_game_state : NetworkedGameState ,
board_string : Option < String > ,
received_emote : Option < String > ,
updated_time : Option < String > ,
} ,
2022-04-06 09:43:17 +00:00
GotPlaced ( PlacedEnum , String ) ,
2022-04-04 09:25:17 +00:00
}
#[ derive(Clone, Debug, PartialEq, Eq) ]
2022-03-02 10:19:50 +00:00
pub enum WrapperMsg {
Pressed ( u8 ) ,
2022-03-15 04:16:09 +00:00
AIPressed ( u8 ) ,
AIChoice ,
AIChoiceImpl ,
2022-04-25 06:02:18 +00:00
StartBackendTick ,
StartBackendTickImpl ,
2022-04-04 09:25:17 +00:00
BackendTick ,
BackendRequest { place : u8 } ,
BackendResponse ( BREnum ) ,
2022-04-08 02:42:18 +00:00
Reset ,
2022-04-29 09:30:41 +00:00
SendEmote ( EmoteEnum ) ,
SentEmote ( EmoteEnum ) ,
2022-03-15 04:16:09 +00:00
}
impl WrapperMsg {
2022-04-27 07:51:57 +00:00
fn is_ai_pressed ( & self ) -> bool {
2022-03-15 04:16:09 +00:00
matches! ( self , WrapperMsg ::AIPressed ( _ ) )
}
2022-03-02 10:19:50 +00:00
}
2022-03-02 06:18:10 +00:00
impl Component for Wrapper {
2022-03-02 10:19:50 +00:00
type Message = WrapperMsg ;
2022-03-02 06:18:10 +00:00
type Properties = ( ) ;
fn create ( _ctx : & Context < Self > ) -> Self {
2022-04-04 09:25:17 +00:00
Self {
player_id : None ,
place_request : None ,
do_backend_tick : true ,
2022-04-29 02:08:54 +00:00
cleanup_id_callback : Rc ::new ( RefCell ::new ( None ) ) ,
2022-04-30 07:27:43 +00:00
board_updated_time : None ,
2022-04-04 09:25:17 +00:00
}
2022-03-02 06:18:10 +00:00
}
fn view ( & self , ctx : & Context < Self > ) -> Html {
let ( shared , _ ) = ctx
. link ( )
. context ::< SharedState > ( Callback ::noop ( ) )
. expect ( " state to be set " ) ;
html! {
< div class = " wrapper " >
2022-03-04 07:22:30 +00:00
< MainMenu / >
2022-04-25 06:02:18 +00:00
< ResetButton / >
2022-04-29 09:30:41 +00:00
< div class = " emote_wrapper " >
< EmoteButton emote = { EmoteEnum ::Smile } / >
< EmoteButton emote = { EmoteEnum ::Neutral } / >
< EmoteButton emote = { EmoteEnum ::Frown } / >
< EmoteButton emote = { EmoteEnum ::Think } / >
< / div >
2022-03-10 06:43:03 +00:00
< Slot idx = 0 state = { shared . board [ 0 ] . clone ( ) } placed = { shared . placed [ 0 ] . clone ( ) } / >
< Slot idx = 1 state = { shared . board [ 1 ] . clone ( ) } placed = { shared . placed [ 1 ] . clone ( ) } / >
< Slot idx = 2 state = { shared . board [ 2 ] . clone ( ) } placed = { shared . placed [ 2 ] . clone ( ) } / >
< Slot idx = 3 state = { shared . board [ 3 ] . clone ( ) } placed = { shared . placed [ 3 ] . clone ( ) } / >
< Slot idx = 4 state = { shared . board [ 4 ] . clone ( ) } placed = { shared . placed [ 4 ] . clone ( ) } / >
< Slot idx = 5 state = { shared . board [ 5 ] . clone ( ) } placed = { shared . placed [ 5 ] . clone ( ) } / >
< Slot idx = 6 state = { shared . board [ 6 ] . clone ( ) } placed = { shared . placed [ 6 ] . clone ( ) } / >
< Slot idx = 7 state = { shared . board [ 7 ] . clone ( ) } placed = { shared . placed [ 7 ] . clone ( ) } / >
< Slot idx = 8 state = { shared . board [ 8 ] . clone ( ) } placed = { shared . placed [ 8 ] . clone ( ) } / >
< Slot idx = 9 state = { shared . board [ 9 ] . clone ( ) } placed = { shared . placed [ 9 ] . clone ( ) } / >
< Slot idx = 10 state = { shared . board [ 10 ] . clone ( ) } placed = { shared . placed [ 10 ] . clone ( ) } / >
< Slot idx = 11 state = { shared . board [ 11 ] . clone ( ) } placed = { shared . placed [ 11 ] . clone ( ) } / >
< Slot idx = 12 state = { shared . board [ 12 ] . clone ( ) } placed = { shared . placed [ 12 ] . clone ( ) } / >
< Slot idx = 13 state = { shared . board [ 13 ] . clone ( ) } placed = { shared . placed [ 13 ] . clone ( ) } / >
< Slot idx = 14 state = { shared . board [ 14 ] . clone ( ) } placed = { shared . placed [ 14 ] . clone ( ) } / >
< Slot idx = 15 state = { shared . board [ 15 ] . clone ( ) } placed = { shared . placed [ 15 ] . clone ( ) } / >
< Slot idx = 16 state = { shared . board [ 16 ] . clone ( ) } placed = { shared . placed [ 16 ] . clone ( ) } / >
< Slot idx = 17 state = { shared . board [ 17 ] . clone ( ) } placed = { shared . placed [ 17 ] . clone ( ) } / >
< Slot idx = 18 state = { shared . board [ 18 ] . clone ( ) } placed = { shared . placed [ 18 ] . clone ( ) } / >
< Slot idx = 19 state = { shared . board [ 19 ] . clone ( ) } placed = { shared . placed [ 19 ] . clone ( ) } / >
< Slot idx = 20 state = { shared . board [ 20 ] . clone ( ) } placed = { shared . placed [ 20 ] . clone ( ) } / >
< Slot idx = 21 state = { shared . board [ 21 ] . clone ( ) } placed = { shared . placed [ 21 ] . clone ( ) } / >
< Slot idx = 22 state = { shared . board [ 22 ] . clone ( ) } placed = { shared . placed [ 22 ] . clone ( ) } / >
< Slot idx = 23 state = { shared . board [ 23 ] . clone ( ) } placed = { shared . placed [ 23 ] . clone ( ) } / >
< Slot idx = 24 state = { shared . board [ 24 ] . clone ( ) } placed = { shared . placed [ 24 ] . clone ( ) } / >
< Slot idx = 25 state = { shared . board [ 25 ] . clone ( ) } placed = { shared . placed [ 25 ] . clone ( ) } / >
< Slot idx = 26 state = { shared . board [ 26 ] . clone ( ) } placed = { shared . placed [ 26 ] . clone ( ) } / >
< Slot idx = 27 state = { shared . board [ 27 ] . clone ( ) } placed = { shared . placed [ 27 ] . clone ( ) } / >
< Slot idx = 28 state = { shared . board [ 28 ] . clone ( ) } placed = { shared . placed [ 28 ] . clone ( ) } / >
< Slot idx = 29 state = { shared . board [ 29 ] . clone ( ) } placed = { shared . placed [ 29 ] . clone ( ) } / >
< Slot idx = 30 state = { shared . board [ 30 ] . clone ( ) } placed = { shared . placed [ 30 ] . clone ( ) } / >
< Slot idx = 31 state = { shared . board [ 31 ] . clone ( ) } placed = { shared . placed [ 31 ] . clone ( ) } / >
< Slot idx = 32 state = { shared . board [ 32 ] . clone ( ) } placed = { shared . placed [ 32 ] . clone ( ) } / >
< Slot idx = 33 state = { shared . board [ 33 ] . clone ( ) } placed = { shared . placed [ 33 ] . clone ( ) } / >
< Slot idx = 34 state = { shared . board [ 34 ] . clone ( ) } placed = { shared . placed [ 34 ] . clone ( ) } / >
< Slot idx = 35 state = { shared . board [ 35 ] . clone ( ) } placed = { shared . placed [ 35 ] . clone ( ) } / >
< Slot idx = 36 state = { shared . board [ 36 ] . clone ( ) } placed = { shared . placed [ 36 ] . clone ( ) } / >
< Slot idx = 37 state = { shared . board [ 37 ] . clone ( ) } placed = { shared . placed [ 37 ] . clone ( ) } / >
< Slot idx = 38 state = { shared . board [ 38 ] . clone ( ) } placed = { shared . placed [ 38 ] . clone ( ) } / >
< Slot idx = 39 state = { shared . board [ 39 ] . clone ( ) } placed = { shared . placed [ 39 ] . clone ( ) } / >
< Slot idx = 40 state = { shared . board [ 40 ] . clone ( ) } placed = { shared . placed [ 40 ] . clone ( ) } / >
< Slot idx = 41 state = { shared . board [ 41 ] . clone ( ) } placed = { shared . placed [ 41 ] . clone ( ) } / >
< Slot idx = 42 state = { shared . board [ 42 ] . clone ( ) } placed = { shared . placed [ 42 ] . clone ( ) } / >
< Slot idx = 43 state = { shared . board [ 43 ] . clone ( ) } placed = { shared . placed [ 43 ] . clone ( ) } / >
< Slot idx = 44 state = { shared . board [ 44 ] . clone ( ) } placed = { shared . placed [ 44 ] . clone ( ) } / >
< Slot idx = 45 state = { shared . board [ 45 ] . clone ( ) } placed = { shared . placed [ 45 ] . clone ( ) } / >
< Slot idx = 46 state = { shared . board [ 46 ] . clone ( ) } placed = { shared . placed [ 46 ] . clone ( ) } / >
< Slot idx = 47 state = { shared . board [ 47 ] . clone ( ) } placed = { shared . placed [ 47 ] . clone ( ) } / >
< Slot idx = 48 state = { shared . board [ 48 ] . clone ( ) } placed = { shared . placed [ 48 ] . clone ( ) } / >
< Slot idx = 49 state = { shared . board [ 49 ] . clone ( ) } placed = { shared . placed [ 49 ] . clone ( ) } / >
< Slot idx = 50 state = { shared . board [ 50 ] . clone ( ) } placed = { shared . placed [ 50 ] . clone ( ) } / >
< Slot idx = 51 state = { shared . board [ 51 ] . clone ( ) } placed = { shared . placed [ 51 ] . clone ( ) } / >
< Slot idx = 52 state = { shared . board [ 52 ] . clone ( ) } placed = { shared . placed [ 52 ] . clone ( ) } / >
< Slot idx = 53 state = { shared . board [ 53 ] . clone ( ) } placed = { shared . placed [ 53 ] . clone ( ) } / >
< Slot idx = 54 state = { shared . board [ 54 ] . clone ( ) } placed = { shared . placed [ 54 ] . clone ( ) } / >
< Slot idx = 55 state = { shared . board [ 55 ] . clone ( ) } placed = { shared . placed [ 55 ] . clone ( ) } / >
2022-03-02 06:18:10 +00:00
< div class = " info_text_wrapper " >
2022-03-02 08:51:14 +00:00
< InfoText id = 0 / >
< / div >
2022-03-03 08:36:51 +00:00
< div class = " info_text_turn_wrapper " >
2022-03-02 08:51:14 +00:00
< InfoText id = 1 / >
2022-03-02 06:18:10 +00:00
< / div >
< / div > // wrapper
}
}
2022-03-02 07:25:01 +00:00
fn update ( & mut self , ctx : & Context < Self > , msg : Self ::Message ) -> bool {
2022-03-02 06:18:10 +00:00
let ( shared , _ ) = ctx
. link ( )
. context ::< SharedState > ( Callback ::noop ( ) )
. expect ( " state to be set " ) ;
2022-03-15 04:16:09 +00:00
let ( _window , document ) =
2022-03-09 08:29:53 +00:00
get_window_document ( ) . expect ( " Should be able to get Window and Document " ) ;
2022-03-02 08:51:14 +00:00
2022-03-02 10:19:50 +00:00
match msg {
2022-03-15 04:16:09 +00:00
WrapperMsg ::Pressed ( idx ) | WrapperMsg ::AIPressed ( idx ) = > {
2022-03-02 08:51:14 +00:00
let mut bottom_idx = idx ;
let mut placed = false ;
let current_player = shared . turn . get ( ) ;
2022-03-15 04:16:09 +00:00
// check if player can make a move
if ! msg . is_ai_pressed ( ) {
2022-04-27 03:47:45 +00:00
match shared . game_state . borrow ( ) . clone ( ) {
2022-03-15 04:16:09 +00:00
GameState ::MainMenu = > ( ) ,
GameState ::SinglePlayer ( turn , _ ) = > {
if current_player ! = turn {
return false ;
}
}
GameState ::LocalMultiplayer = > ( ) ,
2022-04-04 09:25:17 +00:00
GameState ::NetworkedMultiplayer {
paired ,
current_side ,
current_turn ,
2022-04-27 03:47:45 +00:00
phrase : _ ,
2022-04-04 09:25:17 +00:00
} = > {
2022-04-06 09:43:17 +00:00
if paired {
if let Some ( current_side ) = current_side {
if current_side = = current_turn {
self . place_request . replace ( idx ) ;
}
}
}
return true ;
2022-03-15 04:16:09 +00:00
}
GameState ::PostGameResults ( _ ) = > ( ) ,
}
}
2022-03-02 08:51:14 +00:00
// check if clicked on empty slot
2022-03-03 09:01:46 +00:00
if shared . board [ idx as usize ] . get ( ) . is_empty ( ) {
2022-03-02 08:51:14 +00:00
// get bottom-most empty slot
while bottom_idx + COLS < ROWS * COLS
2022-03-03 09:01:46 +00:00
& & shared . board [ ( bottom_idx + COLS ) as usize ] . get ( ) . is_empty ( )
2022-03-02 08:51:14 +00:00
{
bottom_idx + = COLS ;
}
// apply current player's color to bottom-most empty slot
2022-03-03 09:01:46 +00:00
shared . board [ bottom_idx as usize ] . replace ( shared . turn . get ( ) . into ( ) ) ;
2022-03-02 08:51:14 +00:00
let current_board_state = shared . board [ bottom_idx as usize ] . get ( ) ;
// swap turn
2022-03-03 09:01:46 +00:00
shared . turn . replace ( shared . turn . get ( ) . get_opposite ( ) ) ;
2022-03-02 07:25:01 +00:00
2022-03-02 08:51:14 +00:00
// get handle to slot
2022-03-03 05:08:35 +00:00
if let Some ( slot ) = document . get_element_by_id ( & format! ( " slot {bottom_idx} " ) ) {
2022-03-02 08:51:14 +00:00
// set slot info
slot . set_class_name ( & format! (
2022-03-10 06:43:03 +00:00
" slot {} r{} c{} placed " ,
2022-03-02 08:51:14 +00:00
current_board_state ,
bottom_idx / COLS ,
bottom_idx % COLS
) ) ;
2022-03-10 06:43:03 +00:00
shared . placed [ bottom_idx as usize ] . replace ( true ) ;
2022-03-02 08:51:14 +00:00
}
placed = true ;
}
2022-03-10 09:16:30 +00:00
// info text below the grid
{
let output_str = match placed {
true = > format! ( " {} placed into slot {} " , current_player , bottom_idx ) ,
false = > " Invalid place to insert " . into ( ) ,
} ;
let text_append_result = append_to_info_text (
& document ,
" info_text0 " ,
& output_str ,
INFO_TEXT_MAX_ITEMS ,
) ;
if let Err ( e ) = text_append_result {
log ::warn! ( " ERROR: text append to info_text0 failed: {} " , e ) ;
}
}
// info text right of the grid
{
let turn = shared . turn . get ( ) ;
2022-04-27 03:47:45 +00:00
let output_str = if let GameState ::SinglePlayer ( player_turn , _ ) =
shared . game_state . borrow ( ) . clone ( )
{
if shared . turn . get ( ) = = player_turn {
format! (
" <b class= \" {} \" >It is {}'s (player) turn</b> " ,
turn . get_color ( ) ,
turn
)
2022-03-15 04:16:09 +00:00
} else {
format! (
2022-04-27 03:47:45 +00:00
" <b class= \" {} \" >It is {}'s (ai) turn</b> " ,
2022-03-15 04:16:09 +00:00
turn . get_color ( ) ,
turn
)
2022-04-27 03:47:45 +00:00
}
} else {
format! (
" <b class= \" {} \" >It is {}'s Turn</b> " ,
turn . get_color ( ) ,
turn
)
} ;
2022-03-10 09:16:30 +00:00
let text_append_result =
append_to_info_text ( & document , " info_text1 " , & output_str , 1 ) ;
if let Err ( e ) = text_append_result {
log ::warn! ( " ERROR: text append to info_text1 failed: {} " , e ) ;
}
}
2022-03-09 08:29:53 +00:00
// check for win
let check_win_draw_opt = check_win_draw ( & shared . board ) ;
2022-03-09 09:10:13 +00:00
if let Some ( ( endgame_state , win_type ) ) = check_win_draw_opt {
2022-03-09 08:29:53 +00:00
if endgame_state = = BoardState ::Empty {
// draw
let text_append_result = append_to_info_text (
& document ,
" info_text0 " ,
" Game ended in a draw " ,
INFO_TEXT_MAX_ITEMS ,
) ;
if let Err ( e ) = text_append_result {
log ::warn! ( " ERROR: text append to info_text0 failed: {} " , e ) ;
}
2022-03-10 08:01:01 +00:00
shared
. game_state
. replace ( GameState ::PostGameResults ( BoardState ::Empty ) ) ;
2022-03-09 08:29:53 +00:00
} else {
// a player won
let turn = Turn ::from ( endgame_state ) ;
2022-04-06 09:43:17 +00:00
let text_string = format! (
" <b class= \" {} \" >{} has won the game</b> " ,
turn . get_color ( ) ,
turn
) ;
2022-03-09 08:29:53 +00:00
let text_append_result = append_to_info_text (
& document ,
" info_text0 " ,
& text_string ,
INFO_TEXT_MAX_ITEMS ,
) ;
if let Err ( e ) = text_append_result {
log ::warn! ( " ERROR: text append to info_text0 failed: {} " , e ) ;
}
2022-03-09 09:10:13 +00:00
2022-03-10 08:01:01 +00:00
shared
. game_state
. replace ( GameState ::PostGameResults ( turn . into ( ) ) ) ;
2022-03-09 09:10:13 +00:00
match win_type {
WinType ::Horizontal ( idx ) = > {
2022-03-10 06:43:03 +00:00
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 1 ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 2 ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 3 ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
2022-03-09 09:10:13 +00:00
let append_result =
element_append_class ( & document , & format! ( " slot {} " , idx ) , " win " ) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
& format! ( " slot {} " , idx + 1 ) ,
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
& format! ( " slot {} " , idx + 2 ) ,
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
& format! ( " slot {} " , idx + 3 ) ,
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
shared . board [ idx ] . replace ( shared . board [ idx ] . get ( ) . into_win ( ) ) ;
shared . board [ idx + 1 ]
. replace ( shared . board [ idx + 1 ] . get ( ) . into_win ( ) ) ;
shared . board [ idx + 2 ]
. replace ( shared . board [ idx + 2 ] . get ( ) . into_win ( ) ) ;
shared . board [ idx + 3 ]
. replace ( shared . board [ idx + 3 ] . get ( ) . into_win ( ) ) ;
}
WinType ::Vertical ( idx ) = > {
2022-03-10 06:43:03 +00:00
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + ( COLS as usize ) ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 2 * ( COLS as usize ) ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 3 * ( COLS as usize ) ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
2022-03-09 09:10:13 +00:00
let append_result =
element_append_class ( & document , & format! ( " slot {} " , idx ) , " win " ) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
2022-03-09 09:14:01 +00:00
& format! ( " slot {} " , idx + ( COLS as usize ) ) ,
2022-03-09 09:10:13 +00:00
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
& format! ( " slot {} " , idx + 2 * ( COLS as usize ) ) ,
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
& format! ( " slot {} " , idx + 3 * ( COLS as usize ) ) ,
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
shared . board [ idx ] . replace ( shared . board [ idx ] . get ( ) . into_win ( ) ) ;
2022-03-09 09:14:01 +00:00
shared . board [ idx + ( COLS as usize ) ]
. replace ( shared . board [ idx + ( COLS as usize ) ] . get ( ) . into_win ( ) ) ;
2022-03-09 09:10:13 +00:00
shared . board [ idx + 2 * ( COLS as usize ) ] . replace (
shared . board [ idx + 2 * ( COLS as usize ) ] . get ( ) . into_win ( ) ,
) ;
shared . board [ idx + 3 * ( COLS as usize ) ] . replace (
shared . board [ idx + 3 * ( COLS as usize ) ] . get ( ) . into_win ( ) ,
) ;
}
WinType ::DiagonalUp ( idx ) = > {
2022-03-10 06:43:03 +00:00
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 1 - ( COLS as usize ) ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 2 - 2 * ( COLS as usize ) ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 3 - 3 * ( COLS as usize ) ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
2022-03-09 09:10:13 +00:00
let append_result =
element_append_class ( & document , & format! ( " slot {} " , idx ) , " win " ) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
2022-03-09 09:14:01 +00:00
& format! ( " slot {} " , idx + 1 - ( COLS as usize ) ) ,
2022-03-09 09:10:13 +00:00
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
& format! ( " slot {} " , idx + 2 - 2 * ( COLS as usize ) ) ,
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
& format! ( " slot {} " , idx + 3 - 3 * ( COLS as usize ) ) ,
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
shared . board [ idx ] . replace ( shared . board [ idx ] . get ( ) . into_win ( ) ) ;
2022-03-09 09:14:01 +00:00
shared . board [ idx + 1 - ( COLS as usize ) ] . replace (
shared . board [ idx + 1 - ( COLS as usize ) ] . get ( ) . into_win ( ) ,
2022-03-09 09:10:13 +00:00
) ;
shared . board [ idx + 2 - 2 * ( COLS as usize ) ] . replace (
shared . board [ idx + 2 - 2 * ( COLS as usize ) ] . get ( ) . into_win ( ) ,
) ;
shared . board [ idx + 3 - 3 * ( COLS as usize ) ] . replace (
shared . board [ idx + 3 - 3 * ( COLS as usize ) ] . get ( ) . into_win ( ) ,
) ;
}
WinType ::DiagonalDown ( idx ) = > {
2022-03-10 06:43:03 +00:00
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 1 + ( COLS as usize ) ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 2 + 2 * ( COLS as usize ) ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
let placed_class_erase_result = element_remove_class (
& document ,
& format! ( " slot {} " , idx + 3 + 3 * ( COLS as usize ) ) ,
" placed " ,
) ;
if let Err ( e ) = placed_class_erase_result {
log ::warn! ( " ERROR: element_remove_class failed: {} " , e ) ;
}
2022-03-09 09:10:13 +00:00
let append_result =
element_append_class ( & document , & format! ( " slot {} " , idx ) , " win " ) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
2022-03-09 09:14:01 +00:00
& format! ( " slot {} " , idx + 1 + ( COLS as usize ) ) ,
2022-03-09 09:10:13 +00:00
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
& format! ( " slot {} " , idx + 2 + 2 * ( COLS as usize ) ) ,
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
let append_result = element_append_class (
& document ,
& format! ( " slot {} " , idx + 3 + 3 * ( COLS as usize ) ) ,
" win " ,
) ;
if let Err ( e ) = append_result {
log ::warn! ( " ERROR: element_append_class failed: {} " , e ) ;
}
shared . board [ idx ] . replace ( shared . board [ idx ] . get ( ) . into_win ( ) ) ;
2022-03-09 09:14:01 +00:00
shared . board [ idx + 1 + ( COLS as usize ) ] . replace (
shared . board [ idx + 1 + ( COLS as usize ) ] . get ( ) . into_win ( ) ,
2022-03-09 09:10:13 +00:00
) ;
shared . board [ idx + 2 + 2 * ( COLS as usize ) ] . replace (
shared . board [ idx + 2 + 2 * ( COLS as usize ) ] . get ( ) . into_win ( ) ,
) ;
shared . board [ idx + 3 + 3 * ( COLS as usize ) ] . replace (
shared . board [ idx + 3 + 3 * ( COLS as usize ) ] . get ( ) . into_win ( ) ,
) ;
}
2022-03-10 08:01:01 +00:00
WinType ::None = > unreachable! ( " WinType should never be None on win " ) ,
2022-03-09 09:10:13 +00:00
}
2022-03-02 08:51:14 +00:00
}
2022-03-09 08:29:53 +00:00
let text_append_result =
append_to_info_text ( & document , " info_text1 " , " <b>Game Over</b> " , 1 ) ;
if let Err ( e ) = text_append_result {
log ::warn! ( " ERROR: text append to info_text1 failed: {} " , e ) ;
2022-03-02 08:51:14 +00:00
}
2022-03-02 07:25:01 +00:00
2022-03-09 08:29:53 +00:00
shared
. game_state
. replace ( GameState ::PostGameResults ( endgame_state ) ) ;
2022-03-10 09:16:30 +00:00
} // if: check for win or draw
2022-03-10 07:17:16 +00:00
// check if it is AI's turn
2022-03-15 04:16:09 +00:00
if let GameState ::SinglePlayer ( player_type , _ai_difficulty ) =
2022-04-27 03:47:45 +00:00
shared . game_state . borrow ( ) . clone ( )
2022-03-15 04:16:09 +00:00
{
if shared . turn . get ( ) ! = player_type {
ctx . link ( ) . send_message ( WrapperMsg ::AIChoice ) ;
}
}
} // WrapperMsg::Pressed(idx) =>
WrapperMsg ::AIChoice = > {
// defer by 1 second
2022-04-04 09:25:17 +00:00
self . defer_message ( ctx , WrapperMsg ::AIChoiceImpl , AI_CHOICE_DURATION_MILLIS ) ;
2022-03-15 04:16:09 +00:00
}
WrapperMsg ::AIChoiceImpl = > {
// get AI's choice
2022-04-27 03:47:45 +00:00
if let GameState ::SinglePlayer ( player_type , ai_difficulty ) =
shared . game_state . borrow ( ) . clone ( )
2022-03-10 07:17:16 +00:00
{
if shared . turn . get ( ) ! = player_type {
2022-03-10 08:01:01 +00:00
let choice =
get_ai_choice ( ai_difficulty , player_type . get_opposite ( ) , & shared . board )
. expect ( " AI should have an available choice " ) ;
2022-03-10 07:17:16 +00:00
ctx . link ( )
2022-03-15 04:16:09 +00:00
. send_message ( WrapperMsg ::AIPressed ( usize ::from ( choice ) as u8 ) ) ;
2022-03-10 07:17:16 +00:00
}
}
2022-03-15 04:16:09 +00:00
}
2022-04-25 06:02:18 +00:00
WrapperMsg ::StartBackendTick = > {
self . defer_message (
ctx ,
WrapperMsg ::StartBackendTickImpl ,
BACKEND_TICK_DURATION_MILLIS ,
) ;
}
WrapperMsg ::StartBackendTickImpl = > {
// If previous id is still stored, request disconnect so that a
// new id can be received
2022-04-29 02:37:15 +00:00
self . send_disconnect ( ) ;
2022-04-25 06:02:18 +00:00
self . do_backend_tick = true ;
ctx . link ( ) . send_message ( WrapperMsg ::BackendTick ) ;
}
2022-04-04 09:25:17 +00:00
WrapperMsg ::BackendTick = > {
2022-04-27 03:47:45 +00:00
let is_networked_multiplayer =
shared . game_state . borrow ( ) . is_networked_multiplayer ( ) ;
if ! self . do_backend_tick | | ! is_networked_multiplayer {
2022-04-29 02:37:15 +00:00
self . send_disconnect ( ) ;
2022-04-29 03:11:49 +00:00
self . cleanup_disconnect_callbacks ( ) ;
2022-04-06 11:17:27 +00:00
return false ;
}
2022-04-04 09:25:17 +00:00
if self . player_id . is_none ( ) {
self . get_networked_player_id ( ctx ) ;
2022-04-06 09:43:17 +00:00
} else if shared
. game_state
2022-04-27 03:47:45 +00:00
. borrow ( )
2022-04-06 09:43:17 +00:00
. get_networked_current_side ( )
. is_none ( )
{
self . get_networked_player_type ( ctx ) ;
2022-04-27 03:47:45 +00:00
} else if ! matches! (
shared . game_state . borrow ( ) . clone ( ) ,
GameState ::PostGameResults ( _ )
) {
2022-04-06 09:43:17 +00:00
if self . place_request . is_some ( ) {
let placement = self . place_request . take ( ) . unwrap ( ) ;
self . send_place_request ( ctx , placement ) ;
} else {
self . get_game_status ( ctx ) ;
}
} else {
self . do_backend_tick = false ;
log ::warn! ( " Ended backend tick " ) ;
2022-04-04 09:25:17 +00:00
}
// repeat BackendTick handling while "connected" to backend
if self . do_backend_tick {
self . defer_message ( ctx , WrapperMsg ::BackendTick , BACKEND_TICK_DURATION_MILLIS ) ;
}
}
WrapperMsg ::BackendRequest { place } = > {
self . place_request = Some ( place ) ;
}
2022-04-06 10:38:37 +00:00
WrapperMsg ::BackendResponse ( br_enum ) = > {
match br_enum {
BREnum ::Error ( string ) = > {
// TODO maybe suppress this for release builds
log ::warn! ( " {} " , string ) ;
2022-04-06 09:43:17 +00:00
}
2022-04-06 10:38:37 +00:00
BREnum ::GotID ( id , turn_opt ) = > {
2022-04-29 02:08:54 +00:00
self . cleanup_disconnect_callbacks ( ) ;
2022-04-08 03:01:15 +00:00
// set reset and disconnect on page "unload"
let player_id = id ;
2022-04-29 02:08:54 +00:00
let listener_function : Rc < RefCell < Option < Function > > > =
self . cleanup_id_callback . clone ( ) ;
2022-04-08 03:01:15 +00:00
ctx . link ( ) . send_future ( async move {
2022-04-29 02:08:54 +00:00
let listener_function = listener_function ;
2022-04-08 03:01:15 +00:00
let promise =
2022-04-29 02:08:54 +00:00
Promise ::new ( & mut move | resolve : js_sys ::Function , _reject | {
2022-04-08 03:01:15 +00:00
let window =
web_sys ::window ( ) . expect ( " Should be able to get window " ) ;
2022-04-08 03:17:15 +00:00
let outer_function = Function ::new_with_args (
" resolve " ,
& format! (
"
let xhr = new XMLHttpRequest ( ) ;
xhr . open ( ' POST ' , ' { } ' ) ;
xhr . send ( ' { { \ " type \" : \" disconnect \" , \" id \" : {}}}');
resolve ( ) ;
" ,
BACKEND_URL , player_id
) ,
) ;
let binded_func =
outer_function . bind1 ( & outer_function , & resolve ) ;
2022-04-29 02:08:54 +00:00
listener_function . replace ( Some ( binded_func ) ) ;
2022-04-08 03:01:15 +00:00
window
2022-04-08 03:41:00 +00:00
. add_event_listener_with_callback_and_add_event_listener_options (
" pagehide " ,
2022-04-29 02:08:54 +00:00
listener_function . borrow ( ) . as_ref ( ) . unwrap ( ) ,
2022-04-08 03:41:00 +00:00
AddEventListenerOptions ::new ( ) . capture ( true ) . once ( true )
)
2022-04-08 03:01:15 +00:00
. expect ( " Should be able to set \" pagehide \" callback " ) ;
window
2022-04-08 03:41:00 +00:00
. add_event_listener_with_callback_and_add_event_listener_options (
2022-04-08 03:17:15 +00:00
" beforeunload " ,
2022-04-29 02:08:54 +00:00
listener_function . borrow ( ) . as_ref ( ) . unwrap ( ) ,
2022-04-08 03:41:00 +00:00
AddEventListenerOptions ::new ( ) . capture ( true ) . once ( true )
2022-04-08 03:17:15 +00:00
)
2022-04-08 03:01:15 +00:00
. expect ( " Should be able to set \" beforeunload \" callback " ) ;
} ) ;
let js_fut = JsFuture ::from ( promise ) ;
js_fut . await . ok ( ) ;
WrapperMsg ::Reset
} ) ;
2022-04-06 10:38:37 +00:00
self . player_id = Some ( id ) ;
2022-04-27 03:47:45 +00:00
let mut game_state = shared . game_state . borrow ( ) . clone ( ) ;
2022-04-06 10:38:37 +00:00
game_state . set_networked_paired ( ) ;
game_state . set_networked_current_side ( turn_opt ) ;
2022-04-27 03:47:45 +00:00
shared . game_state . replace ( game_state ) ;
2022-04-06 10:38:37 +00:00
if let Some ( turn_type ) = turn_opt {
2022-04-06 09:43:17 +00:00
append_to_info_text (
& document ,
2022-04-06 10:38:37 +00:00
" info_text0 " ,
& format! (
" <b class= \" {} \" >Paired with player, you are the {}</b> " ,
turn_type . get_color ( ) ,
turn_type
) ,
INFO_TEXT_MAX_ITEMS ,
2022-04-06 09:43:17 +00:00
)
. ok ( ) ;
append_to_info_text (
& document ,
" info_text1 " ,
2022-04-06 10:38:37 +00:00
& format! (
" <b class= \" cyan \" >It is CyanPlayer's ({}) Turn</b> " ,
if turn_type = = Turn ::CyanPlayer {
" your "
} else {
" opponent's "
}
) ,
2022-04-06 09:43:17 +00:00
1 ,
)
. ok ( ) ;
}
2022-04-06 10:38:37 +00:00
}
BREnum ::GotPairing ( turn_opt ) = > {
2022-04-27 03:47:45 +00:00
let mut game_state = shared . game_state . borrow ( ) . clone ( ) ;
2022-04-06 10:38:37 +00:00
game_state . set_networked_current_side ( turn_opt ) ;
2022-04-27 03:47:45 +00:00
shared . game_state . replace ( game_state ) ;
2022-04-06 10:38:37 +00:00
if let Some ( turn_type ) = turn_opt {
2022-04-06 09:43:17 +00:00
append_to_info_text (
& document ,
2022-04-06 10:38:37 +00:00
" info_text0 " ,
& format! (
" <b class= \" {} \" >Paired with player, you are the {}</b> " ,
turn_type . get_color ( ) ,
turn_type
) ,
INFO_TEXT_MAX_ITEMS ,
2022-04-06 09:43:17 +00:00
)
. ok ( ) ;
append_to_info_text (
& document ,
" info_text1 " ,
2022-04-06 10:38:37 +00:00
& format! (
" <b class= \" cyan \" >It is CyanPlayer's ({}) Turn</b> " ,
if turn_type = = Turn ::CyanPlayer {
" your "
} else {
" opponent's "
}
) ,
2022-04-06 09:43:17 +00:00
1 ,
)
. ok ( ) ;
}
2022-04-06 10:38:37 +00:00
}
2022-04-30 07:27:43 +00:00
BREnum ::GotStatus {
networked_game_state ,
board_string ,
received_emote ,
updated_time ,
} = > {
2022-04-29 09:30:41 +00:00
let current_side = shared
. game_state
. borrow ( )
. get_networked_current_side ( )
. expect ( " Should be Networked mode " ) ;
2022-04-30 07:27:43 +00:00
if let Some ( emote_string ) = received_emote {
2022-04-29 09:30:41 +00:00
if let Ok ( emote_enum ) = EmoteEnum ::try_from ( emote_string . as_str ( ) ) {
append_to_info_text (
& document ,
" info_text0 " ,
& format! (
2022-04-29 10:21:59 +00:00
" <b class= \" {} \" >{} sent <b class= \" emote \" >{}</b></b> " ,
2022-04-29 09:30:41 +00:00
current_side . get_opposite ( ) . get_color ( ) ,
current_side . get_opposite ( ) ,
emote_enum . get_unicode ( )
) ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
} else {
append_to_info_text (
& document ,
" info_text0 " ,
& format! (
" <b class= \" {} \" >{} sent invalid emote</b> " ,
current_side . get_color ( ) ,
current_side . get_opposite ( )
) ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
}
}
2022-04-30 07:27:43 +00:00
// only update board string if updated_time is different
if self . board_updated_time ! = updated_time {
if let Some ( updated_time ) = updated_time {
self . board_updated_time . replace ( updated_time ) ;
if let Some ( board_string ) = board_string {
self . update_board_from_string ( & shared , & document , board_string ) ;
}
}
2022-04-06 09:43:17 +00:00
}
2022-04-06 10:38:37 +00:00
2022-04-27 03:47:45 +00:00
let mut current_game_state : GameState = shared . game_state . borrow ( ) . clone ( ) ;
2022-04-06 10:38:37 +00:00
match networked_game_state {
NetworkedGameState ::CyanTurn = > {
if current_game_state . get_current_turn ( ) ! = Turn ::CyanPlayer {
current_game_state . set_networked_current_turn ( Turn ::CyanPlayer ) ;
2022-04-27 03:47:45 +00:00
shared . game_state . replace ( current_game_state . clone ( ) ) ;
2022-04-06 10:38:37 +00:00
append_to_info_text (
& document ,
" info_text1 " ,
& format! (
" <b class= \" cyan \" >It is CyanPlayer's ({}) Turn</b> " ,
if current_game_state
2022-04-29 09:30:41 +00:00
. get_networked_current_side ( )
2022-04-06 10:38:37 +00:00
. unwrap_or ( Turn ::CyanPlayer )
= = Turn ::CyanPlayer
{
" your "
} else {
" opponent's "
}
) ,
1 ,
)
. ok ( ) ;
}
}
NetworkedGameState ::MagentaTurn = > {
if current_game_state . get_current_turn ( ) ! = Turn ::MagentaPlayer {
current_game_state
. set_networked_current_turn ( Turn ::MagentaPlayer ) ;
2022-04-27 03:47:45 +00:00
shared . game_state . replace ( current_game_state . clone ( ) ) ;
2022-04-06 10:38:37 +00:00
append_to_info_text (
2022-04-06 09:43:17 +00:00
& document ,
" info_text1 " ,
2022-04-06 10:38:37 +00:00
& format! (
" <b class= \" magenta \" >It is MagentaPlayer's ({}) Turn</b> " ,
2022-04-29 09:30:41 +00:00
if current_game_state . get_networked_current_side ( ) . unwrap_or ( Turn ::CyanPlayer ) = = Turn ::MagentaPlayer
2022-04-06 10:38:37 +00:00
{
" your "
} else {
" opponent's "
} ) ,
2022-04-06 09:43:17 +00:00
1 ,
)
. ok ( ) ;
2022-04-06 10:38:37 +00:00
}
}
2022-04-06 09:43:17 +00:00
NetworkedGameState ::CyanWon = > {
append_to_info_text (
& document ,
" info_text1 " ,
2022-04-06 10:38:37 +00:00
" <b class= \" cyan \" >CyanPlayer won the game</b> " ,
2022-04-06 09:43:17 +00:00
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::CyanWin ) ) ;
2022-04-06 09:43:17 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::MagentaWon = > {
append_to_info_text (
& document ,
" info_text1 " ,
2022-04-06 10:38:37 +00:00
" <b class= \" magenta \" >MagentaPlayer won the game</b> " ,
2022-04-06 09:43:17 +00:00
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::MagentaWin ) ) ;
2022-04-06 09:43:17 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::Draw = > {
append_to_info_text (
& document ,
" info_text1 " ,
" <b>The game ended in a draw</b> " ,
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::Empty ) ) ;
2022-04-06 09:43:17 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::Disconnected = > {
2022-04-25 06:02:18 +00:00
append_to_info_text (
& document ,
" info_text0 " ,
" The opponent disconnected " ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
2022-04-06 09:43:17 +00:00
append_to_info_text (
& document ,
" info_text1 " ,
" <b>The opponent disconnected</b> " ,
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::Empty ) ) ;
2022-04-06 09:43:17 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::InternalError = > {
append_to_info_text (
& document ,
" info_text1 " ,
" <b>There was an internal error</b> " ,
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::Empty ) ) ;
2022-04-06 09:43:17 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::NotPaired = > ( ) ,
NetworkedGameState ::UnknownID = > {
append_to_info_text (
& document ,
" info_text1 " ,
" <b>The game has ended (disconnected?)</b> " ,
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::Empty ) ) ;
2022-04-06 09:43:17 +00:00
self . do_backend_tick = false ;
}
2022-04-06 10:38:37 +00:00
}
}
BREnum ::GotPlaced ( placed_status , board_string ) = > {
self . update_board_from_string ( & shared , & document , board_string ) ;
match placed_status {
PlacedEnum ::Accepted = > {
// noop, handled by update_board_from_string
}
PlacedEnum ::Illegal = > {
append_to_info_text (
& document ,
" info_text0 " ,
" <b>Cannot place a token there</b> " ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
}
PlacedEnum ::NotYourTurn = > {
append_to_info_text (
& document ,
" info_text0 " ,
" <b>Cannot place a token, not your turn</b> " ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
}
PlacedEnum ::Other ( networked_game_state ) = > match networked_game_state {
NetworkedGameState ::CyanTurn = > ( ) ,
NetworkedGameState ::MagentaTurn = > ( ) ,
NetworkedGameState ::CyanWon = > {
append_to_info_text (
& document ,
" info_text1 " ,
" <b class= \" cyan \" >CyanPlayer has won the game</b> " ,
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::CyanWin ) ) ;
2022-04-06 10:38:37 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::MagentaWon = > {
append_to_info_text (
& document ,
" info_text1 " ,
" <b class= \" magenta \" >MagentaPlayer has won the game</b> " ,
1 ,
)
. ok ( ) ;
2022-04-27 03:47:45 +00:00
shared . game_state . replace ( GameState ::PostGameResults (
BoardState ::MagentaWin ,
) ) ;
2022-04-06 10:38:37 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::Draw = > {
append_to_info_text (
& document ,
" info_text1 " ,
" <b>The game ended in a draw</b> " ,
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::Empty ) ) ;
2022-04-06 10:38:37 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::Disconnected = > {
append_to_info_text (
& document ,
" info_text1 " ,
" <b>The opponent disconnected</b> " ,
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::Empty ) ) ;
2022-04-06 10:38:37 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::InternalError = > {
append_to_info_text (
& document ,
" info_text1 " ,
" <b>There was an internal error</b> " ,
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::Empty ) ) ;
2022-04-06 10:38:37 +00:00
self . do_backend_tick = false ;
}
NetworkedGameState ::NotPaired = > ( ) ,
NetworkedGameState ::UnknownID = > {
append_to_info_text (
& document ,
" info_text1 " ,
" <b>The game has ended (disconnected?)</b> " ,
1 ,
)
. ok ( ) ;
shared
. game_state
2022-04-27 03:47:45 +00:00
. replace ( GameState ::PostGameResults ( BoardState ::Empty ) ) ;
2022-04-06 10:38:37 +00:00
self . do_backend_tick = false ;
}
} ,
}
2022-04-06 09:43:17 +00:00
}
2022-04-05 09:16:04 +00:00
}
2022-04-06 10:38:37 +00:00
}
2022-04-08 02:42:18 +00:00
WrapperMsg ::Reset = > {
2022-04-27 03:47:45 +00:00
shared . game_state . replace ( GameState ::default ( ) ) ;
2022-04-08 02:42:18 +00:00
shared . turn . set ( Turn ::CyanPlayer ) ;
for idx in 0 .. ( ( ROWS * COLS ) as usize ) {
shared . placed [ idx ] . set ( false ) ;
shared . board [ idx ] . set ( BoardState ::Empty ) ;
element_remove_class ( & document , & format! ( " slot {} " , idx ) , " open " ) . ok ( ) ;
element_remove_class ( & document , & format! ( " slot {} " , idx ) , " placed " ) . ok ( ) ;
element_remove_class ( & document , & format! ( " slot {} " , idx ) , " win " ) . ok ( ) ;
element_remove_class ( & document , & format! ( " slot {} " , idx ) , " cyan " ) . ok ( ) ;
element_remove_class ( & document , & format! ( " slot {} " , idx ) , " magenta " ) . ok ( ) ;
element_append_class ( & document , & format! ( " slot {} " , idx ) , " open " ) . ok ( ) ;
}
2022-04-29 02:37:15 +00:00
self . send_disconnect ( ) ;
2022-04-08 02:42:18 +00:00
self . place_request = None ;
2022-04-25 06:02:18 +00:00
element_remove_class ( & document , " mainmenu " , " hidden_menu " ) . ok ( ) ;
element_append_class ( & document , " mainmenu " , " menu " ) . ok ( ) ;
append_to_info_text (
& document ,
" info_text1 " ,
" <b>Waiting to choose game-mode...</b> " ,
1 ,
)
. ok ( ) ;
append_to_info_text (
& document ,
" info_text0 " ,
" Reset button was pressed " ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
2022-04-08 02:42:18 +00:00
self . do_backend_tick = false ;
2022-04-29 02:08:54 +00:00
self . cleanup_disconnect_callbacks ( ) ;
2022-04-08 02:42:18 +00:00
}
2022-04-29 09:30:41 +00:00
WrapperMsg ::SendEmote ( emote ) = > {
if self . player_id . is_none ( ) {
return false ;
}
let emote = emote ;
let player_id = self . player_id . unwrap ( ) ;
ctx . link ( ) . send_future ( async move {
let mut json_entries = HashMap ::new ( ) ;
json_entries . insert ( " id " . into ( ) , format! ( " {} " , player_id ) ) ;
json_entries . insert ( " type " . into ( ) , " send_emote " . into ( ) ) ;
json_entries . insert ( " emote " . into ( ) , emote . to_string ( ) ) ;
let send_to_backend_result = send_to_backend ( json_entries ) . await ;
if let Err ( e ) = send_to_backend_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
}
let request_result : Result < SendEmoteRequestResponse , _ > =
serde_json ::from_str ( & send_to_backend_result . unwrap ( ) ) ;
if let Err ( e ) = request_result {
return WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , e ) ) ) ;
}
let response = request_result . unwrap ( ) ;
if response . status . as_str ( ) = = " ok " {
WrapperMsg ::SentEmote ( emote )
} else {
WrapperMsg ::BackendResponse ( BREnum ::Error ( format! ( " {:?} " , response . status ) ) )
}
} ) ;
}
WrapperMsg ::SentEmote ( emote ) = > {
let current_side_opt = shared . game_state . borrow ( ) . get_networked_current_side ( ) ;
if let Some ( current_side ) = current_side_opt {
append_to_info_text (
& document ,
" info_text0 " ,
& format! (
2022-04-29 10:21:59 +00:00
" <b class= \" {} \" >{} sent <b class= \" emote \" >{}</b></b> " ,
2022-04-29 09:30:41 +00:00
current_side . get_color ( ) ,
current_side ,
emote . get_unicode ( )
) ,
INFO_TEXT_MAX_ITEMS ,
)
. ok ( ) ;
}
}
2022-03-02 10:19:50 +00:00
} // match (msg)
2022-03-02 06:18:10 +00:00
true
}
}
#[ derive(Clone, Debug, PartialEq, Eq) ]
pub struct InfoText { }
2022-03-02 08:51:14 +00:00
#[ derive(Copy, Clone, Debug, PartialEq, Eq, Properties) ]
pub struct InfoTextProperties {
id : usize ,
}
2022-03-02 06:18:10 +00:00
impl Component for InfoText {
type Message = ( ) ;
2022-03-02 08:51:14 +00:00
type Properties = InfoTextProperties ;
2022-03-02 06:18:10 +00:00
fn create ( _ctx : & Context < Self > ) -> Self {
Self { }
}
fn view ( & self , ctx : & Context < Self > ) -> Html {
2022-03-04 07:22:30 +00:00
let ( shared , _ ) = ctx
. link ( )
. context ::< SharedState > ( Callback ::noop ( ) )
. expect ( " state to be set " ) ;
2022-03-03 08:36:51 +00:00
match ctx . props ( ) . id {
0 = > {
html! {
< div id = { format! ( " info_text {} " , ctx . props ( ) . id ) } class = { format! ( " info_text {} " , ctx . props ( ) . id ) } >
{ " Hello " }
< / div >
}
}
1 = > {
2022-04-27 03:47:45 +00:00
if shared . game_state . borrow ( ) . eq ( & GameState ::MainMenu ) {
2022-03-04 07:22:30 +00:00
html! {
< div id = { format! ( " info_text {} " , ctx . props ( ) . id ) } class = { format! ( " info_text {} " , ctx . props ( ) . id ) } >
< p >
< b >
{ " Waiting to choose game-mode... " }
< / b >
< / p >
< / div >
}
} else if shared . turn . get ( ) = = Turn ::CyanPlayer {
html! {
< div id = { format! ( " info_text {} " , ctx . props ( ) . id ) } class = { format! ( " info_text {} " , ctx . props ( ) . id ) } >
< p >
< b class = { " cyan " } >
{ " It is CyanPlayer's turn " }
< / b >
< / p >
< / div >
}
} else {
html! {
< div id = { format! ( " info_text {} " , ctx . props ( ) . id ) } class = { format! ( " info_text {} " , ctx . props ( ) . id ) } >
< p >
< b class = { " magenta " } >
{ " It is MagentaPlayer's turn " }
< / b >
< / p >
< / div >
}
2022-03-03 08:36:51 +00:00
}
}
_ = > {
unreachable! ( ) ;
}
2022-03-02 06:18:10 +00:00
}
}
}