use crate::constants::{COLS, INFO_TEXT_MAX_ITEMS, ROWS}; use crate::game_logic::check_win_draw; use crate::html_helper::{append_to_info_text, get_window_document}; use crate::state::{BoardState, GameState, SharedState, Turn}; use std::cell::Cell; use std::rc::Rc; use yew::prelude::*; pub struct MainMenu {} pub enum MainMenuMessage { SinglePlayer, LocalMultiplayer, NetworkedMultiplayer, } 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 onclick_local_multiplayer = ctx.link().callback(|_| MainMenuMessage::LocalMultiplayer); 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"); info_text_turn.set_inner_html("

It is CyanPlayer's Turn

"); } true } } pub struct Slot {} pub enum SlotMessage { Press(u8), } #[derive(Clone, PartialEq, Properties)] pub struct SlotProperties { idx: u8, state: 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 idx_copy = idx; let onclick = ctx.link().callback(move |_| SlotMessage::Press(idx_copy)); let col = idx % COLS; let row = idx / COLS; 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 => (), GameState::PostGameResults(_) => return false, } if shared.game_state.get() == GameState::MainMenu { return false; } match msg { SlotMessage::Press(idx) => { // notify Wrapper with message let msg = WrapperMsg::Pressed(idx); if let Some(p) = ctx.link().get_parent() { p.clone().downcast::().send_message(msg); } } } true } } pub struct Wrapper {} pub enum WrapperMsg { Pressed(u8), } impl Component for Wrapper { type Message = WrapperMsg; 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"); 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) => { let mut bottom_idx = idx; let mut placed = false; let current_player = shared.turn.get(); // 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{}", current_board_state, bottom_idx / COLS, bottom_idx % COLS )); } placed = true; } // check for win let check_win_draw_opt = check_win_draw(&shared.board); if let Some(endgame_state) = 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); } } 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); } } 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)); } else { // game is still ongoing // 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 = 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); } } } // else: game is still ongoing after logic check } // WrapperMsg::Pressed(idx) => } // 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!(); } } } }