]> git.seodisparate.com - EN605.607.81.SP22_ASDM_Project/commitdiff
backend: Impl second backend protocol query
authorStephen Seo <seo.disparate@gmail.com>
Tue, 29 Mar 2022 08:02:57 +0000 (17:02 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Tue, 29 Mar 2022 08:02:57 +0000 (17:02 +0900)
back_end/src/db_handler.rs
back_end/src/json_handlers.rs

index 35487c073fb14fe2a5cb79c6c7b1905493b4f7cb..52453c63387b91c280543e570d367c8b203fb408 100644 (file)
@@ -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<bool>);
+/// 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<GetIDSenderType>),
+    CheckPairing {
+        id: u32,
+        response_sender: SyncSender<CheckPairingType>,
+    },
 }
 
 #[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<bool> = 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::<usize, u32>(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<u32> = 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<u32, String> {
+        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<CheckPairingType, String> {
+        {
+            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::<usize, u32>(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<bool, String> {
+        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::<usize, u32>(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
+}
index 792019504e2f64e27983dd687fb019324d2ac6fb..9872e62cc382560622c1ccb5dbb808b82affe642 100644 (file)
@@ -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<DBHandlerRequest>,
@@ -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<DBHandlerRequest>) -> Result<String, St
     if tx.send(DBHandlerRequest::GetID(player_tx)).is_err() {
         return Err("{\"type\":\"pairing_response\", \"status\":\"internal_error\"}".into());
     }
-    if let Ok((pid, is_cyan_opt)) = player_rx.recv_timeout(Duration::from_secs(5)) {
-        Ok(format!(
-            "{{\"type\":\"pairing_response\", \"id\": \"{}\", \"status\": \"waiting\"}}",
-            pid
-        ))
+    if let Ok((pid, is_cyan_opt)) = player_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
+        if let Some(is_cyan) = is_cyan_opt {
+            Ok(format!(
+                "{{\"type\":\"pairing_response\", \"id\": \"{}\", \"status\": \"paired\", \"color\": \"{}\"}}",
+                pid,
+                if is_cyan { "cyan" } else { "magenta" }
+            ))
+        } else {
+            Ok(format!(
+                "{{\"type\":\"pairing_response\", \"id\": \"{}\", \"status\": \"waiting\"}}",
+                pid
+            ))
+        }
     } else {
         Err("{\"type\":\"pairing_response\", \"status\":\"internal_error_timeout\"}".into())
     }
 }
 
-fn handle_check_pairing(root: Value) -> Result<String, String> {
-    Err("{\"type\":\"unimplemented\"}".into())
+fn handle_check_pairing(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
+    if let Some(Value::Number(id)) = root.get("id") {
+        let (request_tx, request_rx) = sync_channel::<CheckPairingType>(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<String, String> {