From 0d9e35dd28716fdf44bc31d222fdfac65d3dc30b Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Fri, 1 Apr 2022 17:17:42 +0900 Subject: [PATCH] Refactorings and bug fixes Protocol was also updated to match the output from the backend. Fixed "id" not returning as an integer from the backend. --- back_end/src/db_handler.rs | 96 +++++++++++++++++++++++-------- back_end/src/json_handlers.rs | 4 +- backend_protocol_specification.md | 6 +- front_end/src/state.rs | 28 ++++++--- 4 files changed, 99 insertions(+), 35 deletions(-) diff --git a/back_end/src/db_handler.rs b/back_end/src/db_handler.rs index 08b6bb0..cdc6d9e 100644 --- a/back_end/src/db_handler.rs +++ b/back_end/src/db_handler.rs @@ -1,5 +1,5 @@ use crate::constants::{COLS, ROWS}; -use crate::state::{BoardState, new_string_board, board_from_string, string_from_board}; +use crate::state::{board_from_string, new_string_board, string_from_board, BoardState}; use std::sync::mpsc::{Receiver, SyncSender}; use std::{fmt, thread}; @@ -64,14 +64,18 @@ impl From for DBGameState { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum DBPlaceStatus { Accepted, - GameEnded, + GameEndedDraw, + GameEndedCyanWon, + GameEndedMagentaWon, } impl fmt::Display for DBPlaceStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { DBPlaceStatus::Accepted => write!(f, "accepted"), - DBPlaceStatus::GameEnded => write!(f, "game_ended"), + DBPlaceStatus::GameEndedDraw => write!(f, "game_ended_draw"), + DBPlaceStatus::GameEndedCyanWon => write!(f, "game_ended_cyan_won"), + DBPlaceStatus::GameEndedMagentaWon => write!(f, "game_ended_magenta_won"), } } } @@ -512,17 +516,14 @@ impl DBHandler { Ok((DBGameState::InternalError, None)) } else if cyan_opt.is_none() || magenta_opt.is_none() { // One player disconnected - let player_remove_result = self.disconnect_player(Some(conn), player_id); - if player_remove_result.is_err() { - // Failed to disconnect remaining player + self.disconnect_player(Some(conn), player_id).ok(); + // Remove the game(s) with disconnected players + if self.clear_empty_games(Some(conn)).is_err() { Ok((DBGameState::InternalError, None)) + } else if status == 2 || status == 3 { + Ok((DBGameState::from(status), Some(board))) } else { - // Remove the game(s) with disconnected players - if self.clear_empty_games(Some(conn)).is_err() { - Ok((DBGameState::InternalError, None)) - } else { - Ok((DBGameState::OpponentDisconnected, Some(board))) - } + Ok((DBGameState::OpponentDisconnected, Some(board))) } } else { // Game in progress, or other state depending on "status" @@ -637,6 +638,10 @@ impl DBHandler { if let (Ok(cyan_id_opt), Ok(magenta_id_opt)) = (cyan_id_result, magenta_id_result) { if let (Some(cyan_id), Some(_magenta_id)) = (cyan_id_opt, magenta_id_opt) { Ok(Ok((cyan_id == player_id, status, board))) + } else if (2..=4).contains(&status) { + // game has ended, don't return error + // first result will be safely ignored + Ok(Ok((false, status, board))) } else { Ok(Err(DBPlaceError::OpponentDisconnected)) } @@ -654,9 +659,8 @@ impl DBHandler { // if opponent has disconnected, disconnect the remaining player as well if let Err(DBPlaceError::OpponentDisconnected) = query_result { - if self.disconnect_player(Some(conn), player_id).is_err() - || self.clear_empty_games(Some(conn)).is_err() - { + self.disconnect_player(Some(conn), player_id).ok(); + if self.clear_empty_games(Some(conn)).is_err() { return Err(DBPlaceError::InternalError); } } @@ -676,9 +680,29 @@ impl DBHandler { return Err(DBPlaceError::NotYourTurn); } } - 2 | 3 | 4 => { - // game over, cyan won, or magenta won, or draw - return Ok((DBPlaceStatus::GameEnded, Some(board_string))); + 2 => { + // game over, cyan won + self.disconnect_player(Some(conn), player_id).ok(); + if self.clear_empty_games(Some(conn)).is_err() { + return Err(DBPlaceError::InternalError); + } + return Ok((DBPlaceStatus::GameEndedCyanWon, Some(board_string))); + } + 3 => { + // game over, magenta won + self.disconnect_player(Some(conn), player_id).ok(); + if self.clear_empty_games(Some(conn)).is_err() { + return Err(DBPlaceError::InternalError); + } + return Ok((DBPlaceStatus::GameEndedMagentaWon, Some(board_string))); + } + 4 => { + // game over, draw + self.disconnect_player(Some(conn), player_id).ok(); + if self.clear_empty_games(Some(conn)).is_err() { + return Err(DBPlaceError::InternalError); + } + return Ok((DBPlaceStatus::GameEndedDraw, Some(board_string))); } _ => (), } @@ -710,10 +734,28 @@ impl DBHandler { } // board back to string - let (board_string, ended) = string_from_board(board, final_pos); + let (board_string, ended_state_opt) = string_from_board(board, final_pos); // update DB - let update_result = conn.execute("UPDATE games SET status = ?, board = ? FROM players WHERE players.game_id = games.id AND players.id = ?;" , params![if status == 0 { 1u8 } else { 0u8 }, board_string, player_id]); + let update_result = if ended_state_opt.is_none() { + conn.execute( + "UPDATE games SET status = ?, board = ? FROM players WHERE players.game_id = games.id AND players.id = ?;", + params![if status == 0 { 1u8 } + else { 0u8 }, + board_string, + player_id] + ) + } else { + conn.execute( + "UPDATE games SET status = ?, board = ? FROM players WHERE players.game_id = games.id AND players.id = ?;", + params![if ended_state_opt.unwrap() == BoardState::Empty { 4u8 } + else if ended_state_opt.unwrap() == BoardState::CyanWin { 2u8 } + else { 3u8 }, + board_string, + player_id] + ) + }; + if let Err(_e) = update_result { return Err(DBPlaceError::InternalError); } else if let Ok(count) = update_result { @@ -722,9 +764,17 @@ impl DBHandler { } } - if ended { + if let Some(ended_state) = ended_state_opt { self.disconnect_player(Some(conn), player_id).ok(); - Ok((DBPlaceStatus::GameEnded, Some(board_string))) + Ok(( + match ended_state { + BoardState::Empty => DBPlaceStatus::GameEndedDraw, + BoardState::Cyan | BoardState::Magenta => unreachable!(), + BoardState::CyanWin => DBPlaceStatus::GameEndedCyanWon, + BoardState::MagentaWin => DBPlaceStatus::GameEndedMagentaWon, + }, + Some(board_string), + )) } else { Ok((DBPlaceStatus::Accepted, Some(board_string))) } @@ -758,5 +808,3 @@ pub fn start_db_handler_thread( } }); } - - diff --git a/back_end/src/json_handlers.rs b/back_end/src/json_handlers.rs index 03fb98e..badeb72 100644 --- a/back_end/src/json_handlers.rs +++ b/back_end/src/json_handlers.rs @@ -36,13 +36,13 @@ fn handle_pairing_request(tx: SyncSender) -> Result BoardType { board } -/// Returns the board as a String, and true if the game has ended -pub fn string_from_board(board: BoardType, placed: usize) -> (String, bool) { +/// Returns the board as a String, and None if game has not ended, Empty if game +/// ended in a draw, or a player if that player has won +pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option) { let mut board_string = String::with_capacity(56); // check for winning pieces let mut win_set: HashSet = HashSet::new(); let win_opt = check_win_draw(&board); - if let Some((board_state, win_type)) = win_opt { + if let Some((_board_state, win_type)) = win_opt { match win_type { WinType::Horizontal(pos) => { for i in pos..(pos + 4) { @@ -386,5 +387,18 @@ pub fn string_from_board(board: BoardType, placed: usize) -> (String, bool) { }); } - (board_string, is_full || !win_set.is_empty()) + if is_full && win_set.is_empty() { + (board_string, Some(BoardState::Empty)) + } else if !win_set.is_empty() { + ( + board_string.clone(), + if board_string.chars().collect::>()[*win_set.iter().next().unwrap()] == 'd' { + Some(BoardState::CyanWin) + } else { + Some(BoardState::MagentaWin) + }, + ) + } else { + (board_string, None) + } }