Compare commits

...

25 commits

Author SHA1 Message Date
Stephen Seo 2d7c8c37e5 Maintenance update
`cargo update` to update Cargo.lock for backend and frontend Rust
projects.
2024-04-03 11:04:52 +09:00
Stephen Seo 090b8bbd30 Fix crash bug 2022-05-06 11:32:20 +09:00
Stephen Seo 59b2bc34fb back-end: Do refactorings 2022-05-05 12:35:30 +09:00
Stephen Seo 1872c4877e Fix bug: board not updated on win/lose/draw 2022-05-04 14:55:01 +09:00
Stephen Seo b5529cb542 Convert User Stories to MSExcel compatible format 2022-05-04 12:41:04 +09:00
Stephen Seo f4f3ad7a5b Refactorings, fix bug where board doesn't update
Also silence warnings related to unused code since the front-end and
back-end share some code.
2022-05-03 13:18:07 +09:00
Stephen Seo 4331a20daa Update README.md 2022-05-02 21:12:18 +09:00
Stephen Seo 047549ecb5 Convert spreadsheets to MS Excel compatible format 2022-05-02 13:56:48 +09:00
Stephen Seo bc6c234314 Add Sprint 6 Retrospective 2022-05-02 13:53:50 +09:00
Stephen Seo 1f27defe11 Update Sprint 6 Backlog, Product Backlog 2022-05-02 13:45:32 +09:00
Stephen Seo 3eb663c305 Update Sprint 6 backlog 2022-04-30 17:48:58 +09:00
Stephen Seo b2ea79a7f7 Impl conditionally update front-end board
When the front-end polls the back-end for the game-state, the back-end includes
a "date_updated" String in the JSON. If the String is the same as in the
front-end, then no updates are needed, but if they are not the same, then the
front-end will update the board. Because the front-end polls the back-end's
board state approximately every second, this should make the front-end more
efficient.
2022-04-30 16:44:48 +09:00
Stephen Seo b4eaba09c5 Refactorings/Fixes related to emoting 2022-04-29 19:21:59 +09:00
Stephen Seo d88e8ef9f3 Update Product Backlog, Sprint 6 Backlog 2022-04-29 18:34:05 +09:00
Stephen Seo 105cd880f2 Impl sending/receiving emotes 2022-04-29 18:30:41 +09:00
Stephen Seo 36dd43bb70 Fixes related to new send emote functionality 2022-04-29 17:24:42 +09:00
Stephen Seo 5381578b08 Update specs, impl back-end support for send emote 2022-04-29 17:16:32 +09:00
Stephen Seo f498f2c475 Update backend_protocol for emote send/recv 2022-04-29 15:53:36 +09:00
Stephen Seo 8eb30fc5d5 Update Product Backlog 2022-04-29 15:35:26 +09:00
Stephen Seo 6ef8667382 Update Sprint 6 backlog 2022-04-29 15:28:08 +09:00
Stephen Seo a4bf4cbd25 Change fn string_from_board to accept board ref 2022-04-29 15:21:22 +09:00
Stephen Seo f799bae530 front-end: Minor refactorings fixes 2022-04-29 12:12:01 +09:00
Stephen Seo b158e7347e front-end: Minor refactorings 2022-04-29 11:37:15 +09:00
Stephen Seo e6152331b0 front-end: minor refactoring 2022-04-29 11:23:03 +09:00
Stephen Seo e77d25996d front-end: fix repeated disconnects on close
When the front-end connects to the back-end, it creates a callback that
sends a disconnect message with the received ID on "pagehide" and
"beforeunload" events. The previous implementation did not "undo" these
callbacks when the game was reset and a new ID was received. This fix
prevents the front-end from resending disconnect messages with
previously received IDs on browser window/tab close.
2022-04-29 11:08:54 +09:00
20 changed files with 3131 additions and 746 deletions

View file

@ -39,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)

828
back_end/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,9 @@ use crate::constants::{
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, Turn};
use crate::state::{
board_from_string, new_string_board, string_from_board, BoardState, EmoteEnum, Turn,
};
use std::collections::HashMap;
use std::sync::mpsc::{Receiver, RecvTimeoutError, SyncSender};
@ -29,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>;
@ -140,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)]
@ -241,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
@ -268,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
@ -319,6 +352,25 @@ 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"))
@ -603,63 +655,120 @@ impl DBHandler {
_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();
}
}
// 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");
}
@ -805,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;
@ -857,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() {
@ -888,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,
@ -937,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");
}
@ -951,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(
@ -1004,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 {
@ -1065,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(
@ -1099,11 +1268,18 @@ pub fn start_db_handler_thread(
}
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(None) {
if let Err(e) = handler.cleanup_stale_games(conn.as_ref()) {
println!("{}", e);
}
if let Err(e) = handler.cleanup_stale_players(None) {
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);
}
}

View file

@ -9,6 +9,7 @@
use crate::{
constants::BACKEND_PHRASE_MAX_LENGTH,
db_handler::{CheckPairingType, DBHandlerRequest, GetIDSenderType},
state::EmoteEnum,
};
use std::{
@ -32,6 +33,7 @@ pub fn handle_json(
"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 {
@ -264,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\":\"{}\"}}",
@ -280,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())
}
}

182
front_end/Cargo.lock generated
View file

@ -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",
]

View file

@ -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"] }

View file

@ -48,6 +48,33 @@
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;

View file

@ -15,6 +15,7 @@ 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,

View file

@ -9,25 +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";

View file

@ -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 {

View file

@ -17,6 +17,7 @@ use std::collections::hash_set::HashSet;
use std::fmt::Display;
use std::rc::Rc;
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GameState {
MainMenu,
@ -32,6 +33,7 @@ pub enum GameState {
}
impl GameState {
#[allow(dead_code)]
pub fn is_networked_multiplayer(&self) -> bool {
matches!(
*self,
@ -44,6 +46,7 @@ impl GameState {
)
}
#[allow(dead_code)]
pub fn set_networked_paired(&mut self) {
if let GameState::NetworkedMultiplayer {
ref mut paired,
@ -56,6 +59,7 @@ impl GameState {
}
}
#[allow(dead_code)]
pub fn get_networked_current_side(&self) -> Option<Turn> {
if let GameState::NetworkedMultiplayer {
paired: _,
@ -70,6 +74,7 @@ impl GameState {
}
}
#[allow(dead_code)]
pub fn set_networked_current_side(&mut self, side: Option<Turn>) {
if let GameState::NetworkedMultiplayer {
paired: _,
@ -82,6 +87,7 @@ impl GameState {
}
}
#[allow(dead_code)]
pub fn get_current_turn(&self) -> Turn {
if let GameState::SinglePlayer(turn, _) = *self {
turn
@ -98,20 +104,7 @@ impl GameState {
}
}
pub fn get_network_current_side(&self) -> Option<Turn> {
if let GameState::NetworkedMultiplayer {
paired: _,
current_side,
current_turn: _,
phrase: _,
} = *self
{
current_side
} else {
None
}
}
#[allow(dead_code)]
pub fn set_networked_current_turn(&mut self, turn: Turn) {
if let GameState::NetworkedMultiplayer {
paired: _,
@ -124,6 +117,7 @@ impl GameState {
}
}
#[allow(dead_code)]
pub fn get_phrase(&self) -> Option<String> {
if let GameState::NetworkedMultiplayer {
paired: _,
@ -137,6 +131,15 @@ impl GameState {
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 {
@ -160,6 +163,7 @@ impl From<MainMenuMessage> for GameState {
}
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum BoardState {
Empty,
@ -197,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,
@ -208,6 +214,7 @@ impl BoardState {
}
}
#[allow(dead_code)]
pub fn into_win(self) -> Self {
match self {
BoardState::Empty => BoardState::Empty,
@ -216,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,
@ -225,6 +233,7 @@ impl BoardState {
}
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Turn {
CyanPlayer,
@ -250,6 +259,7 @@ impl From<BoardState> for Turn {
}
impl Turn {
#[allow(dead_code)]
pub fn get_color(&self) -> &str {
match *self {
Turn::CyanPlayer => "cyan",
@ -267,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())),
@ -328,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() {
@ -339,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)),
@ -400,6 +413,7 @@ pub fn new_placed() -> PlacedType {
]
}
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq)]
pub struct SharedState {
pub board: BoardType,
@ -422,6 +436,7 @@ impl Default for SharedState {
// This enum moved from yew_components module so that this module would have no
// dependencies on the yew_components module
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MainMenuMessage {
SinglePlayer(Turn, AIDifficulty),
@ -429,6 +444,7 @@ pub enum MainMenuMessage {
NetworkedMultiplayer(Option<String>),
}
#[allow(dead_code)]
pub fn new_string_board() -> String {
let mut board = String::with_capacity(56);
for _i in 0..56 {
@ -437,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() {
@ -456,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) => {
@ -543,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,
@ -551,6 +570,7 @@ pub struct PairingRequestResponse {
pub color: Option<String>,
}
#[allow(dead_code)]
#[derive(Debug, Serialize, Deserialize)]
pub struct PairingStatusResponse {
pub r#type: String,
@ -558,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,
@ -572,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,
@ -585,6 +617,7 @@ pub enum NetworkedGameState {
UnknownID,
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PlacedEnum {
Accepted,
@ -593,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::*;
@ -629,7 +718,7 @@ mod tests {
board[54].set(BoardState::Cyan);
board[55].set(BoardState::Magenta);
let (board_string, state_opt) = string_from_board(board.clone(), 51);
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');
@ -649,7 +738,7 @@ mod tests {
board[54].set(BoardState::Magenta);
board[55].set(BoardState::Cyan);
let (board_string, state_opt) = string_from_board(board.clone(), 51);
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');

View file

@ -18,17 +18,17 @@ use crate::html_helper::{
};
use crate::random_helper::get_seeded_random;
use crate::state::{
board_from_string, BoardState, GameState, GameStateResponse, MainMenuMessage,
board_from_string, BoardState, EmoteEnum, GameState, GameStateResponse, MainMenuMessage,
NetworkedGameState, PairingRequestResponse, PairingStatusResponse, PlaceTokenResponse,
PlacedEnum, SharedState, Turn,
PlacedEnum, SendEmoteRequestResponse, SharedState, Turn,
};
use std::cell::Cell;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
use js_sys::{Function, Promise};
use web_sys::{AddEventListenerOptions, Document, HtmlInputElement};
use web_sys::{AddEventListenerOptions, Document, EventListenerOptions, HtmlInputElement};
use wasm_bindgen_futures::JsFuture;
@ -227,6 +227,76 @@ impl Component for ResetButton {
}
}
struct EmoteButton {}
enum EmoteButtonMsg {
Pressed,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Properties)]
struct EmoteButtonProperties {
emote: EmoteEnum,
}
impl Component for EmoteButton {
type Message = EmoteButtonMsg;
type Properties = EmoteButtonProperties;
fn create(_ctx: &Context<Self>) -> Self {
Self {}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let onclick = ctx.link().callback(|_| EmoteButtonMsg::Pressed);
let emote_id = format!("emote_{}", ctx.props().emote);
html! {
<button class={"emote"} id={emote_id} onclick={onclick}>
{ctx.props().emote.get_unicode()}
</button>
}
}
fn update(&mut self, ctx: &Context<Self>, _msg: Self::Message) -> bool {
let (shared, _) = ctx
.link()
.context::<SharedState>(Callback::noop())
.expect("state to be set");
let (_window, document) =
get_window_document().expect("Should be able to get Window and Document");
if shared.game_state.borrow().is_networked_multiplayer() {
ctx.link()
.get_parent()
.expect("Wrapper should be parent of EmoteButton")
.clone()
.downcast::<Wrapper>()
.send_message(WrapperMsg::SendEmote(ctx.props().emote));
} else if let Some(side) = shared.game_state.borrow().get_singleplayer_current_side() {
append_to_info_text(
&document,
"info_text0",
&format!(
"<b class=\"{}\">{} emoted with <b class=\"emote\">{}</b></b>",
side.get_color(),
side,
ctx.props().emote.get_unicode()
),
INFO_TEXT_MAX_ITEMS,
)
.ok();
} else {
append_to_info_text(
&document,
"info_text0",
"<b>Cannot use emotes at this time</b>",
INFO_TEXT_MAX_ITEMS,
)
.ok();
}
true
}
}
pub struct Slot {}
pub enum SlotMessage {
@ -321,6 +391,8 @@ pub struct Wrapper {
player_id: Option<u32>,
place_request: Option<u8>,
do_backend_tick: bool,
cleanup_id_callback: Rc<RefCell<Option<Function>>>,
board_updated_time: Option<String>,
}
impl Wrapper {
@ -484,7 +556,12 @@ impl Wrapper {
_ => NetworkedGameState::InternalError,
};
WrapperMsg::BackendResponse(BREnum::GotStatus(networked_game_state, response.board))
WrapperMsg::BackendResponse(BREnum::GotStatus {
networked_game_state,
board_string: response.board,
received_emote: response.peer_emote,
updated_time: response.updated_time,
})
});
}
@ -538,9 +615,9 @@ impl Wrapper {
&mut self,
shared: &SharedState,
document: &Document,
board_string: String,
board_string: &str,
) {
let board = board_from_string(board_string.clone());
let board = board_from_string(board_string);
for (idx, slot) in board.iter().enumerate() {
let was_open =
element_has_class(document, &format!("slot{}", idx), "open").unwrap_or(false);
@ -602,6 +679,47 @@ impl Wrapper {
shared.board[idx].set(slot.get());
}
}
fn cleanup_disconnect_callbacks(&mut self) {
// if previously set disconnect callback is set, unset it
if let Some(callback) = self.cleanup_id_callback.take() {
let window = web_sys::window().expect("Should be able to get window");
let mut options = EventListenerOptions::new();
options.capture(true);
if window
.remove_event_listener_with_callback_and_event_listener_options(
"pagehide", &callback, &options,
)
.is_err()
{
log::warn!("Failed to remove event listener for disconnect ID on pagehide");
}
if window
.remove_event_listener_with_callback_and_event_listener_options(
"beforeunload",
&callback,
&options,
)
.is_err()
{
log::warn!("Failed to remove event listener for disconnect ID on beforeunload");
}
}
}
fn send_disconnect(&mut self) {
if let Some(id) = self.player_id.take() {
let function = Function::new_no_args(&format!(
"
let xhr = new XMLHttpRequest();
xhr.open('POST', '{}');
xhr.send('{{\"type\": \"disconnect\", \"id\": {}}}');
",
BACKEND_URL, id
));
function.call0(&function).ok();
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -609,7 +727,14 @@ pub enum BREnum {
Error(String),
GotID(u32, Option<Turn>),
GotPairing(Option<Turn>),
GotStatus(NetworkedGameState, Option<String>),
/// Second opt string is board_str, third opt string is received emote,
/// fourth opt string is updated_time
GotStatus {
networked_game_state: NetworkedGameState,
board_string: Option<String>,
received_emote: Option<String>,
updated_time: Option<String>,
},
GotPlaced(PlacedEnum, String),
}
@ -625,6 +750,8 @@ pub enum WrapperMsg {
BackendRequest { place: u8 },
BackendResponse(BREnum),
Reset,
SendEmote(EmoteEnum),
SentEmote(EmoteEnum),
}
impl WrapperMsg {
@ -642,6 +769,8 @@ impl Component for Wrapper {
player_id: None,
place_request: None,
do_backend_tick: true,
cleanup_id_callback: Rc::new(RefCell::new(None)),
board_updated_time: None,
}
}
@ -654,6 +783,12 @@ impl Component for Wrapper {
<div class="wrapper">
<MainMenu />
<ResetButton />
<div class="emote_wrapper">
<EmoteButton emote={EmoteEnum::Smile} />
<EmoteButton emote={EmoteEnum::Neutral} />
<EmoteButton emote={EmoteEnum::Frown} />
<EmoteButton emote={EmoteEnum::Think} />
</div>
<Slot idx=0 state={shared.board[0].clone()} placed={shared.placed[0].clone()} />
<Slot idx=1 state={shared.board[1].clone()} placed={shared.placed[1].clone()} />
<Slot idx=2 state={shared.board[2].clone()} placed={shared.placed[2].clone()} />
@ -1235,17 +1370,7 @@ impl Component for Wrapper {
WrapperMsg::StartBackendTickImpl => {
// If previous id is still stored, request disconnect so that a
// new id can be received
if let Some(id) = self.player_id.take() {
let function = Function::new_no_args(&format!(
"
let xhr = new XMLHttpRequest();
xhr.open('POST', '{}');
xhr.send('{{\"type\": \"disconnect\", \"id\": {}}}');
",
BACKEND_URL, id
));
function.call0(&function).ok();
}
self.send_disconnect();
self.do_backend_tick = true;
ctx.link().send_message(WrapperMsg::BackendTick);
}
@ -1253,18 +1378,8 @@ impl Component for Wrapper {
let is_networked_multiplayer =
shared.game_state.borrow().is_networked_multiplayer();
if !self.do_backend_tick || !is_networked_multiplayer {
// disconnect id if backend tick is to be stopped
if let Some(id) = self.player_id.take() {
let function = Function::new_no_args(&format!(
"
let xhr = new XMLHttpRequest();
xhr.open('POST', '{}');
xhr.send('{{\"type\": \"disconnect\", \"id\": {}}}');
",
BACKEND_URL, id
));
function.call0(&function).ok();
}
self.send_disconnect();
self.cleanup_disconnect_callbacks();
return false;
}
@ -1307,11 +1422,16 @@ impl Component for Wrapper {
log::warn!("{}", string);
}
BREnum::GotID(id, turn_opt) => {
self.cleanup_disconnect_callbacks();
// set reset and disconnect on page "unload"
let player_id = id;
let listener_function: Rc<RefCell<Option<Function>>> =
self.cleanup_id_callback.clone();
ctx.link().send_future(async move {
let listener_function = listener_function;
let promise =
Promise::new(&mut |resolve: js_sys::Function, _reject| {
Promise::new(&mut move |resolve: js_sys::Function, _reject| {
let window =
web_sys::window().expect("Should be able to get window");
let outer_function = Function::new_with_args(
@ -1328,17 +1448,18 @@ impl Component for Wrapper {
);
let binded_func =
outer_function.bind1(&outer_function, &resolve);
listener_function.replace(Some(binded_func));
window
.add_event_listener_with_callback_and_add_event_listener_options(
"pagehide",
&binded_func,
listener_function.borrow().as_ref().unwrap(),
AddEventListenerOptions::new().capture(true).once(true)
)
.expect("Should be able to set \"pagehide\" callback");
window
.add_event_listener_with_callback_and_add_event_listener_options(
"beforeunload",
&binded_func,
listener_function.borrow().as_ref().unwrap(),
AddEventListenerOptions::new().capture(true).once(true)
)
.expect("Should be able to set \"beforeunload\" callback");
@ -1414,15 +1535,63 @@ impl Component for Wrapper {
.ok();
}
}
BREnum::GotStatus(networked_game_state, board_opt) => {
if let Some(board_string) = board_opt {
self.update_board_from_string(&shared, &document, board_string);
BREnum::GotStatus {
networked_game_state,
board_string,
received_emote,
updated_time,
} => {
let current_side = shared.game_state.borrow().get_networked_current_side();
let current_side = if let Some(side) = current_side {
side
} else {
return true;
};
if let Some(emote_string) = received_emote {
if let Ok(emote_enum) = EmoteEnum::try_from(emote_string.as_str()) {
append_to_info_text(
&document,
"info_text0",
&format!(
"<b class=\"{}\">{} sent <b class=\"emote\">{}</b></b>",
current_side.get_opposite().get_color(),
current_side.get_opposite(),
emote_enum.get_unicode()
),
INFO_TEXT_MAX_ITEMS,
)
.ok();
} else {
append_to_info_text(
&document,
"info_text0",
&format!(
"<b class=\"{}\">{} sent invalid emote</b>",
current_side.get_color(),
current_side.get_opposite()
),
INFO_TEXT_MAX_ITEMS,
)
.ok();
}
}
// only update board string if updated_time is different
if self.board_updated_time != updated_time {
if let Some(updated_time) = updated_time {
self.board_updated_time.replace(updated_time);
if let Some(board_string) = board_string.as_ref() {
self.update_board_from_string(&shared, &document, board_string);
}
}
}
let mut current_game_state: GameState = shared.game_state.borrow().clone();
match networked_game_state {
NetworkedGameState::CyanTurn => {
if current_game_state.get_current_turn() != Turn::CyanPlayer {
self.board_updated_time.take();
current_game_state.set_networked_current_turn(Turn::CyanPlayer);
shared.game_state.replace(current_game_state.clone());
append_to_info_text(
@ -1431,7 +1600,7 @@ impl Component for Wrapper {
&format!(
"<b class=\"cyan\">It is CyanPlayer's ({}) Turn</b>",
if current_game_state
.get_network_current_side()
.get_networked_current_side()
.unwrap_or(Turn::CyanPlayer)
== Turn::CyanPlayer
{
@ -1447,6 +1616,7 @@ impl Component for Wrapper {
}
NetworkedGameState::MagentaTurn => {
if current_game_state.get_current_turn() != Turn::MagentaPlayer {
self.board_updated_time.take();
current_game_state
.set_networked_current_turn(Turn::MagentaPlayer);
shared.game_state.replace(current_game_state.clone());
@ -1455,7 +1625,7 @@ impl Component for Wrapper {
"info_text1",
&format!(
"<b class=\"magenta\">It is MagentaPlayer's ({}) Turn</b>",
if current_game_state.get_network_current_side().unwrap_or(Turn::CyanPlayer) == Turn::MagentaPlayer
if current_game_state.get_networked_current_side().unwrap_or(Turn::CyanPlayer) == Turn::MagentaPlayer
{
"your"
} else {
@ -1467,6 +1637,9 @@ impl Component for Wrapper {
}
}
NetworkedGameState::CyanWon => {
if let Some(board_string) = board_string.as_ref() {
self.update_board_from_string(&shared, &document, board_string);
}
append_to_info_text(
&document,
"info_text1",
@ -1480,6 +1653,9 @@ impl Component for Wrapper {
self.do_backend_tick = false;
}
NetworkedGameState::MagentaWon => {
if let Some(board_string) = board_string.as_ref() {
self.update_board_from_string(&shared, &document, board_string);
}
append_to_info_text(
&document,
"info_text1",
@ -1493,6 +1669,9 @@ impl Component for Wrapper {
self.do_backend_tick = false;
}
NetworkedGameState::Draw => {
if let Some(board_string) = board_string.as_ref() {
self.update_board_from_string(&shared, &document, board_string);
}
append_to_info_text(
&document,
"info_text1",
@ -1555,7 +1734,7 @@ impl Component for Wrapper {
}
}
BREnum::GotPlaced(placed_status, board_string) => {
self.update_board_from_string(&shared, &document, board_string);
self.update_board_from_string(&shared, &document, &board_string);
match placed_status {
PlacedEnum::Accepted => {
@ -1679,17 +1858,7 @@ impl Component for Wrapper {
element_remove_class(&document, &format!("slot{}", idx), "magenta").ok();
element_append_class(&document, &format!("slot{}", idx), "open").ok();
}
if let Some(id) = self.player_id.take() {
let function = Function::new_no_args(&format!(
"
let xhr = new XMLHttpRequest();
xhr.open('POST', '{}');
xhr.send('{{\"type\": \"disconnect\", \"id\": {}}}');
",
BACKEND_URL, id
));
function.call0(&function).ok();
}
self.send_disconnect();
self.place_request = None;
element_remove_class(&document, "mainmenu", "hidden_menu").ok();
element_append_class(&document, "mainmenu", "menu").ok();
@ -1708,6 +1877,55 @@ impl Component for Wrapper {
)
.ok();
self.do_backend_tick = false;
self.cleanup_disconnect_callbacks();
}
WrapperMsg::SendEmote(emote) => {
if self.player_id.is_none() {
return false;
}
let emote = emote;
let player_id = self.player_id.unwrap();
ctx.link().send_future(async move {
let mut json_entries = HashMap::new();
json_entries.insert("id".into(), format!("{}", player_id));
json_entries.insert("type".into(), "send_emote".into());
json_entries.insert("emote".into(), emote.to_string());
let send_to_backend_result = send_to_backend(json_entries).await;
if let Err(e) = send_to_backend_result {
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
}
let request_result: Result<SendEmoteRequestResponse, _> =
serde_json::from_str(&send_to_backend_result.unwrap());
if let Err(e) = request_result {
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
}
let response = request_result.unwrap();
if response.status.as_str() == "ok" {
WrapperMsg::SentEmote(emote)
} else {
WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", response.status)))
}
});
}
WrapperMsg::SentEmote(emote) => {
let current_side_opt = shared.game_state.borrow().get_networked_current_side();
if let Some(current_side) = current_side_opt {
append_to_info_text(
&document,
"info_text0",
&format!(
"<b class=\"{}\">{} sent <b class=\"emote\">{}</b></b>",
current_side.get_color(),
current_side,
emote.get_unicode()
),
INFO_TEXT_MAX_ITEMS,
)
.ok();
}
}
} // match (msg)

View file

@ -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 }

View file

@ -37,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

View file

@ -60,6 +60,16 @@ the same "phrase".
}
```
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
@ -139,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
@ -149,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.

View file

@ -1,20 +1,20 @@
<?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-04-25T13:39:27.911997704</meta:creation-date><dc:date>2022-04-27T15:16:51.193988054</dc:date><meta:editing-duration>PT1H8M50S</meta:editing-duration><meta:editing-cycles>7</meta:editing-cycles><meta:generator>LibreOffice/7.3.2.2$Linux_X86_64 LibreOffice_project/30$Build-2</meta:generator><meta:document-statistic meta:table-count="1" meta:cell-count="108" meta:object-count="0"/></office:meta>
<office:meta><meta:creation-date>2022-04-25T13:39:27.911997704</meta:creation-date><dc:date>2022-05-02T13:06:13.913729005</dc:date><meta:editing-duration>PT1H12M34S</meta:editing-duration><meta:editing-cycles>11</meta:editing-cycles><meta:generator>LibreOffice/7.3.2.2$Linux_X86_64 LibreOffice_project/30$Build-2</meta:generator><meta:document-statistic meta:table-count="1" meta:cell-count="144" meta:object-count="1"/></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">30233</config:config-item>
<config:config-item config:name="VisibleAreaHeight" config:type="int">11289</config:config-item>
<config:config-item config:name="VisibleAreaHeight" config:type="int">20772</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="Sheet1">
<config:config-item config:name="CursorPositionX" config:type="int">3</config:config-item>
<config:config-item config:name="CursorPositionY" config:type="int">8</config:config-item>
<config:config-item config:name="CursorPositionX" config:type="int">4</config:config-item>
<config:config-item config:name="CursorPositionY" config:type="int">32</config:config-item>
<config:config-item config:name="ActiveSplitRange" config:type="short">2</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">0</config:config-item>
@ -28,7 +28,7 @@
</config:config-item-map-entry>
</config:config-item-map-named>
<config:config-item config:name="ActiveTable" config:type="string">Sheet1</config:config-item>
<config:config-item config:name="HorizontalScrollbarWidth" config:type="int">1427</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>
@ -111,6 +111,13 @@
<style:paragraph-properties style:tab-stop-distance="0.5in"/>
<style:text-properties style:font-name="Liberation Sans" fo:font-size="10pt" fo:language="en" fo:country="US" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans Devanagari" style:font-size-complex="10pt" style:language-complex="hi" style:country-complex="IN"/>
</style:default-style>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in"/>
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:punctuation-wrap="simple" style:line-break="strict" style:writing-mode="page" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" fo:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-family-asian="&apos;DejaVu Sans&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="12pt" style:language-asian="zh" style:country-asian="CN" style:font-family-complex="&apos;Noto Sans&apos;" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
</style:default-style>
<number:number-style style:name="N0">
<number:number number:min-integer-digits="1"/>
</number:number-style>
@ -222,6 +229,10 @@
<style:table-cell-properties fo:background-color="#b2b2b2" fo:border="0.74pt solid #000000"/>
</style:style>
<style:style style:name="gr1" style:family="graphic">
<style:graphic-properties draw:stroke="none" draw:fill="none" draw:textarea-horizontal-align="center" draw:textarea-vertical-align="middle" draw:ole-draw-aspect="1"/>
<style:paragraph-properties fo:text-align="center"/>
</style:style>
<style:style style:name="gr2" style:family="graphic">
<style:graphic-properties draw:marker-start="Arrowheads_20_1" draw:marker-start-width="0.0787in" draw:marker-start-center="false" draw:fill="solid" draw:fill-color="#ffffc0" draw:auto-grow-height="true" draw:auto-grow-width="false" fo:min-height="0.6295in" fo:padding-top="0.0394in" fo:padding-bottom="0.0394in" fo:padding-left="0.0394in" fo:padding-right="0.0394in" draw:shadow="hidden" draw:shadow-offset-x="0.0394in" draw:shadow-offset-y="0.0394in" draw:caption-escape-direction="auto"/>
<style:paragraph-properties style:text-autospace="none" style:line-break="normal"/>
<style:text-properties style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-size="10pt" fo:language="en" fo:country="US" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:text-underline-mode="continuous" style:text-overline-mode="continuous" style:text-line-through-mode="continuous" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10pt" style:language-asian="zh" style:country-asian="CN" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Noto Sans Devanagari" style:font-size-complex="10pt" style:language-complex="hi" style:country-complex="IN" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color" fo:hyphenate="false"/>
@ -249,10 +260,14 @@
</style:footer-style>
</style:page-layout>
<style:style style:name="P1" style:family="paragraph">
<loext:graphic-properties draw:fill="none"/>
<style:paragraph-properties fo:text-align="center"/>
</style:style>
<style:style style:name="P2" style:family="paragraph">
<style:paragraph-properties style:text-autospace="none" style:line-break="normal" style:writing-mode="page"/>
<style:text-properties style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-size="10pt" fo:language="en" fo:country="US" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:text-underline-mode="continuous" style:text-overline-mode="continuous" style:text-line-through-mode="continuous" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10pt" style:language-asian="zh" style:country-asian="CN" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Noto Sans Devanagari" style:font-size-complex="10pt" style:language-complex="hi" style:country-complex="IN" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color" fo:hyphenate="false"/>
</style:style>
<style:style style:name="P2" style:family="paragraph">
<style:style style:name="P3" style:family="paragraph">
<loext:graphic-properties draw:fill="solid" draw:fill-color="#ffffc0"/>
<style:paragraph-properties style:text-autospace="none" style:line-break="normal" style:writing-mode="page"/>
<style:text-properties style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Sans" fo:font-size="10pt" fo:language="en" fo:country="US" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:text-underline-mode="continuous" style:text-overline-mode="continuous" style:text-line-through-mode="continuous" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10pt" style:language-asian="zh" style:country-asian="CN" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-name-complex="Noto Sans Devanagari" style:font-size-complex="10pt" style:language-complex="hi" style:country-complex="IN" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-emphasize="none" style:font-relief="none" style:text-overline-style="none" style:text-overline-color="font-color" fo:hyphenate="false"/>
@ -280,7 +295,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-04-27">00/00/0000</text:date>, <text:time style:data-style-name="N2" text:time-value="15:16:19.961829363">00:00:00</text:time></text:p>
<text:p><text:date style:data-style-name="N2" text:date-value="2022-05-02">00/00/0000</text:date>, <text:time style:data-style-name="N2" text:time-value="13:04:41.212246576">00:00:00</text:time></text:p>
</style:region-right>
</style:header>
<style:header-left style:display="false"/>
@ -296,6 +311,712 @@
<office:spreadsheet>
<table:calculation-settings table:automatic-find-labels="false" table:use-regular-expressions="false" table:use-wildcards="true"/>
<table:table table:name="Sheet1" table:style-name="ta1">
<table:shapes>
<draw:frame draw:z-index="0" draw:style-name="gr1" draw:text-style-name="P1" svg:width="6.2992in" svg:height="3.5449in" svg:x="0.2705in" svg:y="4.5492in">
<draw:object draw:notify-on-update-of-ranges="Sheet1.A23:Sheet1.A23 Sheet1.C23:Sheet1.J23 Sheet1.A24:Sheet1.A24 Sheet1.C24:Sheet1.J24 Sheet1.A25:Sheet1.A25 Sheet1.C25:Sheet1.J25">
<loext:p/>
<office:document xmlns:chartooo="http://openoffice.org/2010/chart" 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:rpt="http://openoffice.org/2005/report" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing: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: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:xsd="http://www.w3.org/2001/XMLSchema" 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.chart">
<office:meta><meta:generator>LibreOffice/7.3.2.2$Linux_X86_64 LibreOffice_project/30$Build-2</meta:generator></office:meta>
<office:styles/>
<office:automatic-styles>
<number:number-style style:name="N0">
<number:number number:min-integer-digits="1"/>
</number:number-style>
<style:style style:name="ch1" style:family="chart">
<style:graphic-properties draw:stroke="none"/>
</style:style>
<style:style style:name="ch2" style:family="chart">
<style:chart-properties chart:auto-position="true"/>
<style:graphic-properties draw:stroke="none" svg:stroke-color="#b3b3b3" draw:fill="none" draw:fill-color="#e6e6e6"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="ch3" style:family="chart">
<style:chart-properties chart:include-hidden-cells="false" chart:auto-position="true" chart:auto-size="true" chart:treat-empty-cells="leave-gap" chart:series-source="rows" chart:right-angled-axes="true"/>
</style:style>
<style:style style:name="ch4" style:family="chart" style:data-style-name="N0">
<style:chart-properties chart:display-label="true" chart:logarithmic="false" chart:reverse-direction="false" text:line-break="false" loext:try-staggering-first="false" chart:link-data-style-to-source="true" chart:axis-position="0"/>
<style:graphic-properties svg:stroke-color="#b3b3b3"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="ch5" style:family="chart">
<style:chart-properties chart:auto-position="true" style:rotation-angle="0"/>
<style:text-properties fo:font-size="9pt" style:font-size-asian="9pt" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="ch6" style:family="chart" style:data-style-name="N0">
<style:chart-properties chart:display-label="true" chart:logarithmic="false" chart:reverse-direction="false" text:line-break="false" loext:try-staggering-first="false" chart:link-data-style-to-source="true" chart:axis-position="0"/>
<style:graphic-properties svg:stroke-color="#b3b3b3"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="ch7" style:family="chart">
<style:chart-properties chart:auto-position="true" style:rotation-angle="90"/>
<style:text-properties fo:font-size="9pt" style:font-size-asian="9pt" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="ch8" style:family="chart">
<style:graphic-properties svg:stroke-color="#b3b3b3"/>
</style:style>
<style:style style:name="ch9" style:family="chart" style:data-style-name="N0">
<style:chart-properties chart:link-data-style-to-source="true"/>
<style:graphic-properties draw:stroke="none" draw:fill-color="#ff8000" dr3d:edge-rounding="5%"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="ch10" style:family="chart" style:data-style-name="N0">
<style:chart-properties chart:link-data-style-to-source="true"/>
<style:graphic-properties draw:stroke="none" draw:fill-color="#ff420e" dr3d:edge-rounding="5%"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="ch11" style:family="chart" style:data-style-name="N0">
<style:chart-properties chart:link-data-style-to-source="true"/>
<style:graphic-properties draw:stroke="none" draw:fill-color="#81d41a" dr3d:edge-rounding="5%"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="ch12" style:family="chart">
<style:graphic-properties draw:stroke="solid" svg:stroke-color="#b3b3b3" draw:fill="none" draw:fill-color="#e6e6e6"/>
</style:style>
<style:style style:name="ch13" style:family="chart">
<style:graphic-properties svg:stroke-color="#b3b3b3" draw:fill-color="#cccccc"/>
</style:style>
</office:automatic-styles>
<office:body>
<office:chart>
<chart:chart svg:width="16.001cm" svg:height="9.005cm" xlink:href=".." xlink:type="simple" chart:class="chart:bar" chart:style-name="ch1">
<chart:legend chart:legend-position="end" svg:x="12.6cm" svg:y="3.705cm" style:legend-expansion="high" chart:style-name="ch2"/>
<chart:plot-area chart:style-name="ch3" table:cell-range-address="Sheet1.A23:Sheet1.A25 Sheet1.C23:Sheet1.J25" chart:data-source-has-labels="column" svg:x="1.331cm" svg:y="0.18cm" svg:width="10.949cm" svg:height="7.664cm">
<chart:coordinate-region svg:x="1.952cm" svg:y="0.379cm" svg:width="10.328cm" svg:height="6.818cm"/>
<chart:axis chart:dimension="x" chart:name="primary-x" chart:style-name="ch4">
<chart:title svg:x="6.457cm" svg:y="8.024cm" chart:style-name="ch5">
<text:p>Day</text:p>
</chart:title>
</chart:axis>
<chart:axis chart:dimension="y" chart:name="primary-y" chart:style-name="ch6">
<chart:title svg:x="0.451cm" svg:y="4.506cm" chart:style-name="ch7">
<text:p>Hours</text:p>
</chart:title>
<chart:grid chart:style-name="ch8" chart:class="major"/>
</chart:axis>
<chart:series chart:style-name="ch9" chart:values-cell-range-address="Sheet1.C23:Sheet1.J23" chart:label-cell-address="Sheet1.A23:Sheet1.A23" chart:class="chart:bar">
<chart:data-point chart:repeated="8"/>
</chart:series>
<chart:series chart:style-name="ch10" chart:values-cell-range-address="Sheet1.C24:Sheet1.J24" chart:label-cell-address="Sheet1.A24:Sheet1.A24" chart:class="chart:bar">
<chart:data-point chart:repeated="8"/>
</chart:series>
<chart:series chart:style-name="ch11" chart:values-cell-range-address="Sheet1.C25:Sheet1.J25" chart:label-cell-address="Sheet1.A25:Sheet1.A25" chart:class="chart:bar">
<chart:data-point chart:repeated="8"/>
</chart:series>
<chart:wall chart:style-name="ch12"/>
<chart:floor chart:style-name="ch13"/>
</chart:plot-area>
<table:table table:name="local-table">
<table:table-header-columns>
<table:table-column/>
</table:table-header-columns>
<table:table-columns>
<table:table-column table:number-columns-repeated="8"/>
</table:table-columns>
<table:table-header-rows>
<table:table-row>
<table:table-cell>
<text:p/>
</table:table-cell>
<table:table-cell office:value-type="string">
<text:p>1</text:p>
</table:table-cell>
<table:table-cell office:value-type="string">
<text:p>2</text:p>
</table:table-cell>
<table:table-cell office:value-type="string">
<text:p>3</text:p>
</table:table-cell>
<table:table-cell office:value-type="string">
<text:p>4</text:p>
</table:table-cell>
<table:table-cell office:value-type="string">
<text:p>5</text:p>
</table:table-cell>
<table:table-cell office:value-type="string">
<text:p>6</text:p>
</table:table-cell>
<table:table-cell office:value-type="string">
<text:p>7</text:p>
</table:table-cell>
<table:table-cell office:value-type="string">
<text:p>8</text:p>
</table:table-cell>
</table:table-row>
</table:table-header-rows>
<table:table-rows>
<table:table-row>
<table:table-cell office:value-type="string">
<text:p>Total Hours</text:p>
<draw:g>
<svg:desc>Sheet1.A23:Sheet1.A23</svg:desc></draw:g>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="33">
<text:p>33</text:p>
<draw:g>
<svg:desc>Sheet1.C23:Sheet1.J23</svg:desc></draw:g>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="33">
<text:p>33</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="33">
<text:p>33</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="33">
<text:p>33</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="33">
<text:p>33</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="33">
<text:p>33</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="33">
<text:p>33</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="33">
<text:p>33</text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell office:value-type="string">
<text:p>Hours Remaining</text:p>
<draw:g>
<svg:desc>Sheet1.A24:Sheet1.A24</svg:desc></draw:g>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="33">
<text:p>33</text:p>
<draw:g>
<svg:desc>Sheet1.C24:Sheet1.J24</svg:desc></draw:g>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="30">
<text:p>30</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="30">
<text:p>30</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="20">
<text:p>20</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="20">
<text:p>20</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="10">
<text:p>10</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="10">
<text:p>10</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="10">
<text:p>10</text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell office:value-type="string">
<text:p>Hours Completed</text:p>
<draw:g>
<svg:desc>Sheet1.A25:Sheet1.A25</svg:desc></draw:g>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="0">
<text:p>0</text:p>
<draw:g>
<svg:desc>Sheet1.C25:Sheet1.J25</svg:desc></draw:g>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="3">
<text:p>3</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="3">
<text:p>3</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="13">
<text:p>13</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="13">
<text:p>13</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="23">
<text:p>23</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="23">
<text:p>23</text:p>
</table:table-cell>
<table:table-cell office:value-type="float" office:value="23">
<text:p>23</text:p>
</table:table-cell>
</table:table-row>
</table:table-rows>
</table:table>
</chart:chart>
</office:chart>
</office:body>
</office:document>
</draw:object><draw:image>
<office:binary-data>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
AAAATwAAAAEASQAAAAEAEwAAAAIACgcAABwcAACgBwAAHBwAAAABAAIAAAAAAAEAAgAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACEAAEABQAAALOzswABhQABAAUAAAAA
AAAAAG0AAwBTAAAAAgAKBwAAHBwAAKAHAAAcHAAABQA6AAAAAQAAAAAAAAAAAAAAAAAAAAAA
AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgEAHQAAABMAWFBB
VEhTVFJPS0VfU0VRX0VORAAAAAAAAAAAjAABAAAAAACLAAEAAgAAAAMAhAABAAUAAACzs7MA
AQACAQBuAAAAFQBYUEFUSFNUUk9LRV9TRVFfQkVHSU4AAAAATwAAAAEASQAAAAEAEwAAAAIA
CgcAAE4YAACgBwAAThgAAAABAAIAAAAAAAEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA
AAAAAAAAAAAAAACEAAEABQAAALOzswABhQABAAUAAAAAAAAAAG0AAwBTAAAAAgAKBwAAThgA
AKAHAABOGAAABQA6AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAgEAHQAAABMAWFBBVEhTVFJPS0VfU0VRX0VORAAAAAAA
AAAAjAABAAAAAACLAAEAAgAAAAMAhAABAAUAAACzs7MAAQACAQBuAAAAFQBYUEFUSFNUUk9L
RV9TRVFfQkVHSU4AAAAATwAAAAEASQAAAAEAEwAAAAIACgcAAE4YAACgBwAAThgAAAABAAIA
AAAAAAEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACEAAEABQAAALOz
swABhQABAAUAAAAAAAAAAG0AAwBTAAAAAgAKBwAAThgAAKAHAABOGAAABQA6AAAAAQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AgEAHQAAABMAWFBBVEhTVFJPS0VfU0VRX0VORAAAAAAAAAAAjAABAAAAAACLAAEAAgAAAAMA
hAABAAUAAACzs7MAAQACAQBuAAAAFQBYUEFUSFNUUk9LRV9TRVFfQkVHSU4AAAAATwAAAAEA
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
AACgBwAAFgkAAAABAAIAAAAAAAEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA
AAAAAACEAAEABQAAALOzswABhQABAAUAAAAAAAAAAG0AAwBTAAAAAgAKBwAAFgkAAKAHAAAW
CQAABQA6AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAgEAHQAAABMAWFBBVEhTVFJPS0VfU0VRX0VORAAAAAAAAAAAjAAB
AAAAAACLAAEAAgAAAAMAhAABAAUAAACzs7MAAQACAQBuAAAAFQBYUEFUSFNUUk9LRV9TRVFf
QkVHSU4AAAAATwAAAAEASQAAAAEAEwAAAAIACgcAABYJAACgBwAAFgkAAAABAAIAAAAAAAEA
AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACEAAEABQAAALOzswABhQAB
AAUAAAAAAAAAAG0AAwBTAAAAAgAKBwAAFgkAAKAHAAAWCQAABQA6AAAAAQAAAAAAAAAAAAAA
AAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgEAHQAA
ABMAWFBBVEhTVFJPS0VfU0VRX0VORAAAAAAAAAAAjAABAAAAAACLAAEAAgAAAAMAhAABAAUA
AACzs7MAAQACAQBuAAAAFQBYUEFUSFNUUk9LRV9TRVFfQkVHSU4AAAAATwAAAAEASQAAAAEA
EwAAAAIACgcAAEgFAACgBwAASAUAAAABAAIAAAAAAAEAAgAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAQAAAAAAAAAAAAAAAACEAAEABQAAALOzswABhQABAAUAAAAAAAAAAG0AAwBTAAAAAgAK
BwAASAUAAKAHAABIBQAABQA6AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgEAHQAAABMAWFBBVEhTVFJPS0VfU0VRX0VO
RAAAAAAAAAAAjAABAAAAAACLAAEAAgAAAAMAhAABAAUAAACzs7MAAQACAQBuAAAAFQBYUEFU
SFNUUk9LRV9TRVFfQkVHSU4AAAAATwAAAAEASQAAAAEAEwAAAAIACgcAAEgFAACgBwAASAUA
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
CQBYVEVYVF9FT0MAAAAAAAAAAAACAQATAAAACQBYVEVYVF9FT0wAAAAAAAAAAAACAQATAAAA
CQBYVEVYVF9FT1AAAAAAAAAAAAACAQAeAAAAFABYVEVYVF9QQUlOVFNIQVBFX0VORAAAAAAA
AAAAAAIBACAAAAAWAFhURVhUX1BBSU5UU0hBUEVfQkVHSU4AAAAAAAAAAIoAAQBFAAAABAA/
AAAADwBMaWJlcmF0aW9uIFNhbnMAAAAAAABhAQAA//8AAAIABQAAAAAAAAAJBAAAAAAAAAAB
AP8DAAAAAAAAAAAAiAABAAIAAAABAIcAAQAFAAAA/////wCGAAEABAAAAAAAAABxAAIAHgAA
APUdAABUHgAAAQAAADUAAAABAAEAAAC5AAAAAQA1AAACAQATAAAACQBYVEVYVF9FT0MAAAAA
AAAAAAACAQATAAAACQBYVEVYVF9FT0wAAAAAAAAAAAACAQATAAAACQBYVEVYVF9FT1AAAAAA
AAAAAAACAQAeAAAAFABYVEVYVF9QQUlOVFNIQVBFX0VORAAAAAAAAAAAAAIBACAAAAAWAFhU
RVhUX1BBSU5UU0hBUEVfQkVHSU4AAAAAAAAAAIoAAQBFAAAABAA/AAAADwBMaWJlcmF0aW9u
IFNhbnMAAAAAAABhAQAA//8AAAIABQAAAAAAAAAJBAAAAAAAAAABAP8DAAAAAAAAAAAAiAAB
AAIAAAABAIcAAQAFAAAA/////wCGAAEABAAAAAAAAABxAAIAHgAAAAAjAABUHgAAAQAAADYA
AAABAAEAAAC5AAAAAQA2AAACAQATAAAACQBYVEVYVF9FT0MAAAAAAAAAAAACAQATAAAACQBY
VEVYVF9FT0wAAAAAAAAAAAACAQATAAAACQBYVEVYVF9FT1AAAAAAAAAAAAACAQAeAAAAFABY
VEVYVF9QQUlOVFNIQVBFX0VORAAAAAAAAAAAAAIBACAAAAAWAFhURVhUX1BBSU5UU0hBUEVf
QkVHSU4AAAAAAAAAAIoAAQBFAAAABAA/AAAADwBMaWJlcmF0aW9uIFNhbnMAAAAAAABhAQAA
//8AAAIABQAAAAAAAAAJBAAAAAAAAAABAP8DAAAAAAAAAAAAiAABAAIAAAABAIcAAQAFAAAA
/////wCGAAEABAAAAAAAAABxAAIAHgAAAAsoAABUHgAAAQAAADcAAAABAAEAAAC5AAAAAQA3
AAACAQATAAAACQBYVEVYVF9FT0MAAAAAAAAAAAACAQATAAAACQBYVEVYVF9FT0wAAAAAAAAA
AAACAQATAAAACQBYVEVYVF9FT1AAAAAAAAAAAAACAQAeAAAAFABYVEVYVF9QQUlOVFNIQVBF
X0VORAAAAAAAAAAAAAIBACAAAAAWAFhURVhUX1BBSU5UU0hBUEVfQkVHSU4AAAAAAAAAAIoA
AQBFAAAABAA/AAAADwBMaWJlcmF0aW9uIFNhbnMAAAAAAABhAQAA//8AAAIABQAAAAAAAAAJ
BAAAAAAAAAABAP8DAAAAAAAAAAAAiAABAAIAAAABAIcAAQAFAAAA/////wCGAAEABAAAAAAA
AABxAAIAHgAAABYtAABUHgAAAQAAADgAAAABAAEAAAC5AAAAAQA4AAACAQATAAAACQBYVEVY
VF9FT0MAAAAAAAAAAAACAQATAAAACQBYVEVYVF9FT0wAAAAAAAAAAAACAQATAAAACQBYVEVY
VF9FT1AAAAAAAAAAAAACAQAeAAAAFABYVEVYVF9QQUlOVFNIQVBFX0VORAAAAAAAAAAAAAIB
ACAAAAAWAFhURVhUX1BBSU5UU0hBUEVfQkVHSU4AAAAAAAAAAIoAAQBFAAAABAA/AAAADwBM
aWJlcmF0aW9uIFNhbnMAAAAAAABhAQAA//8AAAIABQAAAAAAAAAJBAAAAAAAAAABAP8DAAAA
AAAAAAAAiAABAAIAAAABAIcAAQAFAAAA/////wCGAAEABAAAAAAAAABxAAIAHgAAAO0FAACV
HAAAAQAAADAAAAABAAEAAAC5AAAAAQAwAAACAQATAAAACQBYVEVYVF9FT0MAAAAAAAAAAAAC
AQATAAAACQBYVEVYVF9FT0wAAAAAAAAAAAACAQATAAAACQBYVEVYVF9FT1AAAAAAAAAAAAAC
AQAeAAAAFABYVEVYVF9QQUlOVFNIQVBFX0VORAAAAAAAAAAAAAIBACAAAAAWAFhURVhUX1BB
SU5UU0hBUEVfQkVHSU4AAAAAAAAAAIoAAQBFAAAABAA/AAAADwBMaWJlcmF0aW9uIFNhbnMA
AAAAAABhAQAA//8AAAIABQAAAAAAAAAJBAAAAAAAAAABAP8DAAAAAAAAAAAAiAABAAIAAAAB
AIcAAQAFAAAA/////wCGAAEABAAAAAAAAABxAAIAHgAAAO0FAADHGAAAAQAAADUAAAABAAEA
AAC5AAAAAQA1AAACAQATAAAACQBYVEVYVF9FT0MAAAAAAAAAAAACAQATAAAACQBYVEVYVF9F
T0wAAAAAAAAAAAACAQATAAAACQBYVEVYVF9FT1AAAAAAAAAAAAACAQAeAAAAFABYVEVYVF9Q
QUlOVFNIQVBFX0VORAAAAAAAAAAAAAIBACAAAAAWAFhURVhUX1BBSU5UU0hBUEVfQkVHSU4A
AAAAAAAAAIoAAQBFAAAABAA/AAAADwBMaWJlcmF0aW9uIFNhbnMAAAAAAABhAQAA//8AAAIA
BQAAAAAAAAAJBAAAAAAAAAABAP8DAAAAAAAAAAAAiAABAAIAAAABAIcAAQAFAAAA/////wCG
AAEABAAAAAAAAABxAAIAJgAAADQFAAD5FAAAAgAAADEAMAAAAAIAAgAAALkAAAByAQAAAgAx
ADAAAAIBABMAAAAJAFhURVhUX0VPQwAAAAAAAAAAAAIBABMAAAAJAFhURVhUX0VPQwEAAAAA
AAAAAAIBABMAAAAJAFhURVhUX0VPTAAAAAAAAAAAAAIBABMAAAAJAFhURVhUX0VPUAAAAAAA
AAAAAAIBAB4AAAAUAFhURVhUX1BBSU5UU0hBUEVfRU5EAAAAAAAAAAAAAgEAIAAAABYAWFRF
WFRfUEFJTlRTSEFQRV9CRUdJTgAAAAAAAAAAigABAEUAAAAEAD8AAAAPAExpYmVyYXRpb24g
U2FucwAAAAAAAGEBAAD//wAAAgAFAAAAAAAAAAkEAAAAAAAAAAEA/wMAAAAAAAAAAACIAAEA
AgAAAAEAhwABAAUAAAD/////AIYAAQAEAAAAAAAAAHEAAgAmAAAANAUAACsRAAACAAAAMQA1
AAAAAgACAAAAuQAAAHIBAAACADEANQAAAgEAEwAAAAkAWFRFWFRfRU9DAAAAAAAAAAAAAgEA
EwAAAAkAWFRFWFRfRU9DAQAAAAAAAAAAAgEAEwAAAAkAWFRFWFRfRU9MAAAAAAAAAAAAAgEA
EwAAAAkAWFRFWFRfRU9QAAAAAAAAAAAAAgEAHgAAABQAWFRFWFRfUEFJTlRTSEFQRV9FTkQA
AAAAAAAAAAACAQAgAAAAFgBYVEVYVF9QQUlOVFNIQVBFX0JFR0lOAAAAAAAAAACKAAEARQAA
AAQAPwAAAA8ATGliZXJhdGlvbiBTYW5zAAAAAAAAYQEAAP//AAACAAUAAAAAAAAACQQAAAAA
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
</office:binary-data>
</draw:image>
</draw:frame></table:shapes>
<table:table-column table:style-name="co1" table:default-cell-style-name="ce4"/>
<table:table-column table:style-name="co2" table:default-cell-style-name="ce4"/>
<table:table-column table:style-name="co3" table:default-cell-style-name="ce4"/>
@ -314,9 +1035,9 @@
<text:p>Monday</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce1" office:value-type="string" calcext:value-type="string">
<office:annotation draw:style-name="gr1" draw:text-style-name="P2" svg:width="1.1413in" svg:height="0.7083in" svg:x="7.6945in" svg:y="0in" draw:caption-point-x="-0.2402in" draw:caption-point-y="0.0039in">
<office:annotation draw:style-name="gr2" draw:text-style-name="P3" svg:width="1.1413in" svg:height="0.7083in" svg:x="7.6945in" svg:y="0in" draw:caption-point-x="-0.2402in" draw:caption-point-y="0.0039in">
<dc:date>2022-04-27T00:00:00</dc:date>
<text:p text:style-name="P1"><text:span text:style-name="T1">No work on the project was done on Tuesday.</text:span></text:p>
<text:p text:style-name="P2"><text:span text:style-name="T1">No work on the project was done on Tuesday.</text:span></text:p>
</office:annotation>
<text:p>Tuesday</text:p>
</table:table-cell>
@ -324,12 +1045,20 @@
<text:p>Wednesday</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce1" office:value-type="string" calcext:value-type="string">
<office:annotation draw:style-name="gr2" draw:text-style-name="P3" svg:width="1.1413in" svg:height="0.7083in" svg:x="9.4724in" svg:y="0in" draw:caption-point-x="-0.2402in" draw:caption-point-y="0.0039in">
<dc:date>2022-04-29T00:00:00</dc:date>
<text:p text:style-name="P2"><text:span text:style-name="T1">No work on tasks, only a refactoring/fix on this day.</text:span></text:p>
</office:annotation>
<text:p>Thursday</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce1" office:value-type="string" calcext:value-type="string">
<text:p>Friday</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce1" office:value-type="string" calcext:value-type="string">
<office:annotation draw:style-name="gr2" draw:text-style-name="P3" svg:width="1.1413in" svg:height="0.7083in" svg:x="11.25in" svg:y="0in" draw:caption-point-x="-0.2402in" draw:caption-point-y="0.0039in">
<dc:date>2022-04-30T00:00:00</dc:date>
<text:p text:style-name="P2"><text:span text:style-name="T1">Refactorings were done on this day.</text:span></text:p>
</office:annotation>
<text:p>Saturday</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce1" office:value-type="string" calcext:value-type="string">
@ -394,49 +1123,65 @@
<table:table-cell table:style-name="ce3" office:value-type="string" calcext:value-type="string">
<text:p>Back-end protocol update to support emote sending</text:p>
</table:table-cell>
<table:table-cell office:value-type="string" calcext:value-type="string">
<text:p>Pending</text:p>
<table:table-cell table:style-name="ce7" office:value-type="string" calcext:value-type="string">
<text:p>Done</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="4" office:value-type="float" office:value="1" calcext:value-type="float">
<table:table-cell table:number-columns-repeated="5" office:value-type="float" office:value="1" calcext:value-type="float">
<text:p>1</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1018"/>
<table:table-cell table:style-name="ce8" office:value-type="float" office:value="0" calcext:value-type="float">
<text:p>0</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce8" table:number-columns-repeated="2"/>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
<table:table-cell table:style-name="ce3" office:value-type="string" calcext:value-type="string">
<text:p>Back-end protocol impl. to support emote sending</text:p>
</table:table-cell>
<table:table-cell office:value-type="string" calcext:value-type="string">
<text:p>Pending</text:p>
<table:table-cell table:style-name="ce7" office:value-type="string" calcext:value-type="string">
<text:p>Done</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="4" office:value-type="float" office:value="3" calcext:value-type="float">
<table:table-cell table:number-columns-repeated="5" office:value-type="float" office:value="3" calcext:value-type="float">
<text:p>3</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1018"/>
<table:table-cell table:style-name="ce8" office:value-type="float" office:value="0" calcext:value-type="float">
<text:p>0</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce8" table:number-columns-repeated="2"/>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
<table:table-cell table:style-name="ce3" office:value-type="string" calcext:value-type="string">
<text:p>Front-end buttons to send emotes</text:p>
</table:table-cell>
<table:table-cell office:value-type="string" calcext:value-type="string">
<text:p>Pending</text:p>
<table:table-cell table:style-name="ce7" office:value-type="string" calcext:value-type="string">
<text:p>Done</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="4" office:value-type="float" office:value="3" calcext:value-type="float">
<table:table-cell table:number-columns-repeated="5" office:value-type="float" office:value="3" calcext:value-type="float">
<text:p>3</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1018"/>
<table:table-cell table:style-name="ce8" office:value-type="float" office:value="0" calcext:value-type="float">
<text:p>0</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce8" table:number-columns-repeated="2"/>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
<table:table-cell table:style-name="ce3" office:value-type="string" calcext:value-type="string">
<text:p>Front-end display for received emotes</text:p>
</table:table-cell>
<table:table-cell office:value-type="string" calcext:value-type="string">
<text:p>Pending</text:p>
<table:table-cell table:style-name="ce7" office:value-type="string" calcext:value-type="string">
<text:p>Done</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="4" office:value-type="float" office:value="3" calcext:value-type="float">
<table:table-cell table:number-columns-repeated="5" office:value-type="float" office:value="3" calcext:value-type="float">
<text:p>3</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1018"/>
<table:table-cell table:style-name="ce8" office:value-type="float" office:value="0" calcext:value-type="float">
<text:p>0</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce8" table:number-columns-repeated="2"/>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
<table:table-cell table:style-name="ce2" office:value-type="string" calcext:value-type="string">
@ -452,10 +1197,10 @@
<table:table-cell office:value-type="string" calcext:value-type="string">
<text:p>Pending</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="4" office:value-type="float" office:value="1" calcext:value-type="float">
<table:table-cell table:number-columns-repeated="8" office:value-type="float" office:value="1" calcext:value-type="float">
<text:p>1</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1018"/>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
<table:table-cell table:style-name="ce3" office:value-type="string" calcext:value-type="string">
@ -464,10 +1209,10 @@
<table:table-cell office:value-type="string" calcext:value-type="string">
<text:p>Pending</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="4" office:value-type="float" office:value="3" calcext:value-type="float">
<table:table-cell table:number-columns-repeated="8" office:value-type="float" office:value="3" calcext:value-type="float">
<text:p>3</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1018"/>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
<table:table-cell table:style-name="ce3" office:value-type="string" calcext:value-type="string">
@ -476,10 +1221,10 @@
<table:table-cell office:value-type="string" calcext:value-type="string">
<text:p>Pending</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="4" office:value-type="float" office:value="3" calcext:value-type="float">
<table:table-cell table:number-columns-repeated="8" office:value-type="float" office:value="3" calcext:value-type="float">
<text:p>3</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1018"/>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
<table:table-cell table:style-name="ce3" office:value-type="string" calcext:value-type="string">
@ -488,10 +1233,10 @@
<table:table-cell office:value-type="string" calcext:value-type="string">
<text:p>Pending</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="4" office:value-type="float" office:value="3" calcext:value-type="float">
<table:table-cell table:number-columns-repeated="8" office:value-type="float" office:value="3" calcext:value-type="float">
<text:p>3</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1018"/>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
<table:table-cell table:style-name="ce2" office:value-type="string" calcext:value-type="string">
@ -568,10 +1313,9 @@
<text:p>Total Hours</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce5"/>
<table:table-cell table:number-columns-repeated="4" table:style-name="ce5" office:value-type="float" office:value="33" calcext:value-type="float">
<table:table-cell table:number-columns-repeated="8" table:style-name="ce5" office:value-type="float" office:value="33" calcext:value-type="float">
<text:p>33</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce5" table:number-columns-repeated="4"/>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
@ -591,7 +1335,18 @@
<table:table-cell table:style-name="ce6" table:formula="of:=SUM([.F$3:.F$19])" office:value-type="float" office:value="20" calcext:value-type="float">
<text:p>20</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce6" table:number-columns-repeated="4"/>
<table:table-cell table:style-name="ce6" table:formula="of:=SUM([.G$3:.G$19])" office:value-type="float" office:value="20" calcext:value-type="float">
<text:p>20</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce6" table:formula="of:=SUM([.H$3:.H$19])" office:value-type="float" office:value="10" calcext:value-type="float">
<text:p>10</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce6" table:formula="of:=SUM([.I$3:.I$19])" office:value-type="float" office:value="10" calcext:value-type="float">
<text:p>10</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce6" table:formula="of:=SUM([.J$3:.J$19])" office:value-type="float" office:value="10" calcext:value-type="float">
<text:p>10</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1">
@ -611,7 +1366,18 @@
<table:table-cell table:style-name="ce7" table:formula="of:=[.F$23]-[.F$24]" 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="ce7" table:number-columns-repeated="4"/>
<table:table-cell table:style-name="ce7" table:formula="of:=[.G$23]-[.G$24]" 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="ce7" table:formula="of:=[.H$23]-[.H$24]" office:value-type="float" office:value="23" calcext:value-type="float">
<text:p>23</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce7" table:formula="of:=[.I$23]-[.I$24]" office:value-type="float" office:value="23" calcext:value-type="float">
<text:p>23</text:p>
</table:table-cell>
<table:table-cell table:style-name="ce7" table:formula="of:=[.J$23]-[.J$24]" office:value-type="float" office:value="23" calcext:value-type="float">
<text:p>23</text:p>
</table:table-cell>
<table:table-cell table:number-columns-repeated="1014"/>
</table:table-row>
<table:table-row table:style-name="ro1" table:number-rows-repeated="1048550">

Binary file not shown.