Impl buttons indicator that won the game

This commit is contained in:
Stephen Seo 2022-03-09 18:10:13 +09:00
parent b902b1c7b4
commit 76e6d3be52
5 changed files with 247 additions and 21 deletions

View file

@ -143,6 +143,20 @@
button.magenta { button.magenta {
background: #F0F; 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;
}
}
</style> </style>
</head> </head>
</html> </html>

View file

@ -1,8 +1,17 @@
use crate::constants::{COLS, ROWS}; use crate::constants::{COLS, ROWS};
use crate::state::{BoardState, BoardType}; 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 /// Returns a BoardState if win/draw, None if game is still going
pub fn check_win_draw(board: &BoardType) -> Option<BoardState> { pub fn check_win_draw(board: &BoardType) -> Option<(BoardState, WinType)> {
let mut has_empty_slot = false; let mut has_empty_slot = false;
for slot in board { for slot in board {
match slot.get() { match slot.get() {
@ -10,28 +19,32 @@ pub fn check_win_draw(board: &BoardType) -> Option<BoardState> {
has_empty_slot = true; has_empty_slot = true;
break; break;
} }
BoardState::Cyan | BoardState::Magenta => (), BoardState::Cyan
| BoardState::CyanWin
| BoardState::Magenta
| BoardState::MagentaWin => (),
} }
} }
if !has_empty_slot { if !has_empty_slot {
return Some(BoardState::Empty); return Some((BoardState::Empty, WinType::None));
} }
let check_result = |state| -> Option<BoardState> { let check_result = |state| -> Option<BoardState> {
match state { match state {
BoardState::Empty => None, BoardState::Empty => None,
BoardState::Cyan => Some(BoardState::Cyan), BoardState::Cyan | BoardState::CyanWin => Some(BoardState::Cyan),
BoardState::Magenta => Some(BoardState::Magenta), BoardState::Magenta | BoardState::MagentaWin => Some(BoardState::Magenta),
} }
}; };
// check horizontals // check horizontals
for y in 0..(ROWS as usize) { for y in 0..(ROWS as usize) {
for x in 0..((COLS - 3) 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() { 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<BoardState> {
// check verticals // check verticals
for y in 0..((ROWS - 3) as usize) { for y in 0..((ROWS - 3) as usize) {
for x in 0..(COLS 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() { 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<BoardState> {
// check up diagonals // check up diagonals
for y in 3..(ROWS as usize) { for y in 3..(ROWS as usize) {
for x in 0..((COLS - 3) 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() { 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<BoardState> {
// check down diagonals // check down diagonals
for y in 0..((ROWS - 3) as usize) { for y in 0..((ROWS - 3) as usize) {
for x in 0..((COLS - 3) as usize) { for x in 0..((COLS - 3) as usize) {
let result = check_result(has_right_down_diagonal_at_idx( let idx = x + y * (COLS as usize);
x + y * (COLS as usize), let result = check_result(has_right_down_diagonal_at_idx(idx, board));
board,
));
if result.is_some() { if result.is_some() {
return result; return Some((result.unwrap(), WinType::DiagonalDown(idx)));
} }
} }
} }

View file

@ -52,3 +52,13 @@ pub fn append_to_info_text(
Ok(()) 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(())
}

View file

@ -33,6 +33,8 @@ pub enum BoardState {
Empty, Empty,
Cyan, Cyan,
Magenta, Magenta,
CyanWin,
MagentaWin,
} }
impl Default for BoardState { impl Default for BoardState {
@ -46,7 +48,9 @@ impl Display for BoardState {
match *self { match *self {
BoardState::Empty => f.write_str("open"), BoardState::Empty => f.write_str("open"),
BoardState::Cyan => f.write_str("cyan"), BoardState::Cyan => f.write_str("cyan"),
BoardState::CyanWin => f.write_str("cyan win"),
BoardState::Magenta => f.write_str("magenta"), 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 { pub fn is_empty(&self) -> bool {
*self == BoardState::Empty *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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -84,8 +104,8 @@ impl Display for Turn {
impl From<BoardState> for Turn { impl From<BoardState> for Turn {
fn from(board_state: BoardState) -> Self { fn from(board_state: BoardState) -> Self {
match board_state { match board_state {
BoardState::Empty | BoardState::Cyan => Turn::CyanPlayer, BoardState::Empty | BoardState::Cyan | BoardState::CyanWin => Turn::CyanPlayer,
BoardState::Magenta => Turn::MagentaPlayer, BoardState::Magenta | BoardState::MagentaWin => Turn::MagentaPlayer,
} }
} }
} }

View file

@ -1,6 +1,6 @@
use crate::constants::{COLS, INFO_TEXT_MAX_ITEMS, ROWS}; use crate::constants::{COLS, INFO_TEXT_MAX_ITEMS, ROWS};
use crate::game_logic::check_win_draw; use crate::game_logic::{check_win_draw, WinType};
use crate::html_helper::{append_to_info_text, get_window_document}; use crate::html_helper::{append_to_info_text, element_append_class, get_window_document};
use crate::state::{BoardState, GameState, SharedState, Turn}; use crate::state::{BoardState, GameState, SharedState, Turn};
use std::cell::Cell; use std::cell::Cell;
@ -281,7 +281,7 @@ impl Component for Wrapper {
// check for win // check for win
let check_win_draw_opt = check_win_draw(&shared.board); 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 { if endgame_state == BoardState::Empty {
// draw // draw
let text_append_result = append_to_info_text( let text_append_result = append_to_info_text(
@ -307,6 +307,175 @@ impl Component for Wrapper {
if let Err(e) = text_append_result { if let Err(e) = text_append_result {
log::warn!("ERROR: text append to info_text0 failed: {}", e); 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 = let text_append_result =