use crate::ai::{get_ai_choice, AIDifficulty}; use crate::constants::{ AI_CHOICE_DURATION_MILLIS, BACKEND_TICK_DURATION_MILLIS, BACKEND_URL, COLS, INFO_TEXT_MAX_ITEMS, ROWS, }; use crate::game_logic::{check_win_draw, WinType}; use crate::html_helper::{ append_to_info_text, create_json_request, element_append_class, element_remove_class, get_window_document, }; use crate::random_helper::get_seeded_random; use crate::state::{BoardState, GameState, MainMenuMessage, SharedState, Turn}; use std::cell::Cell; use std::rc::Rc; use js_sys::{Function, Promise}; use wasm_bindgen::JsCast; use web_sys::Response; use serde_json::Value as SerdeJSONValue; use wasm_bindgen_futures::JsFuture; use yew::prelude::*; pub struct MainMenu {} impl Component for MainMenu { type Message = MainMenuMessage; type Properties = (); fn create(_ctx: &Context) -> Self { Self {} } fn view(&self, ctx: &Context) -> Html { let (shared, _) = ctx .link() .context::(Callback::noop()) .expect("state to be set"); match shared.game_state.get() { GameState::MainMenu => { let player_type: Turn; { let mut rng = get_seeded_random().expect("Random should be available"); player_type = if rng.rand_range(0..2) == 0 { Turn::CyanPlayer } else { Turn::MagentaPlayer }; } let easy_player_type = player_type; let normal_player_type = player_type; let hard_player_type = player_type; let onclick_singleplayer_easy = ctx.link().callback(move |_| { MainMenuMessage::SinglePlayer(easy_player_type, AIDifficulty::Easy) }); let onclick_singleplayer_normal = ctx.link().callback(move |_| { MainMenuMessage::SinglePlayer(normal_player_type, AIDifficulty::Normal) }); let onclick_singleplayer_hard = ctx.link().callback(move |_| { MainMenuMessage::SinglePlayer(hard_player_type, AIDifficulty::Hard) }); let onclick_local_multiplayer = ctx.link().callback(|_| MainMenuMessage::LocalMultiplayer); let onclick_networked_multiplayer = ctx .link() .callback(|_| MainMenuMessage::NetworkedMultiplayer); html! {
{"Please pick a game mode."}
} } _ => html! {
}, } } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { let (shared, _) = ctx .link() .context::(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()); if shared.game_state.get() != GameState::MainMenu { let mainmenu = document .get_element_by_id("mainmenu") .expect("mainmenu should exist"); mainmenu.set_class_name("hidden_menu"); mainmenu.set_inner_html(""); let info_text_turn = document .get_element_by_id("info_text1") .expect("info_text1 should exist"); match shared.game_state.get() { GameState::SinglePlayer(turn, _) => { if shared.turn.get() == turn { info_text_turn.set_inner_html( "

It is CyanPlayer's (player) Turn

", ); } else { info_text_turn.set_inner_html( "

It is CyanPlayer's (ai) Turn

", ); // AI player starts first ctx.link() .get_parent() .expect("Wrapper should be parent of MainMenu") .clone() .downcast::() .send_message(WrapperMsg::AIChoice); } } GameState::NetworkedMultiplayer { paired: _, current_side: _, current_turn: _, } => { // start the Wrapper Tick loop ctx.link() .get_parent() .expect("Wrapper should be a parent of MainMenu") .clone() .downcast::() .send_message(WrapperMsg::BackendTick); } _ => { info_text_turn .set_inner_html("

It is CyanPlayer's Turn

"); } } } true } } pub struct Slot {} pub enum SlotMessage { Press, } #[derive(Clone, PartialEq, Properties)] pub struct SlotProperties { idx: u8, state: Rc>, placed: Rc>, } impl Component for Slot { type Message = SlotMessage; type Properties = SlotProperties; fn create(_ctx: &Context) -> Self { Self {} } fn view(&self, ctx: &Context) -> Html { let idx = ctx.props().idx; let state = ctx.props().state.as_ref().get(); let onclick = ctx.link().callback(move |_| SlotMessage::Press); let col = idx % COLS; let row = idx / COLS; let place = if ctx.props().placed.get() && !state.is_win() { "placed" } else { "" }; ctx.props().placed.replace(false); html! { } } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { let (shared, _) = ctx .link() .context::(Callback::noop()) .expect("state to be set"); match shared.game_state.get() { GameState::MainMenu => return false, GameState::SinglePlayer(_, _) | GameState::LocalMultiplayer => (), GameState::NetworkedMultiplayer { paired, current_side, current_turn, } => { // notify Wrapper with picked slot if let Some(p) = ctx.link().get_parent() { p.clone() .downcast::() .send_message(WrapperMsg::BackendRequest { place: ctx.props().idx, }); } } GameState::PostGameResults(_) => return false, } if shared.game_state.get() == GameState::MainMenu { return false; } match msg { SlotMessage::Press => { // notify Wrapper with message let msg = WrapperMsg::Pressed(ctx.props().idx); if let Some(p) = ctx.link().get_parent() { p.clone().downcast::().send_message(msg); } } } true } } pub struct Wrapper { player_id: Option, place_request: Option, do_backend_tick: bool, } impl Wrapper { fn defer_message( &self, ctx: &Context, msg: ::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) { // make a request to get the player_id ctx.link().send_future(async { // get window let window = web_sys::window().expect("Should be able to get Window"); // get request let request = create_json_request(BACKEND_URL, "{\"type\": \"pairing_request\"}") .expect("Should be able to create the JSON request for player_id"); // send request let promise = window.fetch_with_request(&request); // get request result let jsvalue_result = JsFuture::from(promise) .await .map_err(|e| format!("{:?}", e)); if let Err(e) = jsvalue_result { return WrapperMsg::BackendResponse(BREnum::Error(format!( "ERROR jsvalue_result: {:?}", e ))); } log::warn!("{:?}", jsvalue_result.as_ref().unwrap()); // get response from request result let response_result: Result = jsvalue_result.unwrap().dyn_into(); if let Err(e) = response_result { return WrapperMsg::BackendResponse(BREnum::Error(format!( "ERROR response_result: {:?}", e ))); } let json_jsvalue_promise_result = response_result.unwrap().json(); if let Err(e) = json_jsvalue_promise_result { return WrapperMsg::BackendResponse(BREnum::Error(format!( "ERROR json_jsvalue_promise_result: {:?}", e ))); } let json_jsvalue_result = JsFuture::from(json_jsvalue_promise_result.unwrap()).await; if let Err(e) = json_jsvalue_result { return WrapperMsg::BackendResponse(BREnum::Error(format!( "ERROR json_jsvalue_result: {:?}", e ))); } let json_result = json_jsvalue_result.unwrap().into_serde(); if let Err(e) = json_result { return WrapperMsg::BackendResponse(BREnum::Error(format!( "ERROR json_result: {:?}", e ))); } // get serde json Value from result let json_value: SerdeJSONValue = json_result.unwrap(); log::warn!("{:?}", json_value); // get and check "type" in JSON let type_opt = json_value.get("type"); if type_opt.is_none() { return WrapperMsg::BackendResponse(BREnum::Error( "ERROR: No \"type\" entry in JSON".into(), )); } let json_type = type_opt.unwrap(); if let Some(type_string) = json_type.as_str() { if type_string != "pairing_response" { return WrapperMsg::BackendResponse(BREnum::Error( "ERROR: Invalid \"type\" from response JSON".into(), )); } } else { return WrapperMsg::BackendResponse(BREnum::Error( "ERROR: Missing \"type\" from response JSON".into(), )); } // get and check "id" in JSON let player_id: u32; if let Some(wrapped_player_id) = json_value.get("id") { if let Some(player_id_u64) = wrapped_player_id.as_u64() { let player_id_conv_result: Result = player_id_u64.try_into(); if player_id_conv_result.is_err() { return WrapperMsg::BackendResponse(BREnum::Error( "ERROR: \"id\" is too large".into(), )); } player_id = player_id_conv_result.unwrap(); } else { return WrapperMsg::BackendResponse(BREnum::Error( "ERROR: \"id\" is not a u64".into(), )); } } else { return WrapperMsg::BackendResponse(BREnum::Error( "ERROR: Missing \"id\" from response JSON".into(), )); } // get and check status #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum Status { Waiting, Paired, } let mut status: Status; if let Some(status_value) = json_value.get("status") { if let Some(status_str) = status_value.as_str() { if status_str == "waiting" { status = Status::Waiting; } else if status_str == "paired" { status = Status::Paired; } else { return WrapperMsg::BackendResponse(BREnum::Error( "ERROR: Got invalid \"status\" response in JSON".into(), )); } } else { return WrapperMsg::BackendResponse(BREnum::Error( "ERROR: \"status\" response in JSON is not a str".into(), )); } } else { return WrapperMsg::BackendResponse(BREnum::Error( "ERROR: \"status\" response is missing in JSON".into(), )); } // set "disconnect" callback here so that the client sends // disconnect message when the page is closed let function = Function::new_no_args(&format!( " window.onunload = function(event) {{ let request_conf = {{}}; request_conf.method = 'POST'; request_conf.headers = \"'Content-Type': 'application/json'\"; request_conf.body = \"{{ \"type\": \"disconnect\", \"id\": {} }}\"; const request = new Request('{}', request_conf); fetch(request); }}; ", player_id, BACKEND_URL )); function.call0(&function).ok(); if status == Status::Paired { // Get which side current player is on if paired // TODO return WrapperMsg::BackendResponse(BREnum::Error("ERROR: unimplemented".into())); } else { WrapperMsg::BackendResponse(BREnum::GotID(player_id, None)) } }); } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum BREnum { Error(String), GotID(u32, Option), } #[derive(Clone, Debug, PartialEq, Eq)] pub enum WrapperMsg { Pressed(u8), AIPressed(u8), AIChoice, AIChoiceImpl, BackendTick, BackendRequest { place: u8 }, BackendResponse(BREnum), } impl WrapperMsg { fn is_ai_pressed(self) -> bool { matches!(self, WrapperMsg::AIPressed(_)) } } impl Component for Wrapper { type Message = WrapperMsg; type Properties = (); fn create(_ctx: &Context) -> Self { Self { player_id: None, place_request: None, do_backend_tick: true, } } fn view(&self, ctx: &Context) -> Html { let (shared, _) = ctx .link() .context::(Callback::noop()) .expect("state to be set"); html! {
// wrapper } } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { let (shared, _) = ctx .link() .context::(Callback::noop()) .expect("state to be set"); let (_window, document) = get_window_document().expect("Should be able to get Window and Document"); match msg { 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 { paired, current_side, current_turn, } => { // TODO } GameState::PostGameResults(_) => (), } } // check if clicked on empty slot if shared.board[idx as usize].get().is_empty() { // get bottom-most empty slot while bottom_idx + COLS < ROWS * COLS && shared.board[(bottom_idx + COLS) as usize].get().is_empty() { bottom_idx += COLS; } // apply current player's color to bottom-most empty slot shared.board[bottom_idx as usize].replace(shared.turn.get().into()); let current_board_state = shared.board[bottom_idx as usize].get(); // swap turn shared.turn.replace(shared.turn.get().get_opposite()); // get handle to slot if let Some(slot) = document.get_element_by_id(&format!("slot{bottom_idx}")) { // set slot info slot.set_class_name(&format!( "slot {} r{} c{} placed", current_board_state, bottom_idx / COLS, bottom_idx % COLS )); shared.placed[bottom_idx as usize].replace(true); } placed = true; } // 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(); let output_str = if let GameState::SinglePlayer(player_turn, _) = shared.game_state.get() { if shared.turn.get() == player_turn { format!( "It is {}'s (player) turn", turn.get_color(), turn ) } else { format!( "It is {}'s (ai) turn", turn.get_color(), turn ) } } else { format!( "It is {}'s turn", turn.get_color(), turn ) }; 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); } } // check for win let check_win_draw_opt = check_win_draw(&shared.board); if let Some((endgame_state, win_type)) = check_win_draw_opt { 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); } shared .game_state .replace(GameState::PostGameResults(BoardState::Empty)); } else { // a player won let turn = Turn::from(endgame_state); let text_string = format!("{} has won", turn.get_color(), turn); 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); } shared .game_state .replace(GameState::PostGameResults(turn.into())); match win_type { WinType::Horizontal(idx) => { 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); } 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) => { 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); } 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 + (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 + 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()); shared.board[idx + (COLS as usize)] .replace(shared.board[idx + (COLS as usize)].get().into_win()); 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) => { 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); } 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 - (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 + 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()); shared.board[idx + 1 - (COLS as usize)].replace( shared.board[idx + 1 - (COLS as usize)].get().into_win(), ); 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) => { 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); } 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 + (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 + 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()); shared.board[idx + 1 + (COLS as usize)].replace( shared.board[idx + 1 + (COLS as usize)].get().into_win(), ); 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::None => unreachable!("WinType should never be None on win"), } } let text_append_result = append_to_info_text(&document, "info_text1", "Game Over", 1); if let Err(e) = text_append_result { log::warn!("ERROR: text append to info_text1 failed: {}", e); } shared .game_state .replace(GameState::PostGameResults(endgame_state)); } // 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 self.defer_message(ctx, WrapperMsg::AIChoiceImpl, AI_CHOICE_DURATION_MILLIS); } WrapperMsg::AIChoiceImpl => { // get AI's choice if let GameState::SinglePlayer(player_type, ai_difficulty) = shared.game_state.get() { if shared.turn.get() != player_type { 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::AIPressed(usize::from(choice) as u8)); } } } WrapperMsg::BackendTick => { if self.player_id.is_none() { self.get_networked_player_id(ctx); } // 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); } WrapperMsg::BackendResponse(br_enum) => match br_enum { BREnum::Error(string) => { log::warn!("{}", string); } BREnum::GotID(id, turn_opt) => { self.player_id = Some(id); } }, } // match (msg) true } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct InfoText {} #[derive(Copy, Clone, Debug, PartialEq, Eq, Properties)] pub struct InfoTextProperties { id: usize, } impl Component for InfoText { type Message = (); type Properties = InfoTextProperties; fn create(_ctx: &Context) -> Self { Self {} } fn view(&self, ctx: &Context) -> Html { let (shared, _) = ctx .link() .context::(Callback::noop()) .expect("state to be set"); match ctx.props().id { 0 => { html! {
{"Hello"}
} } 1 => { if shared.game_state.get() == GameState::MainMenu { html! {

{"Waiting to choose game-mode..."}

} } else if shared.turn.get() == Turn::CyanPlayer { html! {

{"It is CyanPlayer's turn"}

} } else { html! {

{"It is MagentaPlayer's turn"}

} } } _ => { unreachable!(); } } } }