backend: Impl "place_token" protocol
Some edge-cases might not be addressed. Need to impl. "timers" for clearing out stale entries in the database.
This commit is contained in:
parent
473e76a1bc
commit
dbb1c3ad35
3 changed files with 414 additions and 36 deletions
|
@ -1,5 +1,8 @@
|
||||||
use crate::constants::{COLS, ROWS};
|
use crate::constants::{COLS, ROWS};
|
||||||
|
use crate::game_logic::{check_win_draw, WinType};
|
||||||
|
use crate::state::{new_empty_board, BoardState, BoardType};
|
||||||
|
|
||||||
|
use std::collections::hash_set::HashSet;
|
||||||
use std::sync::mpsc::{Receiver, SyncSender};
|
use std::sync::mpsc::{Receiver, SyncSender};
|
||||||
use std::{fmt, thread};
|
use std::{fmt, thread};
|
||||||
|
|
||||||
|
@ -14,6 +17,10 @@ pub type CheckPairingType = (bool, bool, bool);
|
||||||
|
|
||||||
pub type BoardStateType = (DBGameState, Option<String>);
|
pub type BoardStateType = (DBGameState, Option<String>);
|
||||||
|
|
||||||
|
pub type PlaceResultType = Result<(DBPlaceStatus, Option<String>), DBPlaceError>;
|
||||||
|
|
||||||
|
// TODO use Error types instead of Strings for Result Errs
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum DBGameState {
|
pub enum DBGameState {
|
||||||
CyanTurn,
|
CyanTurn,
|
||||||
|
@ -56,6 +63,44 @@ impl From<i64> for DBGameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum DBPlaceStatus {
|
||||||
|
Accepted,
|
||||||
|
GameEnded,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DBPlaceStatus {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
DBPlaceStatus::Accepted => write!(f, "accepted"),
|
||||||
|
DBPlaceStatus::GameEnded => write!(f, "game_ended"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum DBPlaceError {
|
||||||
|
NotPairedYet,
|
||||||
|
NotYourTurn,
|
||||||
|
Illegal,
|
||||||
|
OpponentDisconnected,
|
||||||
|
UnknownID,
|
||||||
|
InternalError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DBPlaceError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
DBPlaceError::NotPairedYet => write!(f, "not_paired_yet"),
|
||||||
|
DBPlaceError::NotYourTurn => write!(f, "not_your_turn"),
|
||||||
|
DBPlaceError::Illegal => write!(f, "illegal"),
|
||||||
|
DBPlaceError::OpponentDisconnected => write!(f, "opponent_disconnected"),
|
||||||
|
DBPlaceError::UnknownID => write!(f, "unknown_id"),
|
||||||
|
DBPlaceError::InternalError => write!(f, "internal_error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum DBHandlerRequest {
|
pub enum DBHandlerRequest {
|
||||||
GetID(SyncSender<GetIDSenderType>),
|
GetID(SyncSender<GetIDSenderType>),
|
||||||
|
@ -71,6 +116,11 @@ pub enum DBHandlerRequest {
|
||||||
id: u32,
|
id: u32,
|
||||||
response_sender: SyncSender<bool>,
|
response_sender: SyncSender<bool>,
|
||||||
},
|
},
|
||||||
|
PlaceToken {
|
||||||
|
id: u32,
|
||||||
|
pos: usize,
|
||||||
|
response_sender: SyncSender<PlaceResultType>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -195,6 +245,16 @@ impl DBHandler {
|
||||||
.send(self.disconnect_player(None, id).is_ok())
|
.send(self.disconnect_player(None, id).is_ok())
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
DBHandlerRequest::PlaceToken {
|
||||||
|
id,
|
||||||
|
pos,
|
||||||
|
response_sender,
|
||||||
|
} => {
|
||||||
|
let place_result = self.place_token(None, id, pos);
|
||||||
|
// don't stop server on send fail, may have timed out and
|
||||||
|
// dropped the receiver
|
||||||
|
response_sender.send(place_result).ok();
|
||||||
|
}
|
||||||
} // match db_request
|
} // match db_request
|
||||||
|
|
||||||
false
|
false
|
||||||
|
@ -373,9 +433,9 @@ impl DBHandler {
|
||||||
.check_if_player_exists(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), player_id);
|
.check_if_player_exists(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), player_id);
|
||||||
}
|
}
|
||||||
let conn = conn.unwrap();
|
let conn = conn.unwrap();
|
||||||
let check_player_row =
|
let check_player_row: Result<u32, _> =
|
||||||
conn.query_row("SELECT id FROM players WHERE id = ?;", [player_id], |row| {
|
conn.query_row("SELECT id FROM players WHERE id = ?;", [player_id], |row| {
|
||||||
row.get::<usize, u32>(0)
|
row.get(0)
|
||||||
});
|
});
|
||||||
if let Ok(_id) = check_player_row {
|
if let Ok(_id) = check_player_row {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
@ -384,6 +444,30 @@ impl DBHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_if_player_in_game(
|
||||||
|
&self,
|
||||||
|
conn: Option<&Connection>,
|
||||||
|
player_id: u32,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
if conn.is_none() {
|
||||||
|
return self.check_if_player_in_game(
|
||||||
|
Some(&self.get_conn(DBFirstRun::NotFirstRun)?),
|
||||||
|
player_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let conn = conn.unwrap();
|
||||||
|
|
||||||
|
let check_player_game_row: Result<u32, _> = conn.query_row(
|
||||||
|
"SELECT games.id FROM games JOIN players WHERE players.id = ? AND players.game_id NOTNULL AND players.game_id = games.id;",
|
||||||
|
[player_id],
|
||||||
|
|row| row.get(0));
|
||||||
|
if check_player_game_row.is_ok() {
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_board_state(
|
fn get_board_state(
|
||||||
&self,
|
&self,
|
||||||
conn: Option<&Connection>,
|
conn: Option<&Connection>,
|
||||||
|
@ -395,36 +479,35 @@ impl DBHandler {
|
||||||
let conn = conn.unwrap();
|
let conn = conn.unwrap();
|
||||||
|
|
||||||
// TODO maybe handle "opponent_disconnected" case
|
// TODO maybe handle "opponent_disconnected" case
|
||||||
let row_result: Result<(String, i64, Option<u32>, Option<u32>), RusqliteError> =
|
let row_result: Result<(String, i64, Option<u32>, Option<u32>), RusqliteError> = conn.query_row(
|
||||||
conn.query_row(
|
"SELECT games.board, games.status, games.cyan_player, games.magenta_player FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;",
|
||||||
"SELECT games.board, games.status, games.cyan_player, games.magenta_player FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;",
|
[player_id],
|
||||||
[player_id],
|
|row| {
|
||||||
|row| {
|
let board_result = row.get(0);
|
||||||
let board_result = row.get(0);
|
let status_result = row.get(1);
|
||||||
let status_result = row.get(1);
|
let cyan_player = row.get(2);
|
||||||
let cyan_player = row.get(2);
|
let magenta_player = row.get(3);
|
||||||
let magenta_player = row.get(3);
|
if board_result.is_ok() && status_result.is_ok() && cyan_player.is_ok() && magenta_player.is_ok() {
|
||||||
if board_result.is_ok() && status_result.is_ok() && cyan_player.is_ok() && magenta_player.is_ok() {
|
if let (Ok(board), Ok(status), Ok(cyan_id), Ok(magenta_id)) = (board_result, status_result, cyan_player, magenta_player) {
|
||||||
if let (Ok(board), Ok(status), Ok(cyan_id), Ok(magenta_id)) = (board_result, status_result, cyan_player, magenta_player) {
|
Ok((board, status, cyan_id, magenta_id))
|
||||||
Ok((board, status, cyan_id, magenta_id))
|
|
||||||
} else {
|
|
||||||
unreachable!("Both row items should be Ok");
|
|
||||||
}
|
|
||||||
} else if board_result.is_err() {
|
|
||||||
board_result
|
|
||||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
|
||||||
} else if status_result.is_err() {
|
|
||||||
status_result
|
|
||||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
|
||||||
} else if cyan_player.is_err() {
|
|
||||||
cyan_player
|
|
||||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
|
||||||
} else {
|
} else {
|
||||||
magenta_player
|
unreachable!("Both row items should be Ok");
|
||||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
|
||||||
}
|
}
|
||||||
|
} else if board_result.is_err() {
|
||||||
|
board_result
|
||||||
|
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||||
|
} else if status_result.is_err() {
|
||||||
|
status_result
|
||||||
|
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||||
|
} else if cyan_player.is_err() {
|
||||||
|
cyan_player
|
||||||
|
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||||
|
} else {
|
||||||
|
magenta_player
|
||||||
|
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
);
|
||||||
if let Ok((board, status, cyan_opt, magenta_opt)) = row_result {
|
if let Ok((board, status, cyan_opt, magenta_opt)) = row_result {
|
||||||
if board.len() != (ROWS * COLS) as usize {
|
if board.len() != (ROWS * COLS) as usize {
|
||||||
// board is invalid size
|
// board is invalid size
|
||||||
|
@ -494,6 +577,160 @@ impl DBHandler {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn place_token(
|
||||||
|
&self,
|
||||||
|
conn: Option<&Connection>,
|
||||||
|
player_id: u32,
|
||||||
|
pos: usize,
|
||||||
|
) -> PlaceResultType {
|
||||||
|
if conn.is_none() {
|
||||||
|
return self.place_token(
|
||||||
|
Some(
|
||||||
|
&self
|
||||||
|
.get_conn(DBFirstRun::NotFirstRun)
|
||||||
|
.map_err(|_| DBPlaceError::InternalError)?,
|
||||||
|
),
|
||||||
|
player_id,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let conn = conn.unwrap();
|
||||||
|
|
||||||
|
// check if player exists
|
||||||
|
let player_exist_check_result = self.check_if_player_exists(Some(conn), player_id);
|
||||||
|
if let Ok(exists) = player_exist_check_result {
|
||||||
|
if !exists {
|
||||||
|
return Err(DBPlaceError::UnknownID);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(DBPlaceError::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if player belongs to a game
|
||||||
|
let player_game_result = self.check_if_player_in_game(Some(conn), player_id);
|
||||||
|
if let Ok(is_in_game) = player_game_result {
|
||||||
|
if !is_in_game {
|
||||||
|
return Err(DBPlaceError::NotPairedYet);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(DBPlaceError::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if player is cyan or magenta
|
||||||
|
let query_result_result: Result<Result<(bool, u32, String), DBPlaceError>, _> =
|
||||||
|
conn.query_row(
|
||||||
|
"SELECT cyan_player, magenta_player, status, board FROM games JOIN players WHERE players.id = ? AND players.game_id = games.id;",
|
||||||
|
[player_id],
|
||||||
|
|row| {
|
||||||
|
let cyan_id_result: Result<Option<u32>, _> = row.get(0);
|
||||||
|
let magenta_id_result: Result<Option<u32>, _> = row.get(1);
|
||||||
|
let status_result: Result<u32, _> = row.get(2);
|
||||||
|
let board_result: Result<String, _> = row.get(3);
|
||||||
|
if status_result.is_err() {
|
||||||
|
return status_result.map(|_| Ok((false, 0, "".into())));
|
||||||
|
}
|
||||||
|
let status: u32 = status_result.unwrap();
|
||||||
|
if board_result.is_err() {
|
||||||
|
return board_result.map(|_| Ok((false, 0, "".into())));
|
||||||
|
}
|
||||||
|
let board = board_result.unwrap();
|
||||||
|
if cyan_id_result.is_ok() && magenta_id_result.is_ok() {
|
||||||
|
if let (Ok(cyan_id_opt), Ok(magenta_id_opt)) = (cyan_id_result, magenta_id_result) {
|
||||||
|
if let (Some(cyan_id), Some(magenta_id)) = (cyan_id_opt, magenta_id_opt) {
|
||||||
|
Ok(Ok((cyan_id == player_id, status, board)))
|
||||||
|
} else {
|
||||||
|
Ok(Err(DBPlaceError::OpponentDisconnected))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!("both row items should be Ok")
|
||||||
|
}
|
||||||
|
} else if cyan_id_result.is_err() {
|
||||||
|
cyan_id_result.map(|_| Err(DBPlaceError::InternalError))
|
||||||
|
} else {
|
||||||
|
magenta_id_result.map(|_| Err(DBPlaceError::InternalError))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let query_result = query_result_result.map_err(|_| DBPlaceError::InternalError)?;
|
||||||
|
|
||||||
|
// if opponent has disconnected, disconnect the remaining player as well
|
||||||
|
if let Err(DBPlaceError::OpponentDisconnected) = query_result {
|
||||||
|
if self.disconnect_player(Some(conn), player_id).is_err()
|
||||||
|
|| self.clear_empty_games(Some(conn)).is_err()
|
||||||
|
{
|
||||||
|
return Err(DBPlaceError::InternalError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (is_cyan, status, board_string) = query_result?;
|
||||||
|
|
||||||
|
match status {
|
||||||
|
0 => {
|
||||||
|
// cyan's turn
|
||||||
|
if !is_cyan {
|
||||||
|
return Err(DBPlaceError::NotYourTurn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
// magenta's turn
|
||||||
|
if is_cyan {
|
||||||
|
return Err(DBPlaceError::NotYourTurn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2 | 3 | 4 => {
|
||||||
|
// game over, cyan won, or magenta won, or draw
|
||||||
|
return Ok((DBPlaceStatus::GameEnded, Some(board_string)));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// get board state
|
||||||
|
let board = board_from_string(board_string);
|
||||||
|
|
||||||
|
// find placement position or return "illegal move" if unable to
|
||||||
|
let mut final_pos = pos;
|
||||||
|
loop {
|
||||||
|
if board[final_pos].get() == BoardState::Empty {
|
||||||
|
if final_pos + COLS as usize >= board.len()
|
||||||
|
|| board[final_pos + COLS as usize].get() != BoardState::Empty
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
} else if board[final_pos + COLS as usize].get() == BoardState::Empty {
|
||||||
|
final_pos += COLS as usize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(DBPlaceError::Illegal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// place into board
|
||||||
|
if is_cyan {
|
||||||
|
board[final_pos].replace(BoardState::Cyan);
|
||||||
|
} else {
|
||||||
|
board[final_pos].replace(BoardState::Magenta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// board back to string
|
||||||
|
let (board_string, ended) = string_from_board(board, final_pos);
|
||||||
|
|
||||||
|
// update DB
|
||||||
|
let update_result = conn.execute("UPDATE games SET status = ?, board = ? FROM players WHERE players.game_id = games.id AND players.id = ?;" , params![if status == 0 { 1u8 } else { 0u8 }, board_string, player_id]);
|
||||||
|
if let Err(_e) = update_result {
|
||||||
|
return Err(DBPlaceError::InternalError);
|
||||||
|
} else if let Ok(count) = update_result {
|
||||||
|
if count != 1 {
|
||||||
|
return Err(DBPlaceError::InternalError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ended {
|
||||||
|
self.disconnect_player(Some(conn), player_id).ok();
|
||||||
|
Ok((DBPlaceStatus::GameEnded, Some(board_string)))
|
||||||
|
} else {
|
||||||
|
Ok((DBPlaceStatus::Accepted, Some(board_string)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_db_handler_thread(
|
pub fn start_db_handler_thread(
|
||||||
|
@ -531,3 +768,83 @@ fn new_board() -> String {
|
||||||
}
|
}
|
||||||
board
|
board
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn board_from_string(board_string: String) -> BoardType {
|
||||||
|
let board = new_empty_board();
|
||||||
|
|
||||||
|
for (idx, c) in board_string.chars().enumerate() {
|
||||||
|
match c {
|
||||||
|
'a' => board[idx].replace(BoardState::Empty),
|
||||||
|
'b' | 'd' | 'f' => board[idx].replace(BoardState::Cyan),
|
||||||
|
'c' | 'e' | 'g' => board[idx].replace(BoardState::Magenta),
|
||||||
|
_ => BoardState::Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
board
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the board as a String, and true if the game has ended
|
||||||
|
fn string_from_board(board: BoardType, placed: usize) -> (String, bool) {
|
||||||
|
let mut board_string = String::with_capacity(56);
|
||||||
|
|
||||||
|
// check for winning pieces
|
||||||
|
let mut win_set: HashSet<usize> = HashSet::new();
|
||||||
|
let win_opt = check_win_draw(&board);
|
||||||
|
if let Some((board_state, win_type)) = win_opt {
|
||||||
|
match win_type {
|
||||||
|
WinType::Horizontal(pos) => {
|
||||||
|
for i in pos..(pos + 4) {
|
||||||
|
win_set.insert(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WinType::Vertical(pos) => {
|
||||||
|
for i in 0..4 {
|
||||||
|
win_set.insert(pos + i * COLS as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WinType::DiagonalUp(pos) => {
|
||||||
|
for i in 0..4 {
|
||||||
|
win_set.insert(pos + i - i * COLS as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WinType::DiagonalDown(pos) => {
|
||||||
|
for i in 0..4 {
|
||||||
|
win_set.insert(pos + i + i * COLS as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WinType::None => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set values to String
|
||||||
|
let mut is_full = true;
|
||||||
|
for (idx, board_state) in board.iter().enumerate().take((COLS * ROWS) as usize) {
|
||||||
|
board_string.push(match board_state.get() {
|
||||||
|
BoardState::Empty => {
|
||||||
|
is_full = false;
|
||||||
|
'a'
|
||||||
|
}
|
||||||
|
BoardState::Cyan | BoardState::CyanWin => {
|
||||||
|
if win_set.contains(&idx) {
|
||||||
|
'd'
|
||||||
|
} else if idx == placed {
|
||||||
|
'f'
|
||||||
|
} else {
|
||||||
|
'b'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BoardState::Magenta | BoardState::MagentaWin => {
|
||||||
|
if win_set.contains(&idx) {
|
||||||
|
'e'
|
||||||
|
} else if idx == placed {
|
||||||
|
'g'
|
||||||
|
} else {
|
||||||
|
'c'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(board_string, is_full || !win_set.is_empty())
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub fn handle_json(
|
||||||
match type_str.as_str() {
|
match type_str.as_str() {
|
||||||
"pairing_request" => handle_pairing_request(tx),
|
"pairing_request" => handle_pairing_request(tx),
|
||||||
"check_pairing" => handle_check_pairing(root, tx),
|
"check_pairing" => handle_check_pairing(root, tx),
|
||||||
"place_token" => handle_place_token(root),
|
"place_token" => handle_place_token(root, tx),
|
||||||
"disconnect" => handle_disconnect(root, tx),
|
"disconnect" => handle_disconnect(root, tx),
|
||||||
"game_state" => handle_game_state(root, tx),
|
"game_state" => handle_game_state(root, tx),
|
||||||
_ => Err("{\"type\":\"invalid_type\"}".into()),
|
_ => Err("{\"type\":\"invalid_type\"}".into()),
|
||||||
|
@ -89,8 +89,69 @@ fn handle_check_pairing(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_place_token(root: Value) -> Result<String, String> {
|
fn handle_place_token(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
|
||||||
Err("{\"type\":\"unimplemented\"}".into())
|
let id_option = root.get("id");
|
||||||
|
if id_option.is_none() {
|
||||||
|
return Err("{\"type\":\"invalid_syntax\"}".into());
|
||||||
|
}
|
||||||
|
let player_id = id_option
|
||||||
|
.unwrap()
|
||||||
|
.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\"}"))?;
|
||||||
|
|
||||||
|
let position_option = root.get("position");
|
||||||
|
if position_option.is_none() {
|
||||||
|
return Err("{\"type\":\"invalid_syntax\"}".into());
|
||||||
|
}
|
||||||
|
let position = position_option
|
||||||
|
.unwrap()
|
||||||
|
.as_u64()
|
||||||
|
.ok_or_else(|| String::from("{\"type\":\"invalid_syntax\"}"))?;
|
||||||
|
let position: usize = position
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| String::from("{\"type\":\"invalid_syntax\"}"))?;
|
||||||
|
|
||||||
|
let (resp_tx, resp_rx) = sync_channel(1);
|
||||||
|
|
||||||
|
if tx
|
||||||
|
.send(DBHandlerRequest::PlaceToken {
|
||||||
|
id: player_id,
|
||||||
|
pos: position,
|
||||||
|
response_sender: resp_tx,
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err(String::from(
|
||||||
|
"{\"type\":\"place_token\", \"status\":\"internal_error\"}",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let place_result = resp_rx.recv_timeout(DB_REQUEST_TIMEOUT);
|
||||||
|
if let Ok(Ok((place_status, board_opt))) = place_result {
|
||||||
|
if let Some(board_string) = board_opt {
|
||||||
|
Ok(format!(
|
||||||
|
"{{\"type\":\"place_token\", \"status\":\"{}\", \"board\":\"{}\"}}",
|
||||||
|
place_status, board_string
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(format!(
|
||||||
|
"{{\"type\":\"place_token\", \"status\":\"{}\"}}",
|
||||||
|
place_status
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else if let Ok(Err(place_error)) = place_result {
|
||||||
|
Err(format!(
|
||||||
|
"{{\"type\":\"place_token\", \"status\":\"{}\"}}",
|
||||||
|
place_error
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(String::from(
|
||||||
|
"{\"type\":\"place_token\", \"status\":\"internal_error\"}",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_disconnect(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
|
fn handle_disconnect(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
|
||||||
|
|
|
@ -132,10 +132,10 @@ then the back-end will respond with "too\_many\_players".
|
||||||
// a - empty
|
// a - empty
|
||||||
// b - cyan
|
// b - cyan
|
||||||
// c - magenta
|
// c - magenta
|
||||||
// d - cyan placed
|
// d - cyan winning piece
|
||||||
// e - magenta placed
|
// e - magenta winning piece
|
||||||
// f - cyan winning piece
|
// f - cyan placed
|
||||||
// g - magenta winning piece
|
// g - magenta placed
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue