backend: Impl second backend protocol query
This commit is contained in:
parent
545b5a3a1b
commit
fe56adf562
2 changed files with 219 additions and 11 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Reference in a new issue