use std::cell::{Cell, RefCell}; use std::collections::VecDeque; use std::rc::Rc; use yew::prelude::*; //const ROWS: u8 = 8; const COLS: u8 = 7; const INFO_TEXT_MAX_ITEMS: u32 = 100; const INFO_TEXT_HEIGHT: i32 = 400; #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum BoardState { Empty, Cyan, Magenta, } impl Default for BoardState { fn default() -> Self { Self::Empty } } #[derive(Clone, Debug, PartialEq, Eq)] struct MessageBus { queued: VecDeque, } impl Default for MessageBus { fn default() -> Self { Self { queued: VecDeque::new(), } } } impl MessageBus { pub fn get_next_msg(&mut self) -> Option { self.queued.pop_front() } pub fn push_msg(&mut self, msg: String) -> Result<(), String> { self.queued.push_back(msg); Ok(()) } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum Turn { CyanPlayer, MagentaPlayer, } #[derive(Clone, Debug, PartialEq)] struct SharedState { board: [Rc>; 56], bus: Rc>, turn: Turn, info_text_ref: NodeRef, } impl Default for SharedState { fn default() -> Self { Self { // cannot use [; 56] because Rc does not impl Copy board: [ Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), Rc::new(Cell::new(BoardState::default())), ], bus: Rc::new(RefCell::new(MessageBus::default())), turn: Turn::CyanPlayer, info_text_ref: NodeRef::default(), } } } struct Slot {} enum SlotMessage { Press(u8), } #[derive(Clone, PartialEq, Properties)] struct SlotProperties { idx: u8, state: Rc>, bus: 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 = match ctx.props().state.as_ref().get() { BoardState::Empty => "open", BoardState::Cyan => "cyan", BoardState::Magenta => "magenta", }; 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 { match msg { SlotMessage::Press(idx) => { let (shared, _) = ctx .link() .context::(Callback::noop()) .expect("shared to be set"); let result = shared.bus.borrow_mut().push_msg(format!("pressed {idx}")); if let Err(e) = result { log::error!("Error pushing msg to bus: {}", e); } else { log::info!("Pushed \"pressed {idx}\" msg to bus"); } } } // notify Wrapper with message if let Some(p) = ctx.link().get_parent() { p.clone().downcast::().send_message(()); } true } } struct Wrapper {} impl Component for Wrapper { type Message = (); 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 rendered(&mut self, ctx: &Context, _first_render: bool) { let (shared, _) = ctx .link() .context::(Callback::noop()) .expect("state to be set"); loop { if let Some(msg) = shared.bus.borrow_mut().get_next_msg() { let split_str: Vec<&str> = msg.split_whitespace().collect(); if split_str.len() == 2 { if split_str[0] == "pressed" { if let Ok(idx) = split_str[1].parse::() { let output_str: String = format!("Got {idx} pressed."); log::info!("{}", &output_str); if let Some(info_text) = shared.info_text_ref.cast::() { // create the new text to be appended in the output let window = web_sys::window().expect("no window exists"); let document = window.document().expect("window should have a document"); let p = document .create_element("p") .expect("document should be able to create

"); p.set_text_content(Some(&output_str)); // check if scrolled to bottom let at_bottom: bool = info_text.scroll_top() + INFO_TEXT_HEIGHT >= info_text.scroll_height(); // append text to output info_text .append_with_node_1(&p) .expect("should be able to append to info_text"); while info_text.child_element_count() > INFO_TEXT_MAX_ITEMS { info_text .remove_child(&info_text.first_child().unwrap()) .expect("should be able to limit items in info_text"); } // scroll to bottom only if at bottom if at_bottom { info_text.set_scroll_top(info_text.scroll_height()); } } else { log::warn!("Failed to get \"info_text\""); } } } } } else { break; } } } fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { true } } #[derive(Clone, Debug, PartialEq, Eq)] struct InfoText {} impl Component for InfoText { type Message = (); 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! {

{"Hello"}
} } } #[function_component(App)] fn app() -> Html { let ctx = use_state(SharedState::default); html! { context={(*ctx).clone()}> > } } fn main() { // setup logging to browser console wasm_logger::init(wasm_logger::Config::default()); // start webapp yew::start_app::(); }