Compare commits
54 Commits
sprint_05_
...
main
Author | SHA1 | Date |
---|---|---|
Stephen Seo | 2d7c8c37e5 | |
Stephen Seo | 090b8bbd30 | |
Stephen Seo | 59b2bc34fb | |
Stephen Seo | 1872c4877e | |
Stephen Seo | b5529cb542 | |
Stephen Seo | f4f3ad7a5b | |
Stephen Seo | 4331a20daa | |
Stephen Seo | 047549ecb5 | |
Stephen Seo | bc6c234314 | |
Stephen Seo | 1f27defe11 | |
Stephen Seo | 3eb663c305 | |
Stephen Seo | b2ea79a7f7 | |
Stephen Seo | b4eaba09c5 | |
Stephen Seo | d88e8ef9f3 | |
Stephen Seo | 105cd880f2 | |
Stephen Seo | 36dd43bb70 | |
Stephen Seo | 5381578b08 | |
Stephen Seo | f498f2c475 | |
Stephen Seo | 8eb30fc5d5 | |
Stephen Seo | 6ef8667382 | |
Stephen Seo | a4bf4cbd25 | |
Stephen Seo | f799bae530 | |
Stephen Seo | b158e7347e | |
Stephen Seo | e6152331b0 | |
Stephen Seo | e77d25996d | |
Stephen Seo | e0ed5fc5d8 | |
Stephen Seo | 6b430660b7 | |
Stephen Seo | 174875b88b | |
Stephen Seo | 694da61bd6 | |
Stephen Seo | 3172af19f8 | |
Stephen Seo | dcc9400483 | |
Stephen Seo | 665dff94fe | |
Stephen Seo | edd3b0c65c | |
Stephen Seo | 059d0608b6 | |
Stephen Seo | f9338d4093 | |
Stephen Seo | 87d93e5b4f | |
Stephen Seo | e060d94186 | |
Stephen Seo | 96e28b9d68 | |
Stephen Seo | b26c9ff6d1 | |
Stephen Seo | d55e43cc6c | |
Stephen Seo | b7e0b522af | |
Stephen Seo | e54d239260 | |
Stephen Seo | a7c67fd098 | |
Stephen Seo | 975a878eb6 | |
Stephen Seo | f8da5f4997 | |
Stephen Seo | 501ce91ac3 | |
Stephen Seo | 27ff10293a | |
Stephen Seo | c98e4723bb | |
Stephen Seo | 73ac99b7cc | |
Stephen Seo | 381c5d3b29 | |
Stephen Seo | fdbef5f6df | |
Stephen Seo | 7a4821ac26 | |
Stephen Seo | bb7d150196 | |
Stephen Seo | 6aa2927b42 |
14
README.md
14
README.md
|
@ -12,15 +12,25 @@ The directory `back_end` holds a Rust project for the "back\_end" code. It holds
|
|||
the server-side code for the project. It is mainly used to handle "Networked
|
||||
Multiplayer" mode for the game.
|
||||
|
||||
The directory `specifications` holds defined specifications. It currently holds
|
||||
the back-end specifications (database and protocol).
|
||||
|
||||
The directory `spreadsheets` hold LibreOffice Calc documents that are
|
||||
spreadsheets organizing the work. There is a document for User Stories, a
|
||||
document for the Product Backlogs, and there will be a document for each Sprint.
|
||||
|
||||
The directory `retrospectives` holds the retrospectives of Sprints 3 and onward.
|
||||
|
||||
The directory `plans` contains the release plans.
|
||||
|
||||
The directory `pictures` holds pertinent images to the project. It includes the
|
||||
"Simple Model" of the project.
|
||||
|
||||
## Tags
|
||||
|
||||
The git repository is tagged per Sprint and per Day. [One can visualize the
|
||||
progress here.](https://git.seodisparate.com/stephenseo/EN605.607.81.SP22_ASDM_Project/graph)
|
||||
|
||||
## What is Four-Line Dropper?
|
||||
|
||||
Four-Line Dropper is a game where two players take turns dropping tokens into
|
||||
|
@ -29,3 +39,7 @@ diagonally is the win condition of the game. If the board fills up with no
|
|||
four-line matches, then the game ends in a draw. The game is called "Four-Line
|
||||
Dropper" to avoid clashing with the game's original name that is trademarked
|
||||
(this game is a clone of an existing game).
|
||||
|
||||
# Link to a hosted instance
|
||||
|
||||
[I have hosted an instance of the front-end/back-end here.](https://asdm.seodisparate.com)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "four_line_dropper_backend"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
|
|
@ -8,12 +8,16 @@
|
|||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use crate::ai::{get_ai_choice, AIDifficulty};
|
||||
use crate::constants::{
|
||||
COLS, GAME_CLEANUP_TIMEOUT, PLAYER_CLEANUP_TIMEOUT, PLAYER_COUNT_LIMIT, ROWS, TURN_SECONDS,
|
||||
BACKEND_CLEANUP_INTERVAL_SECONDS, COLS, GAME_CLEANUP_TIMEOUT, PLAYER_CLEANUP_TIMEOUT,
|
||||
PLAYER_COUNT_LIMIT, ROWS, TURN_SECONDS,
|
||||
};
|
||||
use crate::state::{
|
||||
board_from_string, new_string_board, string_from_board, BoardState, EmoteEnum, 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::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{fmt, thread};
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
|
@ -27,7 +31,14 @@ pub type GetIDSenderType = (Option<u32>, Option<bool>);
|
|||
/// third bool is if cyan player
|
||||
pub type CheckPairingType = (bool, bool, bool);
|
||||
|
||||
pub type BoardStateType = (DBGameState, Option<String>);
|
||||
/// second String is board string, third String is date updated, fourth value
|
||||
/// is EmoteEnum
|
||||
pub type BoardStateType = (
|
||||
DBGameState,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<EmoteEnum>,
|
||||
);
|
||||
|
||||
pub type PlaceResultType = Result<(DBPlaceStatus, Option<String>), DBPlaceError>;
|
||||
|
||||
|
@ -117,7 +128,10 @@ impl fmt::Display for DBPlaceError {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DBHandlerRequest {
|
||||
GetID(SyncSender<GetIDSenderType>),
|
||||
GetID {
|
||||
response_sender: SyncSender<GetIDSenderType>,
|
||||
phrase: Option<String>,
|
||||
},
|
||||
CheckPairing {
|
||||
id: u32,
|
||||
response_sender: SyncSender<CheckPairingType>,
|
||||
|
@ -135,6 +149,11 @@ pub enum DBHandlerRequest {
|
|||
pos: usize,
|
||||
response_sender: SyncSender<PlaceResultType>,
|
||||
},
|
||||
SendEmote {
|
||||
id: u32,
|
||||
emote_type: EmoteEnum,
|
||||
response_sender: SyncSender<Result<(), ()>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -162,7 +181,10 @@ impl DBHandler {
|
|||
}
|
||||
let db_request = rx_recv_result.unwrap();
|
||||
match db_request {
|
||||
DBHandlerRequest::GetID(player_tx) => {
|
||||
DBHandlerRequest::GetID {
|
||||
response_sender,
|
||||
phrase,
|
||||
} => {
|
||||
// got request to create new player, create new player
|
||||
let conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
if let Err(e) = conn_result {
|
||||
|
@ -171,10 +193,10 @@ impl DBHandler {
|
|||
}
|
||||
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 {
|
||||
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
|
||||
return false;
|
||||
}
|
||||
|
@ -196,11 +218,11 @@ impl DBHandler {
|
|||
if paired {
|
||||
// don't stop server on send fail, may have timed
|
||||
// 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 {
|
||||
// don't stop server on send fail, may have timed
|
||||
// out and dropped the receiver
|
||||
player_tx.send((Some(player_id), None)).ok();
|
||||
response_sender.send((Some(player_id), None)).ok();
|
||||
}
|
||||
} else {
|
||||
println!("Internal error, created player doesn't exist");
|
||||
|
@ -233,7 +255,9 @@ impl DBHandler {
|
|||
println!("{}", e);
|
||||
// don't stop server on send fail, may have timed out and
|
||||
// dropped the receiver
|
||||
response_sender.send((DBGameState::UnknownID, None)).ok();
|
||||
response_sender
|
||||
.send((DBGameState::UnknownID, None, None, None))
|
||||
.ok();
|
||||
return false;
|
||||
}
|
||||
// don't stop server on send fail, may have timed out and
|
||||
|
@ -260,6 +284,23 @@ impl DBHandler {
|
|||
// dropped the receiver
|
||||
response_sender.send(place_result).ok();
|
||||
}
|
||||
DBHandlerRequest::SendEmote {
|
||||
id,
|
||||
emote_type,
|
||||
response_sender,
|
||||
} => {
|
||||
let result = self.create_new_sent_emote(None, id, emote_type);
|
||||
if let Err(error_string) = result {
|
||||
println!("{}", error_string);
|
||||
// don't stop server on send fail, may have timed
|
||||
// out and dropped the receiver
|
||||
response_sender.send(Err(())).ok();
|
||||
} else {
|
||||
// don't stop server on send fail, may have timed
|
||||
// out and dropped the receiver
|
||||
response_sender.send(Ok(())).ok();
|
||||
}
|
||||
}
|
||||
} // match db_request
|
||||
|
||||
false
|
||||
|
@ -274,6 +315,7 @@ impl DBHandler {
|
|||
CREATE TABLE players (id INTEGER PRIMARY KEY NOT NULL,
|
||||
date_added TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
game_id INTEGER,
|
||||
phrase TEXT,
|
||||
FOREIGN KEY(game_id) REFERENCES games(id) ON DELETE CASCADE);
|
||||
",
|
||||
[],
|
||||
|
@ -284,6 +326,9 @@ impl DBHandler {
|
|||
}
|
||||
} else if first_run == DBFirstRun::FirstRun {
|
||||
println!("\"players\" table exists");
|
||||
if let Err(e) = self.db_check_migration(&conn) {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let result = conn.execute(
|
||||
|
@ -307,17 +352,66 @@ impl DBHandler {
|
|||
} else if first_run == DBFirstRun::FirstRun {
|
||||
println!("\"games\" table exists");
|
||||
}
|
||||
|
||||
let result = conn.execute(
|
||||
"
|
||||
CREATE TABLE emotes (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
date_added TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
receiver_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(receiver_id) REFERENCES players (id) ON DELETE CASCADE);
|
||||
",
|
||||
[],
|
||||
);
|
||||
if result.is_ok() {
|
||||
if first_run == DBFirstRun::FirstRun {
|
||||
println!("Created \"emotes\" table");
|
||||
}
|
||||
} else if first_run == DBFirstRun::FirstRun {
|
||||
println!("\"emotes\" table exists");
|
||||
}
|
||||
|
||||
Ok(conn)
|
||||
} else {
|
||||
Err(String::from("Failed to open connection"))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_new_player(&self, conn: Option<&Connection>) -> Result<u32, String> {
|
||||
if conn.is_none() {
|
||||
return self.create_new_player(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
fn db_check_migration(&self, conn: &Connection) -> Result<(), String> {
|
||||
let mut table_entries_stmt = conn
|
||||
.prepare("PRAGMA table_info(players);")
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
let mut table_entries_rows = table_entries_stmt
|
||||
.query([])
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
// check if "phrase" column exists
|
||||
let mut phrase_exists = false;
|
||||
while let Some(row) = table_entries_rows.next().map_err(|e| format!("{:?}", e))? {
|
||||
let column_name: String = row.get(1).map_err(|e| format!("{:?}", e))?;
|
||||
if column_name.contains("phrase") {
|
||||
phrase_exists = true;
|
||||
}
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
if !phrase_exists {
|
||||
conn.execute("ALTER TABLE players ADD COLUMN phrase TEXT;", [])
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
println!("Added \"phrase\" column to \"players\" in db.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_new_player(
|
||||
&self,
|
||||
conn: Option<&Connection>,
|
||||
phrase: Option<String>,
|
||||
) -> Result<u32, String> {
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let row_result: Result<usize, _> =
|
||||
conn.query_row("SELECT count(id) FROM players;", [], |row| row.get(0));
|
||||
|
@ -349,7 +443,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 {
|
||||
return Err(format!("Failed to insert player into db: {:?}", e));
|
||||
}
|
||||
|
@ -358,29 +455,49 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn pair_up_players(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self.pair_up_players(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
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;")
|
||||
.prepare("SELECT id, phrase 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))?;
|
||||
let mut phrase_map: HashMap<String, u32> = HashMap::new();
|
||||
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))?);
|
||||
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 {
|
||||
let players: [u32; 2] = [
|
||||
to_pair.take().unwrap(),
|
||||
row.get(0).map_err(|e| format!("{:?}", e))?,
|
||||
];
|
||||
self.create_game(Some(conn), &players)?;
|
||||
// pair players that did not use a phrase
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,10 +505,14 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn create_game(&self, conn: Option<&Connection>, players: &[u32; 2]) -> Result<u32, String> {
|
||||
if conn.is_none() {
|
||||
return self.create_game(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), players);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let mut game_id: u32 = thread_rng().gen();
|
||||
{
|
||||
let mut get_game_stmt = conn
|
||||
|
@ -435,13 +556,13 @@ impl DBHandler {
|
|||
}
|
||||
}
|
||||
|
||||
if conn.is_none() {
|
||||
return self.check_if_player_is_paired(
|
||||
Some(&self.get_conn(DBFirstRun::NotFirstRun)?),
|
||||
player_id,
|
||||
);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
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 {
|
||||
|
@ -478,11 +599,14 @@ impl DBHandler {
|
|||
conn: Option<&Connection>,
|
||||
player_id: u32,
|
||||
) -> Result<bool, String> {
|
||||
if conn.is_none() {
|
||||
return self
|
||||
.check_if_player_exists(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), player_id);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let check_player_row: Result<u32, _> =
|
||||
conn.query_row("SELECT id FROM players WHERE id = ?;", [player_id], |row| {
|
||||
row.get(0)
|
||||
|
@ -499,13 +623,13 @@ impl DBHandler {
|
|||
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 mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().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;",
|
||||
|
@ -523,68 +647,128 @@ impl DBHandler {
|
|||
conn: Option<&Connection>,
|
||||
player_id: u32,
|
||||
) -> Result<BoardStateType, String> {
|
||||
if conn.is_none() {
|
||||
return self.get_board_state(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), player_id);
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let mut received_emote: Option<EmoteEnum> = None;
|
||||
{
|
||||
let row_result: Result<(u64, String), RusqliteError> = conn.query_row(
|
||||
"SELECT id, type FROM emotes WHERE receiver_id = ? ORDER BY date_added ASC;",
|
||||
[player_id],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get(0).expect("emotes.id should exist"),
|
||||
row.get(1).expect("emotes.type should exist"),
|
||||
))
|
||||
},
|
||||
);
|
||||
if let Err(RusqliteError::QueryReturnedNoRows) = row_result {
|
||||
// no-op
|
||||
} else if let Err(e) = row_result {
|
||||
println!("Error while fetching received emotes: {:?}", e);
|
||||
} else {
|
||||
let (emote_id, emote_type) = row_result.unwrap();
|
||||
received_emote = emote_type.as_str().try_into().ok();
|
||||
if received_emote.is_none() {
|
||||
println!("WARNING: Invalid emote type \"{}\" in db", emote_type);
|
||||
}
|
||||
conn.execute("DELETE FROM emotes WHERE id = ?;", [emote_id])
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
|
||||
// TODO maybe handle "opponent_disconnected" case
|
||||
let row_result: Result<(String, i64, Option<u32>, Option<u32>), RusqliteError> = 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;",
|
||||
type ResultTuple = (String, i64, Option<u32>, Option<u32>, String);
|
||||
let row_result: Result<ResultTuple, RusqliteError> = conn.query_row(
|
||||
"SELECT games.board, games.status, games.cyan_player, games.magenta_player, games.turn_time_start FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;",
|
||||
[player_id],
|
||||
|row| {
|
||||
let board_result = row.get(0);
|
||||
let status_result = row.get(1);
|
||||
let cyan_player = row.get(2);
|
||||
let magenta_player = row.get(3);
|
||||
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) {
|
||||
Ok((board, status, cyan_id, magenta_id))
|
||||
let updated_time = row.get(4);
|
||||
if board_result.is_ok() && status_result.is_ok() && cyan_player.is_ok() && magenta_player.is_ok() && updated_time.is_ok() {
|
||||
if let (Ok(board), Ok(status), Ok(cyan_id), Ok(magenta_id), Ok(updated_time)) = (board_result, status_result, cyan_player, magenta_player, updated_time) {
|
||||
Ok((board, status, cyan_id, magenta_id, updated_time))
|
||||
} else {
|
||||
unreachable!("Both row items should be Ok");
|
||||
unreachable!("All 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))
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
} else if status_result.is_err() {
|
||||
status_result
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
} else if cyan_player.is_err() {
|
||||
cyan_player
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||
} else {
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
} else if magenta_player.is_err() {
|
||||
magenta_player
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
} else {
|
||||
updated_time
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
}
|
||||
}
|
||||
);
|
||||
if let Ok((board, status, cyan_opt, magenta_opt)) = row_result {
|
||||
if let Ok((board, status, cyan_opt, magenta_opt, updated_time)) = row_result {
|
||||
if board.len() != (ROWS * COLS) as usize {
|
||||
// board is invalid size
|
||||
Ok((DBGameState::InternalError, None))
|
||||
Ok((
|
||||
DBGameState::InternalError,
|
||||
None,
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
} else if cyan_opt.is_none() || magenta_opt.is_none() {
|
||||
// One player disconnected
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
// Remove the game(s) with disconnected players
|
||||
if self.clear_empty_games(Some(conn)).is_err() {
|
||||
Ok((DBGameState::InternalError, None))
|
||||
Ok((
|
||||
DBGameState::InternalError,
|
||||
None,
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
} else if status == 2 || status == 3 {
|
||||
Ok((DBGameState::from(status), Some(board)))
|
||||
Ok((
|
||||
DBGameState::from(status),
|
||||
Some(board),
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
} else {
|
||||
Ok((DBGameState::OpponentDisconnected, Some(board)))
|
||||
Ok((
|
||||
DBGameState::OpponentDisconnected,
|
||||
Some(board),
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
// Game in progress, or other state depending on "status"
|
||||
Ok((DBGameState::from(status), Some(board)))
|
||||
Ok((
|
||||
DBGameState::from(status),
|
||||
Some(board),
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
}
|
||||
} else if let Err(RusqliteError::QueryReturnedNoRows) = row_result {
|
||||
// No rows is either player doesn't exist or not paired
|
||||
let (exists, is_paired, _is_cyan) =
|
||||
self.check_if_player_is_paired(Some(conn), player_id)?;
|
||||
if !exists {
|
||||
Ok((DBGameState::UnknownID, None))
|
||||
Ok((DBGameState::UnknownID, None, None, received_emote))
|
||||
} else if !is_paired {
|
||||
Ok((DBGameState::NotPaired, None))
|
||||
Ok((DBGameState::NotPaired, None, None, received_emote))
|
||||
} else {
|
||||
unreachable!("either exists or is_paired must be false");
|
||||
}
|
||||
|
@ -595,11 +779,13 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn disconnect_player(&self, conn: Option<&Connection>, player_id: u32) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self
|
||||
.disconnect_player(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), player_id);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let stmt_result = conn.execute("DELETE FROM players WHERE id = ?;", [player_id]);
|
||||
if let Ok(1) = stmt_result {
|
||||
|
@ -610,10 +796,13 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn clear_empty_games(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self.clear_empty_games(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
// Only fails if no rows were removed, and that is not an issue
|
||||
conn.execute(
|
||||
|
@ -631,18 +820,13 @@ impl DBHandler {
|
|||
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();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
// check if player exists
|
||||
let player_exist_check_result = self.check_if_player_exists(Some(conn), player_id);
|
||||
|
@ -730,33 +914,21 @@ impl DBHandler {
|
|||
}
|
||||
2 => {
|
||||
// game over, cyan won
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
if self.clear_empty_games(Some(conn)).is_err() {
|
||||
return Err(DBPlaceError::InternalError);
|
||||
}
|
||||
return Ok((DBPlaceStatus::GameEndedCyanWon, Some(board_string)));
|
||||
}
|
||||
3 => {
|
||||
// game over, magenta won
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
if self.clear_empty_games(Some(conn)).is_err() {
|
||||
return Err(DBPlaceError::InternalError);
|
||||
}
|
||||
return Ok((DBPlaceStatus::GameEndedMagentaWon, Some(board_string)));
|
||||
}
|
||||
4 => {
|
||||
// game over, draw
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
if self.clear_empty_games(Some(conn)).is_err() {
|
||||
return Err(DBPlaceError::InternalError);
|
||||
}
|
||||
return Ok((DBPlaceStatus::GameEndedDraw, Some(board_string)));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// get board state
|
||||
let board = board_from_string(board_string);
|
||||
let board = board_from_string(&board_string);
|
||||
|
||||
// find placement position or return "illegal move" if unable to
|
||||
let mut final_pos = pos;
|
||||
|
@ -782,7 +954,7 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
// board back to string
|
||||
let (board_string, ended_state_opt) = string_from_board(board, final_pos);
|
||||
let (board_string, ended_state_opt) = string_from_board(&board, final_pos);
|
||||
|
||||
// update DB
|
||||
let update_result = if ended_state_opt.is_none() {
|
||||
|
@ -813,7 +985,6 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
if let Some(ended_state) = ended_state_opt {
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
Ok((
|
||||
match ended_state {
|
||||
BoardState::Empty => DBPlaceStatus::GameEndedDraw,
|
||||
|
@ -862,7 +1033,7 @@ impl DBHandler {
|
|||
|
||||
for row_result in rows {
|
||||
if let Ok((id, status, board)) = row_result {
|
||||
self.have_ai_take_players_turn(Some(&conn), id, status, board)?;
|
||||
self.have_ai_take_players_turn(Some(&conn), id, status, &board)?;
|
||||
} else {
|
||||
unreachable!("This part should never execute");
|
||||
}
|
||||
|
@ -876,7 +1047,7 @@ impl DBHandler {
|
|||
conn: Option<&Connection>,
|
||||
game_id: u32,
|
||||
status: u32,
|
||||
board_string: String,
|
||||
board_string: &str,
|
||||
) -> Result<(), String> {
|
||||
if status > 1 {
|
||||
return Err(String::from(
|
||||
|
@ -884,15 +1055,13 @@ impl DBHandler {
|
|||
));
|
||||
}
|
||||
|
||||
if conn.is_none() {
|
||||
return self.have_ai_take_players_turn(
|
||||
Some(&self.get_conn(DBFirstRun::NotFirstRun)?),
|
||||
game_id,
|
||||
status,
|
||||
board_string,
|
||||
);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let is_cyan = status == 0;
|
||||
let board = board_from_string(board_string);
|
||||
|
@ -931,7 +1100,7 @@ impl DBHandler {
|
|||
});
|
||||
|
||||
// get board string from board while checking if game has ended
|
||||
let (board_string, end_state_opt) = string_from_board(board, ai_choice_pos);
|
||||
let (board_string, end_state_opt) = string_from_board(&board, ai_choice_pos);
|
||||
|
||||
let state;
|
||||
if let Some(board_state) = end_state_opt {
|
||||
|
@ -958,10 +1127,13 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn cleanup_stale_games(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self.cleanup_stale_games(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM games WHERE unixepoch() - unixepoch(date_added) > ?;",
|
||||
|
@ -973,10 +1145,13 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn cleanup_stale_players(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self.cleanup_stale_players(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM players WHERE unixepoch() - unixepoch(date_added) > ? AND game_id ISNULL;",
|
||||
|
@ -986,6 +1161,79 @@ impl DBHandler {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_stale_emotes(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM emotes WHERE unixepoch() - unixepoch(date_added) > ?;",
|
||||
[GAME_CLEANUP_TIMEOUT],
|
||||
)
|
||||
.ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_new_sent_emote(
|
||||
&self,
|
||||
conn: Option<&Connection>,
|
||||
sender_id: u32,
|
||||
emote: EmoteEnum,
|
||||
) -> Result<(), String> {
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let mut prepared_stmt = conn.prepare("SELECT games.cyan_player, games.magenta_player FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;")
|
||||
.map_err(|_| String::from("Failed to prepare db query for getting opponent id for sending emote"))?;
|
||||
let row_result: Result<(Option<u32>, Option<u32>), RusqliteError> =
|
||||
prepared_stmt.query_row([sender_id], |row| Ok((row.get(0).ok(), row.get(1).ok())));
|
||||
if let Err(RusqliteError::QueryReturnedNoRows) = row_result {
|
||||
return Err(String::from("Failed to send emote, game doesn't exist"));
|
||||
} else if let Err(e) = row_result {
|
||||
return Err(format!("Failed to send emote: {:?}", e));
|
||||
}
|
||||
let (cyan_player_opt, magenta_player_opt) = row_result.unwrap();
|
||||
if cyan_player_opt.is_none() {
|
||||
return Err(String::from(
|
||||
"Failed to send emote, cyan player disconnected",
|
||||
));
|
||||
} else if magenta_player_opt.is_none() {
|
||||
return Err(String::from(
|
||||
"Failed to send emote, magenta player disconnected",
|
||||
));
|
||||
}
|
||||
let cyan_player_id = cyan_player_opt.unwrap();
|
||||
let magenta_player_id = magenta_player_opt.unwrap();
|
||||
|
||||
let receiver_id = if cyan_player_id == sender_id {
|
||||
magenta_player_id
|
||||
} else {
|
||||
cyan_player_id
|
||||
};
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO emotes (type, receiver_id) VALUES (?, ?);",
|
||||
params![String::from(emote), receiver_id],
|
||||
)
|
||||
.map_err(|_| {
|
||||
format!(
|
||||
"Failed to store emote from player {} to player {}",
|
||||
sender_id, receiver_id
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_db_handler_thread(
|
||||
|
@ -1007,6 +1255,8 @@ pub fn start_db_handler_thread(
|
|||
return;
|
||||
}
|
||||
|
||||
let mut cleanup_instant = Instant::now();
|
||||
let cleanup_duration = Duration::from_secs(BACKEND_CLEANUP_INTERVAL_SECONDS);
|
||||
'outer: loop {
|
||||
if handler.handle_request() {
|
||||
handler.shutdown_tx.send(()).ok();
|
||||
|
@ -1017,11 +1267,21 @@ pub fn start_db_handler_thread(
|
|||
println!("{}", e);
|
||||
}
|
||||
|
||||
if let Err(e) = handler.cleanup_stale_games(None) {
|
||||
println!("{}", e);
|
||||
}
|
||||
if let Err(e) = handler.cleanup_stale_players(None) {
|
||||
println!("{}", e);
|
||||
if cleanup_instant.elapsed() > cleanup_duration {
|
||||
let conn = handler.get_conn(DBFirstRun::NotFirstRun).ok();
|
||||
cleanup_instant = Instant::now();
|
||||
if let Err(e) = handler.cleanup_stale_games(conn.as_ref()) {
|
||||
println!("{}", e);
|
||||
}
|
||||
if let Err(e) = handler.cleanup_stale_players(conn.as_ref()) {
|
||||
println!("{}", e);
|
||||
}
|
||||
if let Err(e) = handler.cleanup_stale_emotes(conn.as_ref()) {
|
||||
println!("{}", e);
|
||||
}
|
||||
if let Err(e) = handler.clear_empty_games(conn.as_ref()) {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use crate::db_handler::{CheckPairingType, DBHandlerRequest, GetIDSenderType};
|
||||
use crate::{
|
||||
constants::BACKEND_PHRASE_MAX_LENGTH,
|
||||
db_handler::{CheckPairingType, DBHandlerRequest, GetIDSenderType},
|
||||
state::EmoteEnum,
|
||||
};
|
||||
|
||||
use std::{
|
||||
sync::mpsc::{sync_channel, SyncSender},
|
||||
|
@ -24,11 +28,12 @@ pub fn handle_json(
|
|||
) -> Result<String, String> {
|
||||
if let Some(Value::String(type_str)) = root.get("type") {
|
||||
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),
|
||||
"place_token" => handle_place_token(root, tx),
|
||||
"disconnect" => handle_disconnect(root, tx),
|
||||
"game_state" => handle_game_state(root, tx),
|
||||
"send_emote" => handle_send_emote(root, tx),
|
||||
_ => Err("{\"type\":\"invalid_type\"}".into()),
|
||||
}
|
||||
} else {
|
||||
|
@ -36,9 +41,37 @@ 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);
|
||||
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(mut phrase_str) = phrase_text.as_str() {
|
||||
if !phrase_str.is_empty() {
|
||||
if phrase_str.len() > BACKEND_PHRASE_MAX_LENGTH {
|
||||
let mut idx = BACKEND_PHRASE_MAX_LENGTH;
|
||||
while idx > 0 && !phrase_str.is_char_boundary(idx) {
|
||||
idx -= 1;
|
||||
}
|
||||
if idx == 0 {
|
||||
phrase_str = "";
|
||||
} else {
|
||||
phrase_str = phrase_str.split_at(idx).0;
|
||||
}
|
||||
}
|
||||
|
||||
if !phrase_str.is_empty() {
|
||||
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());
|
||||
}
|
||||
if let Ok((pid_opt, is_cyan_opt)) = player_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||
|
@ -233,12 +266,27 @@ fn handle_game_state(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<St
|
|||
return Err("{\"type\":\"game_state\", \"status\":\"internal_error\"}".into());
|
||||
}
|
||||
|
||||
if let Ok((db_game_state, board_string_opt)) = resp_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||
if let Ok((db_game_state, board_string_opt, updated_time_opt, received_emote_opt)) =
|
||||
resp_rx.recv_timeout(DB_REQUEST_TIMEOUT)
|
||||
{
|
||||
if let Some(board_string) = board_string_opt {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\"}}",
|
||||
db_game_state, board_string
|
||||
))
|
||||
let updated_time = if let Some(time_string) = updated_time_opt {
|
||||
time_string
|
||||
} else {
|
||||
return Err("{\"type\":\"game_state\", \"status\":\"internal_error\"}".into());
|
||||
};
|
||||
|
||||
if let Some(emote) = received_emote_opt {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"peer_emote\": \"{}\", \"updated_time\": \"{}\"}}",
|
||||
db_game_state, board_string, emote, updated_time
|
||||
))
|
||||
} else {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"updated_time\": \"{}\"}}",
|
||||
db_game_state, board_string, updated_time
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"game_state\", \"status\":\"{}\"}}",
|
||||
|
@ -249,3 +297,56 @@ fn handle_game_state(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<St
|
|||
Err("{\"type\":\"game_state\", \"status\":\"internal_error_timeout\"}".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_send_emote(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
|
||||
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 emote_type_option = root.get("emote");
|
||||
if emote_type_option.is_none() {
|
||||
return Err("{\"type\":\"invalid_syntax\"}".into());
|
||||
}
|
||||
let emote_type_option = emote_type_option.unwrap().as_str();
|
||||
if emote_type_option.is_none() {
|
||||
return Err("{\"type\":\"invalid_syntax\"}".into());
|
||||
}
|
||||
let emote_type = emote_type_option.unwrap();
|
||||
|
||||
let emote_enum: Result<EmoteEnum, ()> = emote_type.try_into();
|
||||
if emote_enum.is_err() {
|
||||
return Err("{\"type\":\"invalid_syntax\"}".into());
|
||||
}
|
||||
let emote_enum = emote_enum.unwrap();
|
||||
|
||||
let (resp_tx, resp_rx) = sync_channel(1);
|
||||
|
||||
if tx
|
||||
.send(DBHandlerRequest::SendEmote {
|
||||
id: player_id,
|
||||
emote_type: emote_enum,
|
||||
response_sender: resp_tx,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
return Err("{\"type\":\"send_emote\", \"status\":\"internal_error\"}".into());
|
||||
}
|
||||
|
||||
if let Ok(db_response) = resp_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||
if db_response.is_ok() {
|
||||
Ok("{\"type\":\"send_emote\", \"status\":\"ok\"}".into())
|
||||
} else {
|
||||
Err("{\"type\":\"send_emote\", \"status\":\"internal_error\"}".into())
|
||||
}
|
||||
} else {
|
||||
Err("{\"type\":\"send_emote\", \"status\":\"internal_error\"}".into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
|
||||
[[package]]
|
||||
name = "boolinator"
|
||||
|
@ -16,9 +16,9 @@ checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9"
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.9.1"
|
||||
version = "3.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -71,10 +71,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-console"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3907f786f65bbb4f419e918b0c5674175ef1c231ecda93b2dbd65fd1e8882637"
|
||||
checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
|
@ -83,9 +84,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-dialogs"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ffb557a2ea2ed283f1334423d303a336fad55fb8572d51ae488f828b1464b40"
|
||||
checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
|
@ -93,9 +94,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-events"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "088514ec8ef284891c762c88a66b639b3a730134714692ee31829765c5bc814f"
|
||||
checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
|
@ -103,9 +104,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-file"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa5d6084efa4a2b182ef3a8649cb6506cb4843f22cf907c6e0a799944248ae90"
|
||||
checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7"
|
||||
dependencies = [
|
||||
"gloo-events",
|
||||
"js-sys",
|
||||
|
@ -115,9 +116,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-render"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b4cda6e149df3bb4a3c6a343873903e5bcc2448a9877d61bb8274806ad67f6e"
|
||||
checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
|
@ -125,9 +126,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-storage"
|
||||
version = "0.2.0"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5057761927af1b1929d02b1f49cf83553dd347a473ee7c8bb08420f2673ffc"
|
||||
checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"js-sys",
|
||||
|
@ -140,9 +141,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.2.3"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e"
|
||||
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
@ -150,26 +151,28 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.1.2"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c77af6f96a4f9e27c8ac23a88407381a31f4a74c3fb985c85aa79b8d898136"
|
||||
checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.8.0"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
|
@ -177,15 +180,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.56"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -198,12 +201,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
|
@ -220,7 +226,7 @@ dependencies = [
|
|||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -237,59 +243,59 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.15"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls-hkt"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63"
|
||||
checksum = "3ddc765d3410d9f6c6ca071bf0b67f6b01e3ec4595dc3892f02677e75819dddc"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.136"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.136"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.79"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -298,46 +304,60 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.5"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.86"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
|
@ -347,9 +367,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"serde",
|
||||
|
@ -359,24 +379,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.58",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.29"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
|
||||
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
|
@ -386,9 +406,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -396,22 +416,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.58",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-logger"
|
||||
|
@ -426,9 +446,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.56"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
|
||||
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
@ -464,5 +484,5 @@ dependencies = [
|
|||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ edition = "2021"
|
|||
yew = "0.19"
|
||||
log = "0.4.6"
|
||||
wasm-logger = "0.2.0"
|
||||
web-sys = { version = "0.3.56", features = ["Window", "Document", "Element", "Request", "RequestInit", "Headers", "RequestMode", "Response", "ReadableStream", "AddEventListenerOptions", "EventTarget"] }
|
||||
web-sys = { version = "0.3.56", features = ["Window", "Document", "Element", "Request", "RequestInit", "Headers", "RequestMode", "Response", "ReadableStream", "AddEventListenerOptions", "EventListenerOptions", "EventTarget"] }
|
||||
js-sys = "0.3.56"
|
||||
oorandom = "11.1.3"
|
||||
wasm-bindgen = { version = "0.2.79", features = ["serde-serialize"] }
|
||||
|
|
|
@ -42,9 +42,50 @@
|
|||
grid-row: 2;
|
||||
grid-column: 3;
|
||||
}
|
||||
button.menuMultiplayer {
|
||||
div.multiplayerMenu {
|
||||
grid-row: 2;
|
||||
grid-column: 4;
|
||||
|
||||
display: grid;
|
||||
}
|
||||
div.emote_wrapper {
|
||||
grid-row: 4;
|
||||
grid-column: 8;
|
||||
display: grid;
|
||||
}
|
||||
button.emote {
|
||||
font-size: 2em;
|
||||
}
|
||||
b.emote {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
button#emote_smile {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
button#emote_neutral {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
}
|
||||
button#emote_frown {
|
||||
grid-row: 1;
|
||||
grid-column: 3;
|
||||
}
|
||||
button#emote_think {
|
||||
grid-row: 1;
|
||||
grid-column: 4;
|
||||
}
|
||||
button.networkedMultiplayer {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
button.NMPhrase {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
}
|
||||
input.NMPhrase {
|
||||
grid-row: 3;
|
||||
grid-column: 1;
|
||||
}
|
||||
b.menuText {
|
||||
color: #FFF;
|
||||
|
@ -188,6 +229,12 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
button#resetbutton {
|
||||
background-color: #C55;
|
||||
color: #FFF;
|
||||
grid-row: 2;
|
||||
grid-column: 8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</html>
|
||||
|
|
|
@ -13,6 +13,9 @@ use crate::game_logic::check_win_draw;
|
|||
use crate::random_helper::get_seeded_random;
|
||||
use crate::state::{board_deep_clone, BoardState, BoardType, Turn};
|
||||
|
||||
const AI_THIRD_MAX_UTILITY: f64 = 0.89;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum AIDifficulty {
|
||||
Easy,
|
||||
|
@ -176,25 +179,25 @@ fn get_utility_for_slot(player: Turn, slot: SlotChoice, board: &BoardType) -> Op
|
|||
|
||||
// check if placing a token here connects 2 pieces
|
||||
if get_block_amount(player.get_opposite(), idx, 2, board) {
|
||||
utility *= 1.5;
|
||||
if utility >= 0.8 {
|
||||
utility = 0.8;
|
||||
utility *= 1.22;
|
||||
if utility >= AI_THIRD_MAX_UTILITY {
|
||||
utility = AI_THIRD_MAX_UTILITY;
|
||||
}
|
||||
}
|
||||
|
||||
// check if placing a token here blocks 2 pieces
|
||||
if get_block_amount(player, idx, 2, board) {
|
||||
utility *= 1.2;
|
||||
if utility >= 0.8 {
|
||||
utility = 0.8;
|
||||
utility *= 1.11;
|
||||
if utility >= AI_THIRD_MAX_UTILITY {
|
||||
utility = AI_THIRD_MAX_UTILITY;
|
||||
}
|
||||
}
|
||||
|
||||
// check if placing a token here connects 1 piece
|
||||
if get_block_amount(player.get_opposite(), idx, 1, board) {
|
||||
utility *= 1.09;
|
||||
if utility >= 0.8 {
|
||||
utility = 0.8;
|
||||
utility *= 1.05;
|
||||
if utility >= AI_THIRD_MAX_UTILITY {
|
||||
utility = AI_THIRD_MAX_UTILITY;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,3 +333,24 @@ fn get_block_amount(player: Turn, idx: usize, amount: usize, board: &BoardType)
|
|||
// exhausted all possible potential wins, therefore does not block a win
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::state::new_empty_board;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_get_block_amount() {
|
||||
let board = new_empty_board();
|
||||
board[51].set(BoardState::Cyan);
|
||||
board[52].set(BoardState::Cyan);
|
||||
board[53].set(BoardState::Cyan);
|
||||
assert!(!get_block_amount(Turn::MagentaPlayer, 50, 4, &board));
|
||||
assert!(get_block_amount(Turn::MagentaPlayer, 50, 3, &board));
|
||||
assert!(get_block_amount(Turn::MagentaPlayer, 50, 2, &board));
|
||||
assert!(!get_block_amount(Turn::MagentaPlayer, 54, 4, &board));
|
||||
assert!(get_block_amount(Turn::MagentaPlayer, 54, 3, &board));
|
||||
assert!(get_block_amount(Turn::MagentaPlayer, 54, 2, &board));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,22 +9,36 @@
|
|||
pub const ROWS: u8 = 8;
|
||||
pub const COLS: u8 = 7;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const INFO_TEXT_MAX_ITEMS: u32 = 100;
|
||||
|
||||
pub const AI_EASY_MAX_CHOICES: usize = 5;
|
||||
pub const AI_NORMAL_MAX_CHOICES: usize = 3;
|
||||
#[allow(dead_code)]
|
||||
pub const AI_CHOICE_DURATION_MILLIS: i32 = 1000;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const PLAYER_COUNT_LIMIT: usize = 1000;
|
||||
#[allow(dead_code)]
|
||||
pub const TURN_SECONDS: u64 = 25;
|
||||
#[allow(dead_code)]
|
||||
pub const GAME_CLEANUP_TIMEOUT: u64 = (TURN_SECONDS + 1) * ((ROWS * COLS) as u64 + 5u64);
|
||||
#[allow(dead_code)]
|
||||
pub const PLAYER_CLEANUP_TIMEOUT: u64 = 300;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const BACKEND_TICK_DURATION_MILLIS: i32 = 500;
|
||||
#[allow(dead_code)]
|
||||
pub const BACKEND_CLEANUP_INTERVAL_SECONDS: u64 = 120;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const BACKEND_PHRASE_MAX_LENGTH: usize = 128;
|
||||
|
||||
// TODO: Change this to "https://asdm.seodisparate.com/api" when backend is installed
|
||||
#[allow(dead_code)]
|
||||
#[cfg(debug_assertions)]
|
||||
pub const BACKEND_URL: &str = "http://testlocalhost/api";
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const BACKEND_URL: &str = "https://asdm.seodisparate.com/api";
|
||||
|
|
|
@ -10,7 +10,7 @@ use js_sys::{Function, JsString, Promise};
|
|||
use std::collections::HashMap;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{window, Document, Request, RequestInit, Window};
|
||||
use web_sys::{window, Document, Window};
|
||||
|
||||
use crate::constants::BACKEND_URL;
|
||||
|
||||
|
@ -119,34 +119,6 @@ pub fn element_has_class(document: &Document, id: &str, class: &str) -> Result<b
|
|||
Ok(element_class.contains(class))
|
||||
}
|
||||
|
||||
pub fn create_json_request(target_url: &str, json_body: &str) -> Result<Request, String> {
|
||||
let mut req_init: RequestInit = RequestInit::new();
|
||||
req_init.body(Some(&JsValue::from_str(json_body)));
|
||||
req_init.method("POST");
|
||||
// TODO omit the NoCors when hosted on website
|
||||
req_init.mode(web_sys::RequestMode::NoCors);
|
||||
// req_init.headers(
|
||||
// &JsValue::from_str("{'Content-Type': 'application/json'}"),
|
||||
// &JsValue::from_serde("{'Content-Type': 'application/json'}")
|
||||
// .map_err(|e| format!("{}", e))?,
|
||||
// &JsValue::from_serde("'headers': { 'Content-Type': 'application/json' }")
|
||||
// .map_err(|e| format!("{}", e))?,
|
||||
// );
|
||||
|
||||
let request: Request =
|
||||
Request::new_with_str_and_init(target_url, &req_init).map_err(|e| format!("{:?}", e))?;
|
||||
request
|
||||
.headers()
|
||||
.set("Content-Type", "application/json")
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
request
|
||||
.headers()
|
||||
.set("Accept", "application/json")
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
pub async fn send_to_backend(entries: HashMap<String, String>) -> Result<String, String> {
|
||||
let mut send_json_string = String::from("{");
|
||||
for (key, value) in entries {
|
||||
|
|
|
@ -12,12 +12,13 @@ use crate::game_logic::{check_win_draw, WinType};
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::hash_set::HashSet;
|
||||
use std::fmt::Display;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum GameState {
|
||||
MainMenu,
|
||||
SinglePlayer(Turn, AIDifficulty),
|
||||
|
@ -26,38 +27,45 @@ pub enum GameState {
|
|||
paired: bool,
|
||||
current_side: Option<Turn>,
|
||||
current_turn: Turn,
|
||||
phrase: Option<String>,
|
||||
},
|
||||
PostGameResults(BoardState),
|
||||
}
|
||||
|
||||
impl GameState {
|
||||
pub fn is_networked_multiplayer(self) -> bool {
|
||||
#[allow(dead_code)]
|
||||
pub fn is_networked_multiplayer(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
*self,
|
||||
GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
current_side: _,
|
||||
current_turn: _
|
||||
current_turn: _,
|
||||
phrase: _,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_networked_paired(&mut self) {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
ref mut paired,
|
||||
current_side: _,
|
||||
current_turn: _,
|
||||
phrase: _,
|
||||
} = self
|
||||
{
|
||||
*paired = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_networked_current_side(&self) -> Option<Turn> {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
current_side,
|
||||
current_turn: _,
|
||||
phrase: _,
|
||||
} = *self
|
||||
{
|
||||
current_side
|
||||
|
@ -66,17 +74,20 @@ impl GameState {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_networked_current_side(&mut self, side: Option<Turn>) {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
ref mut current_side,
|
||||
current_turn: _,
|
||||
phrase: _,
|
||||
} = self
|
||||
{
|
||||
*current_side = side;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_current_turn(&self) -> Turn {
|
||||
if let GameState::SinglePlayer(turn, _) = *self {
|
||||
turn
|
||||
|
@ -84,6 +95,7 @@ impl GameState {
|
|||
paired: _,
|
||||
current_side: _,
|
||||
current_turn,
|
||||
phrase: _,
|
||||
} = *self
|
||||
{
|
||||
current_turn
|
||||
|
@ -92,29 +104,42 @@ impl GameState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_network_current_side(&self) -> Option<Turn> {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
current_side,
|
||||
current_turn: _,
|
||||
} = *self
|
||||
{
|
||||
current_side
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_networked_current_turn(&mut self, turn: Turn) {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
current_side: _,
|
||||
ref mut current_turn,
|
||||
phrase: _,
|
||||
} = self
|
||||
{
|
||||
*current_turn = turn;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_phrase(&self) -> Option<String> {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
current_side: _,
|
||||
current_turn: _,
|
||||
phrase,
|
||||
} = self
|
||||
{
|
||||
phrase.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_singleplayer_current_side(&self) -> Option<Turn> {
|
||||
if let GameState::SinglePlayer(turn, _) = *self {
|
||||
Some(turn)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GameState {
|
||||
|
@ -128,15 +153,17 @@ impl From<MainMenuMessage> for GameState {
|
|||
match msg {
|
||||
MainMenuMessage::SinglePlayer(t, ai) => GameState::SinglePlayer(t, ai),
|
||||
MainMenuMessage::LocalMultiplayer => GameState::LocalMultiplayer,
|
||||
MainMenuMessage::NetworkedMultiplayer => GameState::NetworkedMultiplayer {
|
||||
MainMenuMessage::NetworkedMultiplayer(phrase) => GameState::NetworkedMultiplayer {
|
||||
paired: false,
|
||||
current_side: None,
|
||||
current_turn: Turn::CyanPlayer,
|
||||
phrase,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum BoardState {
|
||||
Empty,
|
||||
|
@ -174,10 +201,12 @@ impl From<Turn> for BoardState {
|
|||
}
|
||||
|
||||
impl BoardState {
|
||||
#[allow(dead_code)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
*self == BoardState::Empty
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_win(self) -> bool {
|
||||
match self {
|
||||
BoardState::Empty | BoardState::Cyan | BoardState::Magenta => false,
|
||||
|
@ -185,6 +214,7 @@ impl BoardState {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn into_win(self) -> Self {
|
||||
match self {
|
||||
BoardState::Empty => BoardState::Empty,
|
||||
|
@ -193,8 +223,9 @@ impl BoardState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_win(&self) -> Self {
|
||||
match *self {
|
||||
#[allow(dead_code, clippy::wrong_self_convention)]
|
||||
pub fn from_win(self) -> Self {
|
||||
match self {
|
||||
BoardState::Empty => BoardState::Empty,
|
||||
BoardState::Cyan | BoardState::CyanWin => BoardState::Cyan,
|
||||
BoardState::Magenta | BoardState::MagentaWin => BoardState::Magenta,
|
||||
|
@ -202,6 +233,7 @@ impl BoardState {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Turn {
|
||||
CyanPlayer,
|
||||
|
@ -227,6 +259,7 @@ impl From<BoardState> for Turn {
|
|||
}
|
||||
|
||||
impl Turn {
|
||||
#[allow(dead_code)]
|
||||
pub fn get_color(&self) -> &str {
|
||||
match *self {
|
||||
Turn::CyanPlayer => "cyan",
|
||||
|
@ -244,6 +277,7 @@ impl Turn {
|
|||
|
||||
pub type BoardType = [Rc<Cell<BoardState>>; 56];
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_empty_board() -> BoardType {
|
||||
[
|
||||
Rc::new(Cell::new(BoardState::default())),
|
||||
|
@ -305,6 +339,7 @@ pub fn new_empty_board() -> BoardType {
|
|||
]
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn board_deep_clone(board: &BoardType) -> BoardType {
|
||||
let cloned_board = new_empty_board();
|
||||
for i in 0..board.len() {
|
||||
|
@ -316,6 +351,7 @@ pub fn board_deep_clone(board: &BoardType) -> BoardType {
|
|||
|
||||
pub type PlacedType = [Rc<Cell<bool>>; 56];
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_placed() -> PlacedType {
|
||||
[
|
||||
Rc::new(Cell::new(false)),
|
||||
|
@ -377,10 +413,11 @@ pub fn new_placed() -> PlacedType {
|
|||
]
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SharedState {
|
||||
pub board: BoardType,
|
||||
pub game_state: Rc<Cell<GameState>>,
|
||||
pub game_state: Rc<RefCell<GameState>>,
|
||||
pub turn: Rc<Cell<Turn>>,
|
||||
pub placed: PlacedType,
|
||||
}
|
||||
|
@ -390,7 +427,7 @@ impl Default for SharedState {
|
|||
Self {
|
||||
// cannot use [<type>; 56] because Rc does not impl Copy
|
||||
board: new_empty_board(),
|
||||
game_state: Rc::new(Cell::new(GameState::default())),
|
||||
game_state: Rc::new(RefCell::new(GameState::default())),
|
||||
turn: Rc::new(Cell::new(Turn::CyanPlayer)),
|
||||
placed: new_placed(),
|
||||
}
|
||||
|
@ -399,13 +436,15 @@ impl Default for SharedState {
|
|||
|
||||
// This enum moved from yew_components module so that this module would have no
|
||||
// dependencies on the yew_components module
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MainMenuMessage {
|
||||
SinglePlayer(Turn, AIDifficulty),
|
||||
LocalMultiplayer,
|
||||
NetworkedMultiplayer,
|
||||
NetworkedMultiplayer(Option<String>),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_string_board() -> String {
|
||||
let mut board = String::with_capacity(56);
|
||||
for _i in 0..56 {
|
||||
|
@ -414,7 +453,8 @@ pub fn new_string_board() -> String {
|
|||
board
|
||||
}
|
||||
|
||||
pub fn board_from_string(board_string: String) -> BoardType {
|
||||
#[allow(dead_code)]
|
||||
pub fn board_from_string(board_string: &str) -> BoardType {
|
||||
let board = new_empty_board();
|
||||
|
||||
for (idx, c) in board_string.chars().enumerate() {
|
||||
|
@ -433,12 +473,13 @@ pub fn board_from_string(board_string: String) -> BoardType {
|
|||
|
||||
/// Returns the board as a String, and None if game has not ended, Empty if game
|
||||
/// ended in a draw, or a player if that player has won
|
||||
pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option<BoardState>) {
|
||||
#[allow(dead_code)]
|
||||
pub fn string_from_board(board: &BoardType, placed: usize) -> (String, Option<BoardState>) {
|
||||
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);
|
||||
let win_opt = check_win_draw(board);
|
||||
if let Some((_board_state, win_type)) = win_opt {
|
||||
match win_type {
|
||||
WinType::Horizontal(pos) => {
|
||||
|
@ -505,9 +546,11 @@ pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option<Boa
|
|||
if is_full && win_set.is_empty() {
|
||||
(board_string, Some(BoardState::Empty))
|
||||
} else if !win_set.is_empty() {
|
||||
let winning_char: char =
|
||||
board_string.chars().collect::<Vec<char>>()[*win_set.iter().next().unwrap()];
|
||||
(
|
||||
board_string.clone(),
|
||||
if board_string.chars().collect::<Vec<char>>()[*win_set.iter().next().unwrap()] == 'd' {
|
||||
if winning_char == 'd' || winning_char == 'h' {
|
||||
Some(BoardState::CyanWin)
|
||||
} else {
|
||||
Some(BoardState::MagentaWin)
|
||||
|
@ -518,6 +561,7 @@ pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option<Boa
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PairingRequestResponse {
|
||||
pub r#type: String,
|
||||
|
@ -526,6 +570,7 @@ pub struct PairingRequestResponse {
|
|||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PairingStatusResponse {
|
||||
pub r#type: String,
|
||||
|
@ -533,13 +578,17 @@ pub struct PairingStatusResponse {
|
|||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GameStateResponse {
|
||||
pub r#type: String,
|
||||
pub status: String,
|
||||
pub board: Option<String>,
|
||||
pub peer_emote: Option<String>,
|
||||
pub updated_time: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PlaceTokenResponse {
|
||||
pub r#type: String,
|
||||
|
@ -547,6 +596,14 @@ pub struct PlaceTokenResponse {
|
|||
pub board: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SendEmoteRequestResponse {
|
||||
pub r#type: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum NetworkedGameState {
|
||||
CyanTurn,
|
||||
|
@ -560,6 +617,7 @@ pub enum NetworkedGameState {
|
|||
UnknownID,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum PlacedEnum {
|
||||
Accepted,
|
||||
|
@ -568,6 +626,62 @@ pub enum PlacedEnum {
|
|||
Other(NetworkedGameState),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum EmoteEnum {
|
||||
Smile,
|
||||
Neutral,
|
||||
Frown,
|
||||
Think,
|
||||
}
|
||||
|
||||
impl Display for EmoteEnum {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
EmoteEnum::Smile => f.write_str("smile"),
|
||||
EmoteEnum::Neutral => f.write_str("neutral"),
|
||||
EmoteEnum::Frown => f.write_str("frown"),
|
||||
EmoteEnum::Think => f.write_str("think"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for EmoteEnum {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value.to_lowercase().as_str() {
|
||||
"smile" => Ok(Self::Smile),
|
||||
"neutral" => Ok(Self::Neutral),
|
||||
"frown" => Ok(Self::Frown),
|
||||
"think" => Ok(Self::Think),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EmoteEnum> for String {
|
||||
fn from(e: EmoteEnum) -> Self {
|
||||
match e {
|
||||
EmoteEnum::Smile => "smile".into(),
|
||||
EmoteEnum::Neutral => "neutral".into(),
|
||||
EmoteEnum::Frown => "frown".into(),
|
||||
EmoteEnum::Think => "think".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EmoteEnum {
|
||||
pub fn get_unicode(&self) -> char {
|
||||
match *self {
|
||||
EmoteEnum::Smile => '🙂',
|
||||
EmoteEnum::Neutral => '😐',
|
||||
EmoteEnum::Frown => '🙁',
|
||||
EmoteEnum::Think => '🤔',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -582,12 +696,59 @@ mod tests {
|
|||
paired: false,
|
||||
current_side: None,
|
||||
current_turn: Turn::CyanPlayer,
|
||||
phrase: None,
|
||||
};
|
||||
assert!(state.is_networked_multiplayer());
|
||||
let state = GameState::NetworkedMultiplayer {
|
||||
paired: true,
|
||||
current_side: Some(Turn::CyanPlayer),
|
||||
current_turn: Turn::MagentaPlayer,
|
||||
phrase: None,
|
||||
};
|
||||
assert!(state.is_networked_multiplayer());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_board_string() {
|
||||
let board = new_empty_board();
|
||||
board[49].set(BoardState::Cyan);
|
||||
board[51].set(BoardState::Cyan);
|
||||
board[52].set(BoardState::Cyan);
|
||||
board[53].set(BoardState::Cyan);
|
||||
board[54].set(BoardState::Cyan);
|
||||
board[55].set(BoardState::Magenta);
|
||||
|
||||
let (board_string, state_opt) = string_from_board(&board, 51);
|
||||
|
||||
let board_chars: Vec<char> = board_string.chars().collect();
|
||||
assert_eq!(board_chars[49], 'b');
|
||||
assert_eq!(board_chars[50], 'a');
|
||||
assert_eq!(board_chars[51], 'h');
|
||||
assert_eq!(board_chars[52], 'd');
|
||||
assert_eq!(board_chars[53], 'd');
|
||||
assert_eq!(board_chars[54], 'd');
|
||||
assert_eq!(board_chars[55], 'c');
|
||||
|
||||
assert_eq!(state_opt, Some(BoardState::CyanWin));
|
||||
|
||||
board[49].set(BoardState::Magenta);
|
||||
board[51].set(BoardState::Magenta);
|
||||
board[52].set(BoardState::Magenta);
|
||||
board[53].set(BoardState::Magenta);
|
||||
board[54].set(BoardState::Magenta);
|
||||
board[55].set(BoardState::Cyan);
|
||||
|
||||
let (board_string, state_opt) = string_from_board(&board, 51);
|
||||
|
||||
let board_chars: Vec<char> = board_string.chars().collect();
|
||||
assert_eq!(board_chars[49], 'c');
|
||||
assert_eq!(board_chars[50], 'a');
|
||||
assert_eq!(board_chars[51], 'i');
|
||||
assert_eq!(board_chars[52], 'e');
|
||||
assert_eq!(board_chars[53], 'e');
|
||||
assert_eq!(board_chars[54], 'e');
|
||||
assert_eq!(board_chars[55], 'b');
|
||||
|
||||
assert_eq!(state_opt, Some(BoardState::MagentaWin));
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
@ -0,0 +1,18 @@
|
|||
# Release Plan
|
||||
|
||||
The first Sprint will focus on developing the Minimum Viable Product (MVP).
|
||||
|
||||
Every Sprint afterwards will iterate on the MVP until it reaches enough
|
||||
maturity, or in other words, has implemented all the main features.
|
||||
|
||||
After the main features have been implemented, every Sprint afterwards will
|
||||
refine the product. See the `technical_release_plan.md` for technical details.
|
||||
|
||||
# Sprints
|
||||
|
||||
The first Sprint will focus on creating the prototype that will serve as the
|
||||
Minimum Viable Product (MVP).
|
||||
|
||||
Every Sprint afterwards will choose and work on User Stories that are ideally
|
||||
the highest priority (at the time) and of lowest estimated size (priority takes
|
||||
precedence, but low sizes are ideal).
|
|
@ -4,7 +4,7 @@ This Release Plan was created in April, a few weeks after the project weas
|
|||
actually started. However, the intended plan for releases has not changed during
|
||||
the creation of the project.
|
||||
|
||||
# Release Plan
|
||||
# Technical Release Plan
|
||||
|
||||
## First Stage: Web-app Minimum Viable Product front-end
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{\rtf1\ansi\deff3\adeflang1025
|
||||
{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f5\fnil\fprq2\fcharset0 Noto Sans CJK SC;}{\f6\fswiss\fprq0\fcharset128 Noto Sans Devanagari;}{\f7\fnil\fprq2\fcharset0 Noto Sans Devanagari;}}
|
||||
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}
|
||||
{\stylesheet{\s0\snext0\rtlch\af7\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af8\langfe2052 Normal;}
|
||||
{\s1\sbasedon16\snext17\rtlch\af7\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5 Heading 1;}
|
||||
{\*\cs15\snext15\rtlch\alang255 \ltrch\lang255\langfe255\loch\cf9\lang255\ul\ulc0\dbch\langfe255 Hyperlink;}
|
||||
{\s16\sbasedon0\snext17\rtlch\af7\afs28 \ltrch\hich\af4\loch\sb240\sa120\keepn\f4\fs28\dbch\af5 Heading;}
|
||||
{\s17\sbasedon0\snext17\loch\sl276\slmult1\sb0\sa140 Text Body;}
|
||||
{\s18\sbasedon17\snext18\rtlch\af6 \ltrch\loch\sl276\slmult1\sb0\sa140 List;}
|
||||
{\s19\sbasedon0\snext19\rtlch\af6\afs24\ai \ltrch\loch\sb120\sa120\noline\fs24\i Caption;}
|
||||
{\s20\sbasedon0\snext20\rtlch\af6\alang255 \ltrch\lang255\langfe255\loch\noline\lang255\dbch\langfe255 Index;}
|
||||
{\s21\sbasedon16\snext17\rtlch\af7\afs56\ab \ltrch\hich\af4\loch\qc\sb240\sa120\keepn\f4\fs56\b\dbch\af5 Title;}
|
||||
}{\*\listtable{\list\listtemplateid1
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}\listid1}
|
||||
}{\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}{\*\generator LibreOffice/7.3.2.2$Linux_X86_64 LibreOffice_project/30$Build-2}{\info{\creatim\yr2022\mo4\dy15\hr17\min25}{\revtim\yr2022\mo4\dy15\hr17\min31}{\printim\yr0\mo0\dy0\hr0\min0}}{\*\userprops}\deftab709
|
||||
\hyphauto1\viewscale130
|
||||
{\*\pgdsctbl
|
||||
{\pgdsc0\pgdscuse451\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Default Page Style;}}
|
||||
\formshade\paperh15840\paperw12240\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\pgndec\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc
|
||||
{\*\ftnsep\chftnsep}\pgndec\pard\plain \s21\rtlch\af7\afs56\ab \ltrch\hich\af4\loch\qc\sb240\sa120\keepn\f4\fs56\b\dbch\af5\loch\sb240\sa120\ltrpar{\loch
|
||||
Sprint 5 Retrospective}
|
||||
\par \pard\plain \s1\rtlch\af7\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5\loch{\listtext\pard\plain \tab}\ls1 \li0\ri0\lin0\rin0\fi0\ql\ltrpar{\loch
|
||||
What was done this Sprint}
|
||||
\par \pard\plain \s17\loch\sl276\slmult1\sb0\sa140\loch\ql\ltrpar{\loch
|
||||
The connection between the back-end and the front-end was set up (with some bug-fixes along the way). This part wasn\u8217\'92t difficult because of the previously planned out back-end protocol made it easier to integrate the connection to the back-end in the front-end. There was an issue at first connecting to the back-end, but it was discovered that it was a browser \u8220\'93feature\u8221\'94 involving \u8220\'93CORS\u8221\'94 (I think) where basically the back-end and the front-end needed to be hosted by the same server (and port), otherwise the front-end would fail to connect to the back-end. This required setting up a testing environment that set this up properly, which also would reflect how the deployed set-up would work (front-end at \u8220\'93}{{\field{\*\fldinst HYPERLINK "https://asdm.seodisparate.com/game" }{\fldrslt {\loch\rtlch\alang255 \ltrch\lang255\langfe255\loch\cf9\lang255\ul\ulc0\dbch\langfe255\loch
|
||||
https://asdm.seodisparate.com/game}{}}}\loch
|
||||
\u8221\'94 and back-end at \u8220\'93}{{\field{\*\fldinst HYPERLINK "https://asdm.seodisparate.com/api" }{\fldrslt {\loch\rtlch\alang255 \ltrch\lang255\langfe255\loch\cf9\lang255\ul\ulc0\dbch\langfe255\loch
|
||||
https://asdm.seodisparate.com/api}{}}}\loch
|
||||
\u8221\'94).}
|
||||
\par \pard\plain \s1\rtlch\af7\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5\loch{\listtext\pard\plain \tab}\ls1 \li0\ri0\lin0\rin0\fi0\ql\ltrpar{\loch
|
||||
What\u8217\'92s Left}
|
||||
\par \pard\plain \s17\loch\sl276\slmult1\sb0\sa140\loch\ql\sb0\sa140\ltrpar{\loch
|
||||
Now that all the main features of the game have been implemented, what\u8217\'92s left is refinement and possible implementation of \u8220\'93nice-to-have\u8221\'94 features.}
|
||||
\par }
|
|
@ -0,0 +1,41 @@
|
|||
{\rtf1\ansi\deff3\adeflang1025
|
||||
{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f5\fnil\fprq2\fcharset0 Noto Sans CJK SC;}{\f6\fnil\fprq2\fcharset0 Noto Sans Devanagari;}{\f7\fswiss\fprq0\fcharset128 Noto Sans Devanagari;}}
|
||||
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}
|
||||
{\stylesheet{\s0\snext0\rtlch\af6\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af8\langfe2052 Normal;}
|
||||
{\s1\sbasedon15\snext16\rtlch\af6\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5 Heading 1;}
|
||||
{\s15\sbasedon0\snext16\rtlch\af6\afs28 \ltrch\hich\af4\loch\sb240\sa120\keepn\f4\fs28\dbch\af5 Heading;}
|
||||
{\s16\sbasedon0\snext16\loch\sl276\slmult1\sb0\sa140 Text Body;}
|
||||
{\s17\sbasedon16\snext17\rtlch\af7 \ltrch\loch\sl276\slmult1\sb0\sa140 List;}
|
||||
{\s18\sbasedon0\snext18\rtlch\af7\afs24\ai \ltrch\loch\sb120\sa120\noline\fs24\i Caption;}
|
||||
{\s19\sbasedon0\snext19\rtlch\af7\alang255 \ltrch\lang255\langfe255\loch\noline\lang255\dbch\langfe255 Index;}
|
||||
{\s20\sbasedon15\snext16\rtlch\af6\afs56\ab \ltrch\hich\af4\loch\qc\sb240\sa120\keepn\f4\fs56\b\dbch\af5 Title;}
|
||||
}{\*\listtable{\list\listtemplateid1
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}\listid1}
|
||||
}{\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}{\*\generator LibreOffice/7.3.2.2$Linux_X86_64 LibreOffice_project/30$Build-2}{\info{\creatim\yr2022\mo5\dy2\hr13\min45}{\revtim\yr2022\mo5\dy2\hr13\min53}{\printim\yr0\mo0\dy0\hr0\min0}}{\*\userprops}\deftab709
|
||||
\hyphauto1\viewscale160
|
||||
{\*\pgdsctbl
|
||||
{\pgdsc0\pgdscuse451\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Default Page Style;}}
|
||||
\formshade\paperh15840\paperw12240\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\pgndec\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc
|
||||
{\*\ftnsep\chftnsep}\pgndec\pard\plain \s20\rtlch\af6\afs56\ab \ltrch\hich\af4\loch\qc\sb240\sa120\keepn\f4\fs56\b\dbch\af5\loch\sb240\sa120\ltrpar{\loch
|
||||
Sprint 6 Retrospective}
|
||||
\par \pard\plain \s1\rtlch\af6\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5\loch{\listtext\pard\plain \tab}\ls1 \li0\ri0\lin0\rin0\fi0\ql\ltrpar{\loch
|
||||
What was completed}
|
||||
\par \pard\plain \s16\loch\sl276\slmult1\sb0\sa140\loch\ql\ltrpar{\loch
|
||||
By the end of Sprint 6, almost all User Stories have been marked as done, except for one \u8220\'93Exciter\u8221\'94 User Story (\u8220\'93Board Column Emotes\u8221\'94).}
|
||||
\par \pard\plain \s16\loch\sl276\slmult1\sb0\sa140\loch\ql\ltrpar{\loch
|
||||
This Sprint marked the following User Stories as DONE: \u8220\'93End Game Options\u8221\'94, \u8220\'93AI Implementation\u8221\'94 (fine tuning AI), \u8220\'93In-game Emotes\u8221\'94, and \u8220\'93Multiplayer Phrase Pairing\u8221\'94.}
|
||||
\par \pard\plain \s1\rtlch\af6\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5\loch{\listtext\pard\plain \tab}\ls1 \li0\ri0\lin0\rin0\fi0\ql\ltrpar{\loch
|
||||
Thoughts}
|
||||
\par \pard\plain \s16\loch\sl276\slmult1\sb0\sa140\loch\ql\ltrpar{\loch
|
||||
It took some work adding the additional functionality for \u8220\'93In-game Emotes\u8221\'94 and \u8220\'93Multiplayer Phrase Pairing\u8221\'94 such that both protocol and database specifications had to be updated (and their usage in the back-end/front-end). In the end, it was nice to get nearly all of the User Stories marked as done. Setting up a MVP (Minimum Viable Product) and building upon it proved to be a good strategy for building up this project. The Scrum process facilitated the organization and set-up for the programming work, and has proven its usefulness.}
|
||||
\par \pard\plain \s16\loch\sl276\slmult1\sb0\sa140\loch\ql\sb0\sa140\ltrpar{\loch
|
||||
As this final Sprint focused more on \u8220\'93refinement\u8221\'94, the Sprint resulted in a few new (add-on) features and some refactoring/fixes.}
|
||||
\par }
|
|
@ -10,9 +10,14 @@ IDs, and paired state), and games in progress.
|
|||
PRAGMA foreign_keys = ON;
|
||||
|
||||
// fields should be self explanatory for the players table
|
||||
|
||||
// "phrase" is used to connect players with identical "phrase" text to make it
|
||||
// easier to connect with the player one wants to play with
|
||||
|
||||
CREATE TABLE players (id INTEGER PRIMARY KEY NOT NULL,
|
||||
date_added TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
game_id INTEGER,
|
||||
phrase TEXT,
|
||||
FOREIGN KEY(game_id) REFERENCES games(id) ON DELETE CASCADE);
|
||||
|
||||
// "cyan_player" and "magenta_player" should correspond to an existing entry in
|
||||
|
@ -32,6 +37,14 @@ CREATE TABLE games (id INTEGER PRIMARY KEY NOT NULL,
|
|||
turn_time_start TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(cyan_player) REFERENCES players (id) ON DELETE SET NULL,
|
||||
FOREIGN KEY(magenta_player) REFERENCES players (id) ON DELETE SET NULL);
|
||||
|
||||
// "type" is one of the four possible emotes
|
||||
|
||||
CREATE TABLE emotes (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
date_added TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
receiver_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(receiver_id) REFERENCES players (id) ON DELETE CASCADE);
|
||||
```
|
||||
|
||||
"date" entries are used for garbage collection of the database. A predefined
|
|
@ -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
|
||||
|
||||
```
|
||||
|
@ -51,6 +60,16 @@ of the request, and the backend will respond with JSON.
|
|||
}
|
||||
```
|
||||
|
||||
6. Chat Emote Send:
|
||||
|
||||
```
|
||||
{
|
||||
"id": "id given by backend",
|
||||
"type": "send_emote",
|
||||
"emote": "smile", // or "frown", or "neutral", or "think"
|
||||
}
|
||||
```
|
||||
|
||||
## Responses
|
||||
|
||||
1. Request ID Response
|
||||
|
@ -130,7 +149,7 @@ then the back-end will respond with "too\_many\_players".
|
|||
// "opponent_disconnected", "internal_error"
|
||||
|
||||
// "board" may not be in the response if "unknown_id" is the status
|
||||
"board": "abcdefg..." // 56-char long string with mapping:
|
||||
"board": "abcdefg...",// 56-char long string with mapping:
|
||||
// a - empty
|
||||
// b - cyan
|
||||
// c - magenta
|
||||
|
@ -140,6 +159,21 @@ then the back-end will respond with "too\_many\_players".
|
|||
// g - magenta placed
|
||||
// h - cyan winning and placed piece
|
||||
// i - magenta winning and placed piece
|
||||
// optional "peer_emote" entry is message from opponent
|
||||
"peer_emote": "smile",// or "frown", or "neutral", or "think"
|
||||
|
||||
// should always be available when "board" is available
|
||||
"updated_time": "2022-04-30 12:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
6. Send Emote Request Response
|
||||
|
||||
```
|
||||
{
|
||||
"type": "send_emote",
|
||||
"status": "ok", // or "invalid_emote", "peer_disconnected",
|
||||
// "internal_error"
|
||||
}
|
||||
```
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -1,29 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<office:document xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:xforms="http://www.w3.org/2002/xforms" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
|
||||
<office:meta><meta:creation-date>2022-02-27T14:56:15.671560282</meta:creation-date><meta:generator>LibreOffice/7.3.1.3$Linux_X86_64 LibreOffice_project/30$Build-3</meta:generator><dc:date>2022-03-13T22:01:54.524481726</dc:date><meta:editing-duration>PT2H8M22S</meta:editing-duration><meta:editing-cycles>38</meta:editing-cycles><meta:document-statistic meta:table-count="1" meta:cell-count="298" meta:object-count="0"/></office:meta>
|
||||
<office:meta><meta:creation-date>2022-02-27T14:56:15.671560282</meta:creation-date><meta:generator>LibreOffice/7.3.2.2$Linux_X86_64 LibreOffice_project/30$Build-2</meta:generator><dc:date>2022-04-25T13:54:57.087569254</dc:date><meta:editing-duration>PT2H15M22S</meta:editing-duration><meta:editing-cycles>40</meta:editing-cycles><meta:document-statistic meta:table-count="1" meta:cell-count="316" meta:object-count="0"/></office:meta>
|
||||
<office:settings>
|
||||
<config:config-item-set config:name="ooo:view-settings">
|
||||
<config:config-item config:name="VisibleAreaTop" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="VisibleAreaLeft" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="VisibleAreaWidth" config:type="int">46383</config:config-item>
|
||||
<config:config-item config:name="VisibleAreaHeight" config:type="int">36844</config:config-item>
|
||||
<config:config-item config:name="VisibleAreaHeight" config:type="int">40502</config:config-item>
|
||||
<config:config-item-map-indexed config:name="Views">
|
||||
<config:config-item-map-entry>
|
||||
<config:config-item config:name="ViewId" config:type="string">view1</config:config-item>
|
||||
<config:config-item-map-named config:name="Tables">
|
||||
<config:config-item-map-entry config:name="User Stories">
|
||||
<config:config-item config:name="CursorPositionX" config:type="int">12</config:config-item>
|
||||
<config:config-item config:name="CursorPositionY" config:type="int">23</config:config-item>
|
||||
<config:config-item config:name="CursorPositionX" config:type="int">5</config:config-item>
|
||||
<config:config-item config:name="CursorPositionY" config:type="int">27</config:config-item>
|
||||
<config:config-item config:name="HorizontalSplitMode" config:type="short">2</config:config-item>
|
||||
<config:config-item config:name="VerticalSplitMode" config:type="short">2</config:config-item>
|
||||
<config:config-item config:name="HorizontalSplitPosition" config:type="int">1</config:config-item>
|
||||
<config:config-item config:name="VerticalSplitPosition" config:type="int">1</config:config-item>
|
||||
<config:config-item config:name="ActiveSplitRange" config:type="short">3</config:config-item>
|
||||
<config:config-item config:name="PositionLeft" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="PositionRight" config:type="int">5</config:config-item>
|
||||
<config:config-item config:name="PositionRight" config:type="int">1</config:config-item>
|
||||
<config:config-item config:name="PositionTop" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="PositionBottom" config:type="int">1</config:config-item>
|
||||
<config:config-item config:name="PositionBottom" config:type="int">18</config:config-item>
|
||||
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ZoomValue" config:type="int">100</config:config-item>
|
||||
<config:config-item config:name="PageViewZoomValue" config:type="int">60</config:config-item>
|
||||
|
@ -32,7 +32,7 @@
|
|||
</config:config-item-map-entry>
|
||||
</config:config-item-map-named>
|
||||
<config:config-item config:name="ActiveTable" config:type="string">User Stories</config:config-item>
|
||||
<config:config-item config:name="HorizontalScrollbarWidth" config:type="int">1203</config:config-item>
|
||||
<config:config-item config:name="HorizontalScrollbarWidth" config:type="int">2483</config:config-item>
|
||||
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ZoomValue" config:type="int">100</config:config-item>
|
||||
<config:config-item config:name="PageViewZoomValue" config:type="int">60</config:config-item>
|
||||
|
@ -357,7 +357,7 @@
|
|||
<text:p><text:sheet-name>???</text:sheet-name><text:s/>(<text:title>???</text:title>)</text:p>
|
||||
</style:region-left>
|
||||
<style:region-right>
|
||||
<text:p><text:date style:data-style-name="N2" text:date-value="2022-03-13">00/00/0000</text:date>, <text:time style:data-style-name="N2" text:time-value="21:56:11.588245896">00:00:00</text:time></text:p>
|
||||
<text:p><text:date style:data-style-name="N2" text:date-value="2022-04-25">00/00/0000</text:date>, <text:time style:data-style-name="N2" text:time-value="13:53:35.055125671">00:00:00</text:time></text:p>
|
||||
</style:region-right>
|
||||
</style:header>
|
||||
<style:header-left style:display="false"/>
|
||||
|
@ -442,17 +442,17 @@
|
|||
<table:table-cell table:style-name="ce45" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Mandatory</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$G3:.$G100])" office:value-type="float" office:value="800" calcext:value-type="float">
|
||||
<text:p>800</text:p>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$G3:.$G100])" office:value-type="float" office:value="845" calcext:value-type="float">
|
||||
<text:p>845</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$H3:.$H100])" office:value-type="float" office:value="174" calcext:value-type="float">
|
||||
<text:p>174</text:p>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$H3:.$H100])" office:value-type="float" office:value="182" calcext:value-type="float">
|
||||
<text:p>182</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$I3:.$I100])" office:value-type="float" office:value="148" calcext:value-type="float">
|
||||
<text:p>148</text:p>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$I3:.$I100])" office:value-type="float" office:value="151" calcext:value-type="float">
|
||||
<text:p>151</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$H2:.$I2])" office:value-type="float" office:value="322" calcext:value-type="float">
|
||||
<text:p>322</text:p>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$H2:.$I2])" office:value-type="float" office:value="333" calcext:value-type="float">
|
||||
<text:p>333</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:number-columns-repeated="2" table:style-name="ce51" office:value-type="percentage" office:value="1" calcext:value-type="percentage">
|
||||
<text:p>100.00%</text:p>
|
||||
|
@ -490,14 +490,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H3:.$I3])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J3]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J3]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G3]/[.$G$2]" office:value-type="percentage" office:value="0.0125" calcext:value-type="percentage">
|
||||
<text:p>1.25%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G3]/[.$G$2]" office:value-type="percentage" office:value="0.0118343195266272" calcext:value-type="percentage">
|
||||
<text:p>1.18%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K3]/[.$L3]" office:value-type="float" office:value="3.9751552795031" calcext:value-type="float">
|
||||
<text:p>3.9751552795031</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K3]/[.$L3]" office:value-type="float" office:value="4.06006006006006" calcext:value-type="float">
|
||||
<text:p>4.06006006006006</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -527,14 +527,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H4:.$I4])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J4]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J4]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G4]/[.$G$2]" office:value-type="percentage" office:value="0.05" calcext:value-type="percentage">
|
||||
<text:p>5.00%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G4]/[.$G$2]" office:value-type="percentage" office:value="0.0473372781065089" calcext:value-type="percentage">
|
||||
<text:p>4.73%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K4]/[.$L4]" office:value-type="float" office:value="0.993788819875776" calcext:value-type="float">
|
||||
<text:p>0.993788819875776</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K4]/[.$L4]" office:value-type="float" office:value="1.01501501501502" calcext:value-type="float">
|
||||
<text:p>1.01501501501502</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -567,14 +567,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H5:.$I5])" office:value-type="float" office:value="8" calcext:value-type="float">
|
||||
<text:p>8</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J5]/[.$J$2]" office:value-type="percentage" office:value="0.0248447204968944" calcext:value-type="percentage">
|
||||
<text:p>2.48%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J5]/[.$J$2]" office:value-type="percentage" office:value="0.024024024024024" calcext:value-type="percentage">
|
||||
<text:p>2.40%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G5]/[.$G$2]" office:value-type="percentage" office:value="0.04625" calcext:value-type="percentage">
|
||||
<text:p>4.63%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G5]/[.$G$2]" office:value-type="percentage" office:value="0.0437869822485207" calcext:value-type="percentage">
|
||||
<text:p>4.38%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K5]/[.$L5]" office:value-type="float" office:value="0.537183145878798" calcext:value-type="float">
|
||||
<text:p>0.537183145878798</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K5]/[.$L5]" office:value-type="float" office:value="0.548656764872981" calcext:value-type="float">
|
||||
<text:p>0.548656764872981</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -607,14 +607,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H6:.$I6])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J6]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J6]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G6]/[.$G$2]" office:value-type="percentage" office:value="0.01625" calcext:value-type="percentage">
|
||||
<text:p>1.63%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G6]/[.$G$2]" office:value-type="percentage" office:value="0.0153846153846154" calcext:value-type="percentage">
|
||||
<text:p>1.54%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K6]/[.$L6]" office:value-type="float" office:value="3.05781175346393" calcext:value-type="float">
|
||||
<text:p>3.05781175346393</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K6]/[.$L6]" office:value-type="float" office:value="3.12312312312312" calcext:value-type="float">
|
||||
<text:p>3.12312312312312</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -644,14 +644,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H7:.$I7])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J7]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J7]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G7]/[.$G$2]" office:value-type="percentage" office:value="0.0225" calcext:value-type="percentage">
|
||||
<text:p>2.25%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G7]/[.$G$2]" office:value-type="percentage" office:value="0.021301775147929" calcext:value-type="percentage">
|
||||
<text:p>2.13%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K7]/[.$L7]" office:value-type="float" office:value="2.20841959972395" calcext:value-type="float">
|
||||
<text:p>2.20841959972395</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K7]/[.$L7]" office:value-type="float" office:value="2.25558892225559" calcext:value-type="float">
|
||||
<text:p>2.25558892225559</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -686,14 +686,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H8:.$I8])" office:value-type="float" office:value="17" calcext:value-type="float">
|
||||
<text:p>17</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J8]/[.$J$2]" office:value-type="percentage" office:value="0.0527950310559006" calcext:value-type="percentage">
|
||||
<text:p>5.28%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J8]/[.$J$2]" office:value-type="percentage" office:value="0.0510510510510511" calcext:value-type="percentage">
|
||||
<text:p>5.11%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G8]/[.$G$2]" office:value-type="percentage" office:value="0.03125" calcext:value-type="percentage">
|
||||
<text:p>3.13%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G8]/[.$G$2]" office:value-type="percentage" office:value="0.029585798816568" calcext:value-type="percentage">
|
||||
<text:p>2.96%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K8]/[.$L8]" office:value-type="float" office:value="1.68944099378882" calcext:value-type="float">
|
||||
<text:p>1.68944099378882</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K8]/[.$L8]" office:value-type="float" office:value="1.72552552552553" calcext:value-type="float">
|
||||
<text:p>1.72552552552553</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -725,14 +725,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H9:.$I9])" office:value-type="float" office:value="18" calcext:value-type="float">
|
||||
<text:p>18</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J9]/[.$J$2]" office:value-type="percentage" office:value="0.0559006211180124" calcext:value-type="percentage">
|
||||
<text:p>5.59%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J9]/[.$J$2]" office:value-type="percentage" office:value="0.0540540540540541" calcext:value-type="percentage">
|
||||
<text:p>5.41%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G9]/[.$G$2]" office:value-type="percentage" office:value="0.02875" calcext:value-type="percentage">
|
||||
<text:p>2.88%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G9]/[.$G$2]" office:value-type="percentage" office:value="0.0272189349112426" calcext:value-type="percentage">
|
||||
<text:p>2.72%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K9]/[.$L9]" office:value-type="float" office:value="1.94436943019174" calcext:value-type="float">
|
||||
<text:p>1.94436943019174</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K9]/[.$L9]" office:value-type="float" office:value="1.98589894242068" calcext:value-type="float">
|
||||
<text:p>1.98589894242068</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -762,14 +762,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H10:.$I10])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J10]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J10]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G10]/[.$G$2]" office:value-type="percentage" office:value="0.025" calcext:value-type="percentage">
|
||||
<text:p>2.50%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G10]/[.$G$2]" office:value-type="percentage" office:value="0.0236686390532544" calcext:value-type="percentage">
|
||||
<text:p>2.37%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K10]/[.$L10]" office:value-type="float" office:value="1.98757763975155" calcext:value-type="float">
|
||||
<text:p>1.98757763975155</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K10]/[.$L10]" office:value-type="float" office:value="2.03003003003003" calcext:value-type="float">
|
||||
<text:p>2.03003003003003</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -802,14 +802,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H11:.$I11])" office:value-type="float" office:value="9" calcext:value-type="float">
|
||||
<text:p>9</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J11]/[.$J$2]" office:value-type="percentage" office:value="0.0279503105590062" calcext:value-type="percentage">
|
||||
<text:p>2.80%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J11]/[.$J$2]" office:value-type="percentage" office:value="0.027027027027027" calcext:value-type="percentage">
|
||||
<text:p>2.70%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G11]/[.$G$2]" office:value-type="percentage" office:value="0.01625" calcext:value-type="percentage">
|
||||
<text:p>1.63%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G11]/[.$G$2]" office:value-type="percentage" office:value="0.0153846153846154" calcext:value-type="percentage">
|
||||
<text:p>1.54%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K11]/[.$L11]" office:value-type="float" office:value="1.72001911132346" calcext:value-type="float">
|
||||
<text:p>1.72001911132346</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K11]/[.$L11]" office:value-type="float" office:value="1.75675675675676" calcext:value-type="float">
|
||||
<text:p>1.75675675675676</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -842,14 +842,14 @@
|
|||
<table:table-cell table:style-name="ce48" table:formula="of:=SUM([.$H12:.$I12])" office:value-type="float" office:value="17" calcext:value-type="float">
|
||||
<text:p>17</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J12]/[.$J$2]" office:value-type="percentage" office:value="0.0527950310559006" calcext:value-type="percentage">
|
||||
<text:p>5.28%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J12]/[.$J$2]" office:value-type="percentage" office:value="0.0510510510510511" calcext:value-type="percentage">
|
||||
<text:p>5.11%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G12]/[.$G$2]" office:value-type="percentage" office:value="0.03" calcext:value-type="percentage">
|
||||
<text:p>3.00%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G12]/[.$G$2]" office:value-type="percentage" office:value="0.0284023668639053" calcext:value-type="percentage">
|
||||
<text:p>2.84%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K12]/[.$L12]" office:value-type="float" office:value="1.75983436853002" calcext:value-type="float">
|
||||
<text:p>1.75983436853002</text:p>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K12]/[.$L12]" office:value-type="float" office:value="1.79742242242242" calcext:value-type="float">
|
||||
<text:p>1.79742242242242</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -884,14 +884,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H13:.$I13])" office:value-type="float" office:value="11" calcext:value-type="float">
|
||||
<text:p>11</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J13]/[.$J$2]" office:value-type="percentage" office:value="0.0341614906832298" calcext:value-type="percentage">
|
||||
<text:p>3.42%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J13]/[.$J$2]" office:value-type="percentage" office:value="0.033033033033033" calcext:value-type="percentage">
|
||||
<text:p>3.30%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G13]/[.$G$2]" office:value-type="percentage" office:value="0.0875" calcext:value-type="percentage">
|
||||
<text:p>8.75%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G13]/[.$G$2]" office:value-type="percentage" office:value="0.0828402366863905" calcext:value-type="percentage">
|
||||
<text:p>8.28%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K13]/[.$L13]" office:value-type="float" office:value="0.390417036379769" calcext:value-type="float">
|
||||
<text:p>0.390417036379769</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K13]/[.$L13]" office:value-type="float" office:value="0.398755898755899" calcext:value-type="float">
|
||||
<text:p>0.398755898755899</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -921,14 +921,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H14:.$I14])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J14]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J14]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G14]/[.$G$2]" office:value-type="percentage" office:value="0.0375" calcext:value-type="percentage">
|
||||
<text:p>3.75%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G14]/[.$G$2]" office:value-type="percentage" office:value="0.0355029585798817" calcext:value-type="percentage">
|
||||
<text:p>3.55%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K14]/[.$L14]" office:value-type="float" office:value="1.32505175983437" calcext:value-type="float">
|
||||
<text:p>1.32505175983437</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K14]/[.$L14]" office:value-type="float" office:value="1.35335335335335" calcext:value-type="float">
|
||||
<text:p>1.35335335335335</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -958,14 +958,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H15:.$I15])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J15]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J15]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G15]/[.$G$2]" office:value-type="percentage" office:value="0.08125" calcext:value-type="percentage">
|
||||
<text:p>8.13%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G15]/[.$G$2]" office:value-type="percentage" office:value="0.0769230769230769" calcext:value-type="percentage">
|
||||
<text:p>7.69%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K15]/[.$L15]" office:value-type="float" office:value="0.611562350692785" calcext:value-type="float">
|
||||
<text:p>0.611562350692785</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K15]/[.$L15]" office:value-type="float" office:value="0.624624624624625" calcext:value-type="float">
|
||||
<text:p>0.624624624624625</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1000,14 +1000,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H16:.$I16])" office:value-type="float" office:value="15" calcext:value-type="float">
|
||||
<text:p>15</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J16]/[.$J$2]" office:value-type="percentage" office:value="0.046583850931677" calcext:value-type="percentage">
|
||||
<text:p>4.66%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J16]/[.$J$2]" office:value-type="percentage" office:value="0.045045045045045" calcext:value-type="percentage">
|
||||
<text:p>4.50%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G16]/[.$G$2]" office:value-type="percentage" office:value="0.0875" calcext:value-type="percentage">
|
||||
<text:p>8.75%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G16]/[.$G$2]" office:value-type="percentage" office:value="0.0828402366863905" calcext:value-type="percentage">
|
||||
<text:p>8.28%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K16]/[.$L16]" office:value-type="float" office:value="0.532386867790595" calcext:value-type="float">
|
||||
<text:p>0.532386867790595</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K16]/[.$L16]" office:value-type="float" office:value="0.543758043758044" calcext:value-type="float">
|
||||
<text:p>0.543758043758044</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1040,14 +1040,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H17:.$I17])" office:value-type="float" office:value="13" calcext:value-type="float">
|
||||
<text:p>13</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J17]/[.$J$2]" office:value-type="percentage" office:value="0.0403726708074534" calcext:value-type="percentage">
|
||||
<text:p>4.04%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J17]/[.$J$2]" office:value-type="percentage" office:value="0.039039039039039" calcext:value-type="percentage">
|
||||
<text:p>3.90%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G17]/[.$G$2]" office:value-type="percentage" office:value="0.09625" calcext:value-type="percentage">
|
||||
<text:p>9.63%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G17]/[.$G$2]" office:value-type="percentage" office:value="0.0911242603550296" calcext:value-type="percentage">
|
||||
<text:p>9.11%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K17]/[.$L17]" office:value-type="float" office:value="0.419456320077438" calcext:value-type="float">
|
||||
<text:p>0.419456320077438</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K17]/[.$L17]" office:value-type="float" office:value="0.428415428415428" calcext:value-type="float">
|
||||
<text:p>0.428415428415428</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1077,14 +1077,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H18:.$I18])" office:value-type="float" office:value="18" calcext:value-type="float">
|
||||
<text:p>18</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J18]/[.$J$2]" office:value-type="percentage" office:value="0.0559006211180124" calcext:value-type="percentage">
|
||||
<text:p>5.59%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J18]/[.$J$2]" office:value-type="percentage" office:value="0.0540540540540541" calcext:value-type="percentage">
|
||||
<text:p>5.41%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G18]/[.$G$2]" office:value-type="percentage" office:value="0.03125" calcext:value-type="percentage">
|
||||
<text:p>3.13%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G18]/[.$G$2]" office:value-type="percentage" office:value="0.029585798816568" calcext:value-type="percentage">
|
||||
<text:p>2.96%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K18]/[.$L18]" office:value-type="float" office:value="1.7888198757764" calcext:value-type="float">
|
||||
<text:p>1.7888198757764</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K18]/[.$L18]" office:value-type="float" office:value="1.82702702702703" calcext:value-type="float">
|
||||
<text:p>1.82702702702703</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1117,14 +1117,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H19:.$I19])" office:value-type="float" office:value="17" calcext:value-type="float">
|
||||
<text:p>17</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J19]/[.$J$2]" office:value-type="percentage" office:value="0.0527950310559006" calcext:value-type="percentage">
|
||||
<text:p>5.28%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J19]/[.$J$2]" office:value-type="percentage" office:value="0.0510510510510511" calcext:value-type="percentage">
|
||||
<text:p>5.11%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G19]/[.$G$2]" office:value-type="percentage" office:value="0.025" calcext:value-type="percentage">
|
||||
<text:p>2.50%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G19]/[.$G$2]" office:value-type="percentage" office:value="0.0236686390532544" calcext:value-type="percentage">
|
||||
<text:p>2.37%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K19]/[.$L19]" office:value-type="float" office:value="2.11180124223602" calcext:value-type="float">
|
||||
<text:p>2.11180124223602</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K19]/[.$L19]" office:value-type="float" office:value="2.15690690690691" calcext:value-type="float">
|
||||
<text:p>2.15690690690691</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1154,14 +1154,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H20:.$I20])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J20]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J20]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G20]/[.$G$2]" office:value-type="percentage" office:value="0.025" calcext:value-type="percentage">
|
||||
<text:p>2.50%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G20]/[.$G$2]" office:value-type="percentage" office:value="0.0236686390532544" calcext:value-type="percentage">
|
||||
<text:p>2.37%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K20]/[.$L20]" office:value-type="float" office:value="1.98757763975155" calcext:value-type="float">
|
||||
<text:p>1.98757763975155</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K20]/[.$L20]" office:value-type="float" office:value="2.03003003003003" calcext:value-type="float">
|
||||
<text:p>2.03003003003003</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1191,14 +1191,14 @@
|
|||
<table:table-cell table:style-name="ce48" table:formula="of:=SUM([.$H21:.$I21])" office:value-type="float" office:value="18" calcext:value-type="float">
|
||||
<text:p>18</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J21]/[.$J$2]" office:value-type="percentage" office:value="0.0559006211180124" calcext:value-type="percentage">
|
||||
<text:p>5.59%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J21]/[.$J$2]" office:value-type="percentage" office:value="0.0540540540540541" calcext:value-type="percentage">
|
||||
<text:p>5.41%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G21]/[.$G$2]" office:value-type="percentage" office:value="0.06625" calcext:value-type="percentage">
|
||||
<text:p>6.63%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G21]/[.$G$2]" office:value-type="percentage" office:value="0.0627218934911243" calcext:value-type="percentage">
|
||||
<text:p>6.27%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K21]/[.$L21]" office:value-type="float" office:value="0.843782960271886" calcext:value-type="float">
|
||||
<text:p>0.843782960271886</text:p>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K21]/[.$L21]" office:value-type="float" office:value="0.861805201427843" calcext:value-type="float">
|
||||
<text:p>0.861805201427843</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1231,14 +1231,14 @@
|
|||
<table:table-cell table:style-name="ce48" table:formula="of:=SUM([.$H22:.$I22])" office:value-type="float" office:value="15" calcext:value-type="float">
|
||||
<text:p>15</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J22]/[.$J$2]" office:value-type="percentage" office:value="0.046583850931677" calcext:value-type="percentage">
|
||||
<text:p>4.66%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J22]/[.$J$2]" office:value-type="percentage" office:value="0.045045045045045" calcext:value-type="percentage">
|
||||
<text:p>4.50%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G22]/[.$G$2]" office:value-type="percentage" office:value="0.06" calcext:value-type="percentage">
|
||||
<text:p>6.00%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G22]/[.$G$2]" office:value-type="percentage" office:value="0.0568047337278107" calcext:value-type="percentage">
|
||||
<text:p>5.68%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K22]/[.$L22]" office:value-type="float" office:value="0.77639751552795" calcext:value-type="float">
|
||||
<text:p>0.77639751552795</text:p>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K22]/[.$L22]" office:value-type="float" office:value="0.792980480480481" calcext:value-type="float">
|
||||
<text:p>0.792980480480481</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1271,14 +1271,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H23:.$I23])" office:value-type="float" office:value="9" calcext:value-type="float">
|
||||
<text:p>9</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J23]/[.$J$2]" office:value-type="percentage" office:value="0.0279503105590062" calcext:value-type="percentage">
|
||||
<text:p>2.80%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J23]/[.$J$2]" office:value-type="percentage" office:value="0.027027027027027" calcext:value-type="percentage">
|
||||
<text:p>2.70%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G23]/[.$G$2]" office:value-type="percentage" office:value="0.05875" calcext:value-type="percentage">
|
||||
<text:p>5.88%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G23]/[.$G$2]" office:value-type="percentage" office:value="0.0556213017751479" calcext:value-type="percentage">
|
||||
<text:p>5.56%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K23]/[.$L23]" office:value-type="float" office:value="0.475749966961808" calcext:value-type="float">
|
||||
<text:p>0.475749966961808</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K23]/[.$L23]" office:value-type="float" office:value="0.485911443358252" calcext:value-type="float">
|
||||
<text:p>0.485911443358252</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1311,14 +1311,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H24:.$I24])" office:value-type="float" office:value="9" calcext:value-type="float">
|
||||
<text:p>9</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J24]/[.$J$2]" office:value-type="percentage" office:value="0.0279503105590062" calcext:value-type="percentage">
|
||||
<text:p>2.80%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J24]/[.$J$2]" office:value-type="percentage" office:value="0.027027027027027" calcext:value-type="percentage">
|
||||
<text:p>2.70%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G24]/[.$G$2]" office:value-type="percentage" office:value="0.065" calcext:value-type="percentage">
|
||||
<text:p>6.50%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G24]/[.$G$2]" office:value-type="percentage" office:value="0.0615384615384615" calcext:value-type="percentage">
|
||||
<text:p>6.15%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K24]/[.$L24]" office:value-type="float" office:value="0.430004777830865" calcext:value-type="float">
|
||||
<text:p>0.430004777830865</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K24]/[.$L24]" office:value-type="float" office:value="0.439189189189189" calcext:value-type="float">
|
||||
<text:p>0.439189189189189</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1343,7 +1343,70 @@
|
|||
<table:table-cell table:style-name="ce21" table:number-columns-repeated="2"/>
|
||||
<table:table-cell table:style-name="ce13" table:number-columns-repeated="1012"/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="ro1" table:number-rows-repeated="1048550">
|
||||
<table:table-row table:style-name="ro6">
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Multiplayer Phrase Pairing</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Networking</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>As a player, I would like to be able to pair with another Player I know, by setting up a common phrase that will match me with the Player I know, so that I can play a game with the Player I know.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Set up a “room code” system where Players can specify a “phrase” or “code word” that will only pair them with other Players that use the same “phrase”.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>The back-end should be able to pair Players with identical “phrases” or “code words”. The front-end should allow the Player to set a “phrase” or “code word”.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Exciter</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="float" office:value="45" calcext:value-type="float">
|
||||
<text:p>45</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="float" office:value="8" calcext:value-type="float">
|
||||
<text:p>8</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="float" office:value="3" calcext:value-type="float">
|
||||
<text:p>3</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="float" office:value="11" calcext:value-type="float">
|
||||
<text:p>11</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J26]/[.$J$2]" office:value-type="percentage" office:value="0.033033033033033" calcext:value-type="percentage">
|
||||
<text:p>3.30%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G26]/[.$G$2]" office:value-type="percentage" office:value="0.0532544378698225" calcext:value-type="percentage">
|
||||
<text:p>5.33%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K26]/[.$L26]" office:value-type="float" office:value="0.620286953620287" calcext:value-type="float">
|
||||
<text:p>0.620286953620287</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="ro4">
|
||||
<table:table-cell table:style-name="ce6" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Refactoring and Code Improvement</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce6" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Constraint</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce6" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>As I developer, I expect the code to be improved when improvements can be made to improve code quality and prevent technical debt.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce6"/>
|
||||
<table:table-cell table:style-name="ce6" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>The front-end and back-end code should be reasonably maintained, with fixes and/or refactorings where possible.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce13" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Mandatory</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce13" table:number-columns-repeated="4"/>
|
||||
<table:table-cell table:style-name="ce21" table:number-columns-repeated="2"/>
|
||||
<table:table-cell table:style-name="ce13" table:number-columns-repeated="1012"/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="ro1" table:number-rows-repeated="1048548">
|
||||
<table:table-cell table:number-columns-repeated="1024"/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="ro1">
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue