diff --git a/back_end/src/db_handler.rs b/back_end/src/db_handler.rs index 35487c0..52453c6 100644 --- a/back_end/src/db_handler.rs +++ b/back_end/src/db_handler.rs @@ -2,13 +2,21 @@ use std::sync::mpsc::{Receiver, SyncSender}; use std::thread; use rand::{thread_rng, Rng}; -use rusqlite::Connection; +use rusqlite::{params, Connection}; pub type GetIDSenderType = (u32, Option); +/// first bool is player exists, +/// second bool is if paired, +/// third bool is if cyan player +pub type CheckPairingType = (bool, bool, bool); #[derive(Clone, Debug)] pub enum DBHandlerRequest { GetID(SyncSender), + CheckPairing { + id: u32, + response_sender: SyncSender, + }, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -68,13 +76,51 @@ impl DBHandler { self.shutdown_tx.send(()).ok(); return true; } - let send_result = player_tx.send((player_id, None)); + + let pair_up_result = self.pair_up_players(Some(&conn)); + if let Err(e) = pair_up_result { + println!("Failed to pair up players: {}", e); + return true; + } + + // Check if current player has been paired + let mut is_cyan_player_opt: Option = None; + let check_player_row = conn.query_row("SELECT games.cyan_player FROM players JOIN games WHERE games.id = players.game_id AND players.id = ?;", [player_id], |row| row.get::(0)); + if let Ok(cyan_player) = check_player_row { + if cyan_player == player_id { + // is paired, is cyan_player + is_cyan_player_opt = Some(true); + } else { + // is paired, not cyan_player + is_cyan_player_opt = Some(false); + } + } else if check_player_row.is_err() { + // not paired, can do nothing here + } + + let send_result = player_tx.send((player_id, is_cyan_player_opt)); if let Err(e) = send_result { println!("Failed to send back player id: {:?}", e); self.shutdown_tx.send(()).ok(); return true; } send_result.unwrap(); + } + DBHandlerRequest::CheckPairing { + id, + response_sender, + } => { + let check_result = self.check_if_player_is_paired(id); + if let Ok((exists, is_paired, is_cyan)) = check_result { + let send_result = response_sender.send((exists, is_paired, is_cyan)); + if let Err(e) = send_result { + println!("Failed to send back check pairing status: {:?}", e); + self.shutdown_tx.send(()).ok(); + return true; + } + send_result.unwrap(); + } else { + } } // DBHandlerRequest::GetID(player_tx) } // match db_request @@ -127,6 +173,117 @@ impl DBHandler { Err(String::from("Failed to open connection")) } } + + fn pair_up_players(&self, conn: Option<&Connection>) -> Result<(), String> { + if let Some(conn) = conn { + let mut to_pair: Option = None; + let mut unpaired_players_stmt = conn + .prepare("SELECT id FROM players WHERE game_id ISNULL ORDER BY date_added;") + .map_err(|e| format!("{:?}", e))?; + let mut unpaired_players_rows = unpaired_players_stmt + .query([]) + .map_err(|e| format!("{:?}", e))?; + while let Some(row) = unpaired_players_rows + .next() + .map_err(|e| format!("{:?}", e))? + { + if to_pair.is_none() { + to_pair = Some(row.get(0).map_err(|e| format!("{:?}", e))?); + } else { + let players: [u32; 2] = [ + to_pair.take().unwrap(), + row.get(0).map_err(|e| format!("{:?}", e))?, + ]; + self.create_game(Some(conn), &players)?; + } + } + + Ok(()) + } else { + let conn = self.get_conn(DBFirstRun::NotFirstRun)?; + self.pair_up_players(Some(&conn)) + } + } + + fn create_game(&self, conn: Option<&Connection>, players: &[u32; 2]) -> Result { + if let Some(conn) = conn { + let mut game_id: u32 = thread_rng().gen(); + { + let mut get_game_stmt = conn + .prepare("SELECT id FROM games WHERE id = ?;") + .map_err(|e| format!("{:?}", e))?; + while get_game_stmt.query_row([game_id], |_row| Ok(())).is_ok() { + game_id = thread_rng().gen(); + } + } + + // TODO randomize players (or first-come-first-serve ok to do?) + conn.execute( + "INSERT INTO games (id, cyan_player, magenta_player, date_added, board, status) VALUES (?, ?, ?, datetime(), ?, 0);", + params![game_id, players[0], players[1], new_board()] + ) + .map_err(|e| format!("{:?}", e))?; + conn.execute( + "UPDATE players SET game_id = ? WHERE id = ?", + [game_id, players[0]], + ) + .map_err(|e| format!("{:?}", e))?; + conn.execute( + "UPDATE players SET game_id = ? WHERE id = ?", + [game_id, players[1]], + ) + .map_err(|e| format!("{:?}", e))?; + + Ok(game_id) + } else { + let conn = self.get_conn(DBFirstRun::NotFirstRun)?; + self.create_game(Some(&conn), players) + } + } + + fn check_if_player_is_paired(&self, player_id: u32) -> Result { + { + let player_exists_result = self.check_if_player_exists(player_id); + if player_exists_result.is_err() || !player_exists_result.unwrap() { + // player doesn't exist + return Ok((false, false, true)); + } + } + + let conn = self.get_conn(DBFirstRun::NotFirstRun)?; + + let check_player_row = conn.query_row("SELECT games.cyan_player FROM players JOIN games where games.id = players.game_id AND players.id = ?;", [player_id], |row| row.get::(0)); + if let Ok(cyan_player) = check_player_row { + if cyan_player == player_id { + // is cyan player + Ok((true, true, true)) + } else { + // is magenta player + Ok((true, true, false)) + } + } else if let Err(rusqlite::Error::QueryReturnedNoRows) = check_player_row { + // is not paired + Ok((true, false, true)) + } else if let Err(e) = check_player_row { + Err(format!("check_if_player_is_paired: {:?}", e)) + } else { + unreachable!(); + } + } + + fn check_if_player_exists(&self, player_id: u32) -> Result { + let conn = self.get_conn(DBFirstRun::NotFirstRun)?; + + let check_player_row = + conn.query_row("SELECT id FROM players WHERE id = ?;", [player_id], |row| { + row.get::(0) + }); + if let Ok(_id) = check_player_row { + Ok(true) + } else { + Ok(false) + } + } } pub fn start_db_handler_thread( @@ -156,3 +313,11 @@ pub fn start_db_handler_thread( } }); } + +fn new_board() -> String { + let mut board = String::with_capacity(56); + for _i in 0..56 { + board.push('a'); + } + board +} diff --git a/back_end/src/json_handlers.rs b/back_end/src/json_handlers.rs index 7920195..9872e62 100644 --- a/back_end/src/json_handlers.rs +++ b/back_end/src/json_handlers.rs @@ -1,4 +1,4 @@ -use crate::db_handler::{DBHandlerRequest, GetIDSenderType}; +use crate::db_handler::{CheckPairingType, DBHandlerRequest, GetIDSenderType}; use std::{ sync::mpsc::{sync_channel, SyncSender}, @@ -7,6 +7,8 @@ use std::{ use serde_json::Value; +const DB_REQUEST_TIMEOUT: Duration = Duration::from_secs(5); + pub fn handle_json( root: Value, tx: SyncSender, @@ -15,7 +17,7 @@ pub fn handle_json( if let Some(Value::String(type_str)) = root.get("type") { match type_str.as_str() { "pairing_request" => handle_pairing_request(tx), - "check_pairing" => handle_check_pairing(root), + "check_pairing" => handle_check_pairing(root, tx), "place_token" => handle_place_token(root), "disconnect" => handle_disconnect(root), "game_state" => handle_game_state(root), @@ -31,18 +33,59 @@ fn handle_pairing_request(tx: SyncSender) -> Result Result { - Err("{\"type\":\"unimplemented\"}".into()) +fn handle_check_pairing(root: Value, tx: SyncSender) -> Result { + if let Some(Value::Number(id)) = root.get("id") { + let (request_tx, request_rx) = sync_channel::(1); + let player_id = id + .as_u64() + .ok_or_else(|| String::from("{\"type\":\"invalid_syntax\"}"))?; + let player_id: u32 = player_id + .try_into() + .map_err(|_| String::from("{\"type\":\"invalid_syntax\"}"))?; + if tx + .send(DBHandlerRequest::CheckPairing { + id: player_id, + response_sender: request_tx, + }) + .is_err() + { + return Err("{\"type\":\"pairing_response\", \"status\":\"internal_error\"}".into()); + } + if let Ok((exists, is_paired, is_cyan)) = request_rx.recv_timeout(DB_REQUEST_TIMEOUT) { + if !exists { + Err("{\"type\":\"pairing_response\", \"status\":\"unknown_id\"}".into()) + } else if is_paired { + Ok(format!( + "{{\"type\":\"pairing_response\", \"status\":\"paired\", \"color\":\"{}\"}}", + if is_cyan { "cyan" } else { "magenta" } + )) + } else { + Ok("{\"type\"\"pairing_response\", \"status\":\"waiting\"}".into()) + } + } else { + Err("{\"type\":\"pairing_response\", \"status\":\"internal_error_timeout\"}".into()) + } + } else { + Err("{\"type\":\"invalid_syntax\"}".into()) + } } fn handle_place_token(root: Value) -> Result {