diff --git a/front_end/index.html b/front_end/index.html index 1e3f5bc..40fbfff 100644 --- a/front_end/index.html +++ b/front_end/index.html @@ -157,6 +157,23 @@ opacity: 0; } } + button.placed { + animation-duration: 2200ms; + animation-name: placed_blink; + animation-iteration-count: 1; + animation-direction: normal; + } + @keyframes placed_blink { + from { + opacity: 1; + } + 50% { + opacity: 0; + } + to { + opacity: 1; + } + } diff --git a/front_end/src/html_helper.rs b/front_end/src/html_helper.rs index a1b9599..7dd1476 100644 --- a/front_end/src/html_helper.rs +++ b/front_end/src/html_helper.rs @@ -62,3 +62,18 @@ pub fn element_append_class(document: &Document, id: &str, class: &str) -> Resul Ok(()) } + +pub fn element_remove_class(document: &Document, id: &str, class: &str) -> Result<(), String> { + let element = document + .get_element_by_id(id) + .ok_or_else(|| format!("Failed to get element with id \"{}\"", id))?; + let mut element_class: String = element.class_name(); + let idx_opt = element_class.find(class); + if let Some(idx) = idx_opt { + let mut remaining = element_class.split_off(idx); + element_class += &remaining.split_off(class.len()); + } + element.set_class_name(&element_class); + + Ok(()) +} diff --git a/front_end/src/state.rs b/front_end/src/state.rs index 57a11fe..38b76bf 100644 --- a/front_end/src/state.rs +++ b/front_end/src/state.rs @@ -69,6 +69,13 @@ impl BoardState { *self == BoardState::Empty } + pub fn is_win(self) -> bool { + match self { + BoardState::Empty | BoardState::Cyan | BoardState::Magenta => false, + BoardState::CyanWin | BoardState::MagentaWin => true, + } + } + pub fn into_win(self) -> Self { match self { BoardState::Empty => BoardState::Empty, @@ -77,8 +84,8 @@ impl BoardState { } } - pub fn from_win(self) -> Self { - match self { + pub fn from_win(&self) -> Self { + match *self { BoardState::Empty => BoardState::Empty, BoardState::Cyan | BoardState::CyanWin => BoardState::Cyan, BoardState::Magenta | BoardState::MagentaWin => BoardState::Magenta, @@ -189,11 +196,75 @@ pub fn new_empty_board() -> BoardType { ] } +pub type PlacedType = [Rc>; 56]; + +pub fn new_placed() -> PlacedType { + [ + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + Rc::new(Cell::new(false)), + ] +} + #[derive(Clone, Debug, PartialEq)] pub struct SharedState { pub board: BoardType, pub game_state: Rc>, pub turn: Rc>, + pub placed: PlacedType, } impl Default for SharedState { @@ -203,6 +274,7 @@ impl Default for SharedState { board: new_empty_board(), game_state: Rc::new(Cell::new(GameState::default())), turn: Rc::new(Cell::new(Turn::CyanPlayer)), + placed: new_placed(), } } } diff --git a/front_end/src/yew_components.rs b/front_end/src/yew_components.rs index af4cd4c..e19706c 100644 --- a/front_end/src/yew_components.rs +++ b/front_end/src/yew_components.rs @@ -1,6 +1,8 @@ use crate::constants::{COLS, INFO_TEXT_MAX_ITEMS, ROWS}; use crate::game_logic::{check_win_draw, WinType}; -use crate::html_helper::{append_to_info_text, element_append_class, get_window_document}; +use crate::html_helper::{ + append_to_info_text, element_append_class, element_remove_class, get_window_document, +}; use crate::state::{BoardState, GameState, SharedState, Turn}; use std::cell::Cell; @@ -84,13 +86,14 @@ impl Component for MainMenu { pub struct Slot {} pub enum SlotMessage { - Press(u8), + Press, } #[derive(Clone, PartialEq, Properties)] pub struct SlotProperties { idx: u8, state: Rc>, + placed: Rc>, } impl Component for Slot { @@ -104,12 +107,17 @@ impl Component for Slot { 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 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! { - } @@ -133,9 +141,9 @@ impl Component for Slot { } match msg { - SlotMessage::Press(idx) => { + SlotMessage::Press => { // notify Wrapper with message - let msg = WrapperMsg::Pressed(idx); + let msg = WrapperMsg::Pressed(ctx.props().idx); if let Some(p) = ctx.link().get_parent() { p.clone().downcast::().send_message(msg); } @@ -168,62 +176,62 @@ impl Component for Wrapper { html! {
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -269,11 +277,12 @@ impl Component for Wrapper { if let Some(slot) = document.get_element_by_id(&format!("slot{bottom_idx}")) { // set slot info slot.set_class_name(&format!( - "slot {} r{} c{}", + "slot {} r{} c{} placed", current_board_state, bottom_idx / COLS, bottom_idx % COLS )); + shared.placed[bottom_idx as usize].replace(true); } placed = true; @@ -310,6 +319,39 @@ impl Component for Wrapper { 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 { @@ -349,6 +391,39 @@ impl Component for Wrapper { .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 { @@ -390,6 +465,39 @@ impl Component for Wrapper { ); } 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 { @@ -432,6 +540,39 @@ impl Component for Wrapper { ); } 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 {