350 lines
11 KiB
Rust
350 lines
11 KiB
Rust
//Four Line Dropper Frontend/Backend - A webapp that allows one to play a game of Four Line Dropper
|
|
//Copyright (C) 2022 Stephen Seo
|
|
//
|
|
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
//
|
|
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
//
|
|
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
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<(BoardState, WinType)> {
|
|
let mut has_empty_slot = false;
|
|
for slot in board {
|
|
match slot.get() {
|
|
BoardState::Empty => {
|
|
has_empty_slot = true;
|
|
break;
|
|
}
|
|
BoardState::Cyan
|
|
| BoardState::CyanWin
|
|
| BoardState::Magenta
|
|
| BoardState::MagentaWin => (),
|
|
}
|
|
}
|
|
|
|
if !has_empty_slot {
|
|
return Some((BoardState::Empty, WinType::None));
|
|
}
|
|
|
|
let check_result = |state| -> Option<BoardState> {
|
|
match state {
|
|
BoardState::Empty => None,
|
|
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 idx = x + y * (COLS as usize);
|
|
let result = check_result(has_right_horizontal_at_idx(idx, board));
|
|
if let Some(result) = result {
|
|
return Some((result, WinType::Horizontal(idx)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// check verticals
|
|
for y in 0..((ROWS - 3) as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
let result = check_result(has_down_vertical_at_idx(idx, board));
|
|
if let Some(result) = result {
|
|
return Some((result, WinType::Vertical(idx)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// check up diagonals
|
|
for y in 3..(ROWS as usize) {
|
|
for x in 0..((COLS - 3) as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
let result = check_result(has_right_up_diagonal_at_idx(idx, board));
|
|
if let Some(result) = result {
|
|
return Some((result, WinType::DiagonalUp(idx)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// check down diagonals
|
|
for y in 0..((ROWS - 3) as usize) {
|
|
for x in 0..((COLS - 3) as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
let result = check_result(has_right_down_diagonal_at_idx(idx, board));
|
|
if let Some(result) = result {
|
|
return Some((result, WinType::DiagonalDown(idx)));
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn has_right_horizontal_at_idx(idx: usize, board: &BoardType) -> BoardState {
|
|
let state_at_idx = board[idx].get();
|
|
if idx % (COLS as usize) < (COLS as usize) - 3 {
|
|
for x in 0..=3 {
|
|
if board[idx + x].get() != state_at_idx {
|
|
break;
|
|
} else if x == 3 {
|
|
return state_at_idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
BoardState::Empty
|
|
}
|
|
|
|
fn has_down_vertical_at_idx(idx: usize, board: &BoardType) -> BoardState {
|
|
let state_at_idx = board[idx].get();
|
|
if idx / (COLS as usize) < (ROWS as usize) - 3 {
|
|
for y in 0..=3 {
|
|
if board[idx + y * (COLS as usize)].get() != state_at_idx {
|
|
break;
|
|
} else if y == 3 {
|
|
return state_at_idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
BoardState::Empty
|
|
}
|
|
|
|
fn has_right_up_diagonal_at_idx(idx: usize, board: &BoardType) -> BoardState {
|
|
let state_at_idx = board[idx].get();
|
|
if idx % (COLS as usize) < (COLS as usize) - 3 && idx / (COLS as usize) > 2 {
|
|
for i in 0..=3 {
|
|
if board[idx + i - i * (COLS as usize)].get() != state_at_idx {
|
|
break;
|
|
} else if i == 3 {
|
|
return state_at_idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
BoardState::Empty
|
|
}
|
|
|
|
fn has_right_down_diagonal_at_idx(idx: usize, board: &BoardType) -> BoardState {
|
|
let state_at_idx = board[idx].get();
|
|
if idx % (COLS as usize) < (COLS as usize) - 3 && idx / (COLS as usize) < (ROWS as usize) - 3 {
|
|
for i in 0..=3 {
|
|
if board[idx + i + i * (COLS as usize)].get() != state_at_idx {
|
|
break;
|
|
} else if i == 3 {
|
|
return state_at_idx;
|
|
}
|
|
}
|
|
}
|
|
BoardState::Empty
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::state::{new_empty_board, BoardState};
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_horizontal_check() {
|
|
let board = new_empty_board();
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
assert_eq!(
|
|
has_right_horizontal_at_idx(x + y * (COLS as usize), &board),
|
|
BoardState::Empty
|
|
);
|
|
}
|
|
}
|
|
|
|
board[50].replace(BoardState::Cyan);
|
|
board[51].replace(BoardState::Cyan);
|
|
board[52].replace(BoardState::Cyan);
|
|
board[53].replace(BoardState::Cyan);
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
if idx == 50 {
|
|
assert_eq!(has_right_horizontal_at_idx(idx, &board), BoardState::Cyan);
|
|
} else {
|
|
assert_eq!(has_right_horizontal_at_idx(idx, &board), BoardState::Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
board[51].replace(BoardState::Magenta);
|
|
|
|
board[43].replace(BoardState::Magenta);
|
|
board[44].replace(BoardState::Magenta);
|
|
board[45].replace(BoardState::Magenta);
|
|
board[46].replace(BoardState::Magenta);
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
if idx == 43 {
|
|
assert_eq!(
|
|
has_right_horizontal_at_idx(idx, &board),
|
|
BoardState::Magenta
|
|
);
|
|
} else {
|
|
assert_eq!(has_right_horizontal_at_idx(idx, &board), BoardState::Empty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_vertical_check() {
|
|
let board = new_empty_board();
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
assert_eq!(
|
|
has_down_vertical_at_idx(x + y * (COLS as usize), &board),
|
|
BoardState::Empty
|
|
);
|
|
}
|
|
}
|
|
|
|
board[30].replace(BoardState::Cyan);
|
|
board[37].replace(BoardState::Cyan);
|
|
board[44].replace(BoardState::Cyan);
|
|
board[51].replace(BoardState::Cyan);
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
if idx == 30 {
|
|
assert_eq!(has_down_vertical_at_idx(idx, &board), BoardState::Cyan);
|
|
} else {
|
|
assert_eq!(has_down_vertical_at_idx(idx, &board), BoardState::Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
board[16].replace(BoardState::Magenta);
|
|
board[23].replace(BoardState::Magenta);
|
|
board[30].replace(BoardState::Magenta);
|
|
board[37].replace(BoardState::Magenta);
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
if idx == 16 {
|
|
assert_eq!(has_down_vertical_at_idx(idx, &board), BoardState::Magenta);
|
|
} else {
|
|
assert_eq!(has_down_vertical_at_idx(idx, &board), BoardState::Empty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_upper_diagonal_check() {
|
|
let board = new_empty_board();
|
|
|
|
board[44].replace(BoardState::Cyan);
|
|
board[38].replace(BoardState::Cyan);
|
|
board[32].replace(BoardState::Cyan);
|
|
board[26].replace(BoardState::Cyan);
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
if idx == 44 {
|
|
assert_eq!(has_right_up_diagonal_at_idx(idx, &board), BoardState::Cyan);
|
|
} else {
|
|
assert_eq!(has_right_up_diagonal_at_idx(idx, &board), BoardState::Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
board[38].replace(BoardState::Magenta);
|
|
|
|
board[28].replace(BoardState::Magenta);
|
|
board[22].replace(BoardState::Magenta);
|
|
board[16].replace(BoardState::Magenta);
|
|
board[10].replace(BoardState::Magenta);
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
if idx == 28 {
|
|
assert_eq!(
|
|
has_right_up_diagonal_at_idx(idx, &board),
|
|
BoardState::Magenta
|
|
);
|
|
} else {
|
|
assert_eq!(has_right_up_diagonal_at_idx(idx, &board), BoardState::Empty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_diagonal_check() {
|
|
let board = new_empty_board();
|
|
|
|
board[17].replace(BoardState::Cyan);
|
|
board[25].replace(BoardState::Cyan);
|
|
board[33].replace(BoardState::Cyan);
|
|
board[41].replace(BoardState::Cyan);
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
if idx == 17 {
|
|
assert_eq!(
|
|
has_right_down_diagonal_at_idx(idx, &board),
|
|
BoardState::Cyan
|
|
);
|
|
} else {
|
|
assert_eq!(
|
|
has_right_down_diagonal_at_idx(idx, &board),
|
|
BoardState::Empty
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
board[25].replace(BoardState::Magenta);
|
|
|
|
board[28].replace(BoardState::Magenta);
|
|
board[36].replace(BoardState::Magenta);
|
|
board[44].replace(BoardState::Magenta);
|
|
board[52].replace(BoardState::Magenta);
|
|
|
|
for y in 0..(ROWS as usize) {
|
|
for x in 0..(COLS as usize) {
|
|
let idx = x + y * (COLS as usize);
|
|
if idx == 28 {
|
|
assert_eq!(
|
|
has_right_down_diagonal_at_idx(idx, &board),
|
|
BoardState::Magenta
|
|
);
|
|
} else {
|
|
assert_eq!(
|
|
has_right_down_diagonal_at_idx(idx, &board),
|
|
BoardState::Empty
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|