From 76e6d3be52eef1153d037f87e02570da889144da Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 9 Mar 2022 18:10:13 +0900 Subject: [PATCH] Impl buttons indicator that won the game --- front_end/index.html | 14 +++ front_end/src/game_logic.rs | 45 +++++--- front_end/src/html_helper.rs | 10 ++ front_end/src/state.rs | 24 ++++- front_end/src/yew_components.rs | 175 +++++++++++++++++++++++++++++++- 5 files changed, 247 insertions(+), 21 deletions(-) diff --git a/front_end/index.html b/front_end/index.html index d539014..1e3f5bc 100644 --- a/front_end/index.html +++ b/front_end/index.html @@ -143,6 +143,20 @@ button.magenta { background: #F0F; } + button.win { + animation-duration: 400ms; + animation-name: blink; + animation-iteration-count: infinite; + animation-direction: alternate; + } + @keyframes blink { + from { + opacity: 1; + } + to { + opacity: 0; + } + } diff --git a/front_end/src/game_logic.rs b/front_end/src/game_logic.rs index e2c5111..9d31d10 100644 --- a/front_end/src/game_logic.rs +++ b/front_end/src/game_logic.rs @@ -1,8 +1,17 @@ use crate::constants::{COLS, ROWS}; use crate::state::{BoardState, BoardType}; +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum WinType { + Horizontal(usize), + Vertical(usize), + DiagonalUp(usize), + DiagonalDown(usize), + None, +} + /// Returns a BoardState if win/draw, None if game is still going -pub fn check_win_draw(board: &BoardType) -> Option { +pub fn check_win_draw(board: &BoardType) -> Option<(BoardState, WinType)> { let mut has_empty_slot = false; for slot in board { match slot.get() { @@ -10,28 +19,32 @@ pub fn check_win_draw(board: &BoardType) -> Option { has_empty_slot = true; break; } - BoardState::Cyan | BoardState::Magenta => (), + BoardState::Cyan + | BoardState::CyanWin + | BoardState::Magenta + | BoardState::MagentaWin => (), } } if !has_empty_slot { - return Some(BoardState::Empty); + return Some((BoardState::Empty, WinType::None)); } let check_result = |state| -> Option { match state { BoardState::Empty => None, - BoardState::Cyan => Some(BoardState::Cyan), - BoardState::Magenta => Some(BoardState::Magenta), + BoardState::Cyan | BoardState::CyanWin => Some(BoardState::Cyan), + BoardState::Magenta | BoardState::MagentaWin => Some(BoardState::Magenta), } }; // check horizontals for y in 0..(ROWS as usize) { for x in 0..((COLS - 3) as usize) { - let result = check_result(has_right_horizontal_at_idx(x + y * (COLS as usize), board)); + let idx = x + y * (COLS as usize); + let result = check_result(has_right_horizontal_at_idx(idx, board)); if result.is_some() { - return result; + return Some((result.unwrap(), WinType::Horizontal(idx))); } } } @@ -39,9 +52,10 @@ pub fn check_win_draw(board: &BoardType) -> Option { // check verticals for y in 0..((ROWS - 3) as usize) { for x in 0..(COLS as usize) { - let result = check_result(has_down_vertical_at_idx(x + y * (COLS as usize), board)); + let idx = x + y * (COLS as usize); + let result = check_result(has_down_vertical_at_idx(idx, board)); if result.is_some() { - return result; + return Some((result.unwrap(), WinType::Vertical(idx))); } } } @@ -49,9 +63,10 @@ pub fn check_win_draw(board: &BoardType) -> Option { // check up diagonals for y in 3..(ROWS as usize) { for x in 0..((COLS - 3) as usize) { - let result = check_result(has_right_up_diagonal_at_idx(x + y * (COLS as usize), board)); + let idx = x + y * (COLS as usize); + let result = check_result(has_right_up_diagonal_at_idx(idx, board)); if result.is_some() { - return result; + return Some((result.unwrap(), WinType::DiagonalUp(idx))); } } } @@ -59,12 +74,10 @@ pub fn check_win_draw(board: &BoardType) -> Option { // check down diagonals for y in 0..((ROWS - 3) as usize) { for x in 0..((COLS - 3) as usize) { - let result = check_result(has_right_down_diagonal_at_idx( - x + y * (COLS as usize), - board, - )); + let idx = x + y * (COLS as usize); + let result = check_result(has_right_down_diagonal_at_idx(idx, board)); if result.is_some() { - return result; + return Some((result.unwrap(), WinType::DiagonalDown(idx))); } } } diff --git a/front_end/src/html_helper.rs b/front_end/src/html_helper.rs index 6e4efdf..a1b9599 100644 --- a/front_end/src/html_helper.rs +++ b/front_end/src/html_helper.rs @@ -52,3 +52,13 @@ pub fn append_to_info_text( Ok(()) } + +pub fn element_append_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 new_class = format!("{} {}", element.class_name(), class); + element.set_class_name(&new_class); + + Ok(()) +} diff --git a/front_end/src/state.rs b/front_end/src/state.rs index 5c571e6..dabbdb1 100644 --- a/front_end/src/state.rs +++ b/front_end/src/state.rs @@ -33,6 +33,8 @@ pub enum BoardState { Empty, Cyan, Magenta, + CyanWin, + MagentaWin, } impl Default for BoardState { @@ -46,7 +48,9 @@ impl Display for BoardState { match *self { BoardState::Empty => f.write_str("open"), BoardState::Cyan => f.write_str("cyan"), + BoardState::CyanWin => f.write_str("cyan win"), BoardState::Magenta => f.write_str("magenta"), + BoardState::MagentaWin => f.write_str("magenta win"), } } } @@ -64,6 +68,22 @@ impl BoardState { pub fn is_empty(&self) -> bool { *self == BoardState::Empty } + + pub fn into_win(&self) -> Self { + match *self { + BoardState::Empty => BoardState::Empty, + BoardState::Cyan | BoardState::CyanWin => BoardState::CyanWin, + BoardState::Magenta | BoardState::MagentaWin => BoardState::MagentaWin, + } + } + + pub fn from_win(&self) -> Self { + match *self { + BoardState::Empty => BoardState::Empty, + BoardState::Cyan | BoardState::CyanWin => BoardState::Cyan, + BoardState::Magenta | BoardState::MagentaWin => BoardState::MagentaWin, + } + } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -84,8 +104,8 @@ impl Display for Turn { impl From for Turn { fn from(board_state: BoardState) -> Self { match board_state { - BoardState::Empty | BoardState::Cyan => Turn::CyanPlayer, - BoardState::Magenta => Turn::MagentaPlayer, + BoardState::Empty | BoardState::Cyan | BoardState::CyanWin => Turn::CyanPlayer, + BoardState::Magenta | BoardState::MagentaWin => Turn::MagentaPlayer, } } } diff --git a/front_end/src/yew_components.rs b/front_end/src/yew_components.rs index b6fe872..8b78d29 100644 --- a/front_end/src/yew_components.rs +++ b/front_end/src/yew_components.rs @@ -1,6 +1,6 @@ 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::game_logic::{check_win_draw, WinType}; +use crate::html_helper::{append_to_info_text, element_append_class, get_window_document}; use crate::state::{BoardState, GameState, SharedState, Turn}; use std::cell::Cell; @@ -281,7 +281,7 @@ impl Component for Wrapper { // check for win let check_win_draw_opt = check_win_draw(&shared.board); - if let Some(endgame_state) = check_win_draw_opt { + 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( @@ -307,6 +307,175 @@ impl Component for Wrapper { if let Err(e) = text_append_result { log::warn!("ERROR: text append to info_text0 failed: {}", e); } + + match win_type { + WinType::Horizontal(idx) => { + 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 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 * (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 + 1 * (COLS as usize)].replace( + shared.board[idx + 1 * (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 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 - 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 - 1 * (COLS as usize)].replace( + shared.board[idx + 1 - 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 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 + 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 + 1 * (COLS as usize)].replace( + shared.board[idx + 1 + 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 => todo!(), + } } let text_append_result =