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)]
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
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(
}
});
}
+
+fn new_board() -> String {
+ let mut board = String::with_capacity(56);
+ for _i in 0..56 {
+ board.push('a');
+ }
+ board
+}
-use crate::db_handler::{DBHandlerRequest, GetIDSenderType};
+use crate::db_handler::{CheckPairingType, DBHandlerRequest, GetIDSenderType};
use std::{
sync::mpsc::{sync_channel, SyncSender},
use serde_json::Value;
+const DB_REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
+
pub fn handle_json(
root: Value,
tx: SyncSender<DBHandlerRequest>,
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),
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> {