back-end: Impl "phrase", update protocol
This commit is contained in:
parent
87d93e5b4f
commit
f9338d4093
3 changed files with 70 additions and 19 deletions
|
@ -13,6 +13,7 @@ use crate::constants::{
|
||||||
};
|
};
|
||||||
use crate::state::{board_from_string, new_string_board, string_from_board, BoardState, Turn};
|
use crate::state::{board_from_string, new_string_board, string_from_board, BoardState, Turn};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::mpsc::{Receiver, RecvTimeoutError, SyncSender};
|
use std::sync::mpsc::{Receiver, RecvTimeoutError, SyncSender};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::{fmt, thread};
|
use std::{fmt, thread};
|
||||||
|
@ -118,7 +119,10 @@ impl fmt::Display for DBPlaceError {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum DBHandlerRequest {
|
pub enum DBHandlerRequest {
|
||||||
GetID(SyncSender<GetIDSenderType>),
|
GetID {
|
||||||
|
response_sender: SyncSender<GetIDSenderType>,
|
||||||
|
phrase: Option<String>,
|
||||||
|
},
|
||||||
CheckPairing {
|
CheckPairing {
|
||||||
id: u32,
|
id: u32,
|
||||||
response_sender: SyncSender<CheckPairingType>,
|
response_sender: SyncSender<CheckPairingType>,
|
||||||
|
@ -163,7 +167,10 @@ impl DBHandler {
|
||||||
}
|
}
|
||||||
let db_request = rx_recv_result.unwrap();
|
let db_request = rx_recv_result.unwrap();
|
||||||
match db_request {
|
match db_request {
|
||||||
DBHandlerRequest::GetID(player_tx) => {
|
DBHandlerRequest::GetID {
|
||||||
|
response_sender,
|
||||||
|
phrase,
|
||||||
|
} => {
|
||||||
// got request to create new player, create new player
|
// got request to create new player, create new player
|
||||||
let conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
let conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||||
if let Err(e) = conn_result {
|
if let Err(e) = conn_result {
|
||||||
|
@ -172,10 +179,10 @@ impl DBHandler {
|
||||||
}
|
}
|
||||||
let conn = conn_result.unwrap();
|
let conn = conn_result.unwrap();
|
||||||
|
|
||||||
let create_player_result = self.create_new_player(Some(&conn));
|
let create_player_result = self.create_new_player(Some(&conn), phrase);
|
||||||
if let Err(e) = create_player_result {
|
if let Err(e) = create_player_result {
|
||||||
println!("{}", e);
|
println!("{}", e);
|
||||||
player_tx.send((None, None)).ok();
|
response_sender.send((None, None)).ok();
|
||||||
// don't stop server because player limit may have been reached
|
// don't stop server because player limit may have been reached
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -197,11 +204,11 @@ impl DBHandler {
|
||||||
if paired {
|
if paired {
|
||||||
// don't stop server on send fail, may have timed
|
// don't stop server on send fail, may have timed
|
||||||
// out and dropped the receiver
|
// out and dropped the receiver
|
||||||
player_tx.send((Some(player_id), Some(is_cyan))).ok();
|
response_sender.send((Some(player_id), Some(is_cyan))).ok();
|
||||||
} else {
|
} else {
|
||||||
// don't stop server on send fail, may have timed
|
// don't stop server on send fail, may have timed
|
||||||
// out and dropped the receiver
|
// out and dropped the receiver
|
||||||
player_tx.send((Some(player_id), None)).ok();
|
response_sender.send((Some(player_id), None)).ok();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Internal error, created player doesn't exist");
|
println!("Internal error, created player doesn't exist");
|
||||||
|
@ -341,7 +348,11 @@ impl DBHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_new_player(&self, conn: Option<&Connection>) -> Result<u32, String> {
|
fn create_new_player(
|
||||||
|
&self,
|
||||||
|
conn: Option<&Connection>,
|
||||||
|
phrase: Option<String>,
|
||||||
|
) -> Result<u32, String> {
|
||||||
let mut _conn_result = Err(String::new());
|
let mut _conn_result = Err(String::new());
|
||||||
let conn = if conn.is_none() {
|
let conn = if conn.is_none() {
|
||||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||||
|
@ -380,7 +391,10 @@ impl DBHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let insert_result = conn.execute("INSERT INTO players (id) VALUES (?);", [player_id]);
|
let insert_result = conn.execute(
|
||||||
|
"INSERT INTO players (id, phrase) VALUES (?, ?);",
|
||||||
|
params![player_id, phrase],
|
||||||
|
);
|
||||||
if let Err(e) = insert_result {
|
if let Err(e) = insert_result {
|
||||||
return Err(format!("Failed to insert player into db: {:?}", e));
|
return Err(format!("Failed to insert player into db: {:?}", e));
|
||||||
}
|
}
|
||||||
|
@ -399,15 +413,30 @@ impl DBHandler {
|
||||||
|
|
||||||
let mut to_pair: Option<u32> = None;
|
let mut to_pair: Option<u32> = None;
|
||||||
let mut unpaired_players_stmt = conn
|
let mut unpaired_players_stmt = conn
|
||||||
.prepare("SELECT id FROM players WHERE game_id ISNULL ORDER BY date_added;")
|
.prepare("SELECT id, phrase FROM players WHERE game_id ISNULL ORDER BY date_added;")
|
||||||
.map_err(|e| format!("{:?}", e))?;
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
let mut unpaired_players_rows = unpaired_players_stmt
|
let mut unpaired_players_rows = unpaired_players_stmt
|
||||||
.query([])
|
.query([])
|
||||||
.map_err(|e| format!("{:?}", e))?;
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
let mut phrase_map: HashMap<String, u32> = HashMap::new();
|
||||||
while let Some(row) = unpaired_players_rows
|
while let Some(row) = unpaired_players_rows
|
||||||
.next()
|
.next()
|
||||||
.map_err(|e| format!("{:?}", e))?
|
.map_err(|e| format!("{:?}", e))?
|
||||||
{
|
{
|
||||||
|
if let Ok(phrase_text) = row.get::<usize, String>(1) {
|
||||||
|
// pair players with matching phrases
|
||||||
|
if let Some(matching_player_id) = phrase_map.get(&phrase_text) {
|
||||||
|
let players: [u32; 2] = [
|
||||||
|
*matching_player_id,
|
||||||
|
row.get(0).map_err(|e| format!("{:?}", e))?,
|
||||||
|
];
|
||||||
|
self.create_game(Some(conn), &players)?;
|
||||||
|
phrase_map.remove(&phrase_text);
|
||||||
|
} else {
|
||||||
|
phrase_map.insert(phrase_text, row.get(0).map_err(|e| format!("{:?}", e))?);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// pair players that did not use a phrase
|
||||||
if to_pair.is_none() {
|
if to_pair.is_none() {
|
||||||
to_pair = Some(row.get(0).map_err(|e| format!("{:?}", e))?);
|
to_pair = Some(row.get(0).map_err(|e| format!("{:?}", e))?);
|
||||||
} else {
|
} else {
|
||||||
|
@ -418,6 +447,7 @@ impl DBHandler {
|
||||||
self.create_game(Some(conn), &players)?;
|
self.create_game(Some(conn), &players)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub fn handle_json(
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
if let Some(Value::String(type_str)) = root.get("type") {
|
if let Some(Value::String(type_str)) = root.get("type") {
|
||||||
match type_str.as_str() {
|
match type_str.as_str() {
|
||||||
"pairing_request" => handle_pairing_request(tx),
|
"pairing_request" => handle_pairing_request(root, tx),
|
||||||
"check_pairing" => handle_check_pairing(root, tx),
|
"check_pairing" => handle_check_pairing(root, tx),
|
||||||
"place_token" => handle_place_token(root, tx),
|
"place_token" => handle_place_token(root, tx),
|
||||||
"disconnect" => handle_disconnect(root, tx),
|
"disconnect" => handle_disconnect(root, tx),
|
||||||
|
@ -36,9 +36,21 @@ pub fn handle_json(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_pairing_request(tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
|
fn handle_pairing_request(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
|
||||||
let (player_tx, player_rx) = sync_channel::<GetIDSenderType>(1);
|
let (player_tx, player_rx) = sync_channel::<GetIDSenderType>(1);
|
||||||
if tx.send(DBHandlerRequest::GetID(player_tx)).is_err() {
|
let mut phrase: Option<String> = None;
|
||||||
|
if let Some(phrase_text) = root.get("phrase") {
|
||||||
|
if let Some(phrase_str) = phrase_text.as_str() {
|
||||||
|
phrase = Some(phrase_str.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tx
|
||||||
|
.send(DBHandlerRequest::GetID {
|
||||||
|
response_sender: player_tx,
|
||||||
|
phrase,
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
return Err("{\"type\":\"pairing_response\", \"status\":\"internal_error\"}".into());
|
return Err("{\"type\":\"pairing_response\", \"status\":\"internal_error\"}".into());
|
||||||
}
|
}
|
||||||
if let Ok((pid_opt, is_cyan_opt)) = player_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
if let Ok((pid_opt, is_cyan_opt)) = player_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||||
|
|
|
@ -14,6 +14,15 @@ of the request, and the backend will respond with JSON.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
An optional "phrase" parameter can be sent to match against other players with
|
||||||
|
the same "phrase".
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"type": "pairing_request",
|
||||||
|
"phrase": "user_defined_phrase",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
2. Check pairing status
|
2. Check pairing status
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
Loading…
Reference in a new issue