From 583ece9c12a55a4f3654b8ce9b407eb0737b76ef Mon Sep 17 00:00:00 2001 From: Evan Pratten Date: Sun, 3 Oct 2021 12:12:06 -0400 Subject: [PATCH 1/3] Loading and saving time data Co-authored-by: Luna --- game/Cargo.toml | 4 +-- game/src/context.rs | 6 ++-- game/src/lib.rs | 12 ++++++++ game/src/progress.rs | 44 +++++++++++++++++++++++++++++ game/src/scenes/ingame_scene/mod.rs | 13 ++++++++- savegame.json | 1 + 6 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 game/src/progress.rs create mode 100644 savegame.json diff --git a/game/Cargo.toml b/game/Cargo.toml index 4538ee0..e1da054 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -14,7 +14,7 @@ tracing = { version = "0.1", features = ["log"] } serde = { version = "1.0.126", features = ["derive"] } serde_json = "1.0.64" thiserror = "1.0" -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } rust-embed = "6.2.0" raylib = { version = "3.5", git = "https://github.com/ewpratten/raylib-rs", rev = "2ae949cb3488dd1bb052ece71d61021c8dd6e910", features = [ "serde" @@ -31,7 +31,7 @@ pkg-version = "1.0" cfg-if = "1.0" num-derive = "0.3" num = "0.4" -tiled = { version ="0.9.5", default-features = false } +tiled = { version = "0.9.5", default-features = false } async-trait = "0.1.51" webbrowser = "0.5" diff --git a/game/src/context.rs b/game/src/context.rs index 10957df..a27eb26 100644 --- a/game/src/context.rs +++ b/game/src/context.rs @@ -3,19 +3,21 @@ use std::{cell::RefCell, sync::mpsc::Sender}; use chrono::{DateTime, Utc}; use discord_sdk::activity::ActivityBuilder; -use crate::{utilities::non_ref_raylib::HackedRaylibHandle, GameConfig}; +use crate::{progress::ProgressData, utilities::non_ref_raylib::HackedRaylibHandle, GameConfig}; #[derive(Debug)] pub enum ControlFlag { Quit, SwitchLevel(usize), - UpdateLevelStart(DateTime) + UpdateLevelStart(DateTime), + SaveProgress } #[derive(Debug)] pub struct GameContext { pub renderer: RefCell, pub config: GameConfig, + pub player_progress: ProgressData, pub current_level: usize, pub level_start_time: DateTime, pub discord_rpc_send: Sender>, diff --git a/game/src/lib.rs b/game/src/lib.rs index 3b4c965..f1cb87f 100644 --- a/game/src/lib.rs +++ b/game/src/lib.rs @@ -81,6 +81,7 @@ use utilities::discord::DiscordConfig; use crate::{ context::GameContext, discord_rpc::{maybe_set_discord_presence, try_connect_to_local_discord}, + progress::ProgressData, scenes::{build_screen_state_machine, Scenes}, utilities::{ game_config::FinalShaderConfig, @@ -108,6 +109,7 @@ mod scenes; mod utilities; pub use utilities::{datastore::StaticGameData, game_config::GameConfig}; mod character; +mod progress; /// The game entrypoint pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box> { @@ -148,6 +150,9 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box Result<(), Box Result<(), Box break, context::ControlFlag::SwitchLevel(level) => { context.as_mut().current_level = level; + context.as_mut().player_progress.save(); } context::ControlFlag::UpdateLevelStart(time) => { context.as_mut().level_start_time = time; + context.as_mut().player_progress.save(); + } + context::ControlFlag::SaveProgress => { + context.as_mut().player_progress.save(); } } } @@ -333,5 +344,6 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box, +} + +impl ProgressData { + pub fn get_level_best_time(&self, level: usize) -> Option { + let level_best_time = self.level_best_times.get(&level); + match level_best_time { + Some(time) => Some(Duration::seconds(*time)), + None => None, + } + } + + pub fn maybe_write_new_time(&mut self, level: usize, time: &Duration) { + let time_in_seconds = time.num_seconds(); + if let Some(best_time) = self.get_level_best_time(level) { + if best_time.num_seconds() > time_in_seconds { + self.level_best_times.insert(level, time_in_seconds); + } + } else { + self.level_best_times.insert(level, time_in_seconds); + } + } + + pub fn load_from_file() -> Self { + info!("Loading progress data from file"); + serde_json::from_str( + &std::fs::read_to_string("./savegame.json") + .unwrap_or("{\"level_best_times\":{}}".to_string()), + ) + .unwrap_or(Self::default()) + } + + pub fn save(&self) { + info!("Saving progress data to file"); + std::fs::write("./savegame.json", serde_json::to_string(self).unwrap()).unwrap() + } +} diff --git a/game/src/scenes/ingame_scene/mod.rs b/game/src/scenes/ingame_scene/mod.rs index 798a098..3645217 100644 --- a/game/src/scenes/ingame_scene/mod.rs +++ b/game/src/scenes/ingame_scene/mod.rs @@ -104,7 +104,12 @@ impl Action for InGameScreen { if self.current_level_idx != context.current_level { self.current_level_idx = context.current_level; self.level_switch_timestamp = Utc::now(); - context.flag_send.send(Some(ControlFlag::UpdateLevelStart(self.level_switch_timestamp))).unwrap(); + context + .flag_send + .send(Some(ControlFlag::UpdateLevelStart( + self.level_switch_timestamp, + ))) + .unwrap(); } // Grab exclusive access to the renderer @@ -131,6 +136,12 @@ impl Action for InGameScreen { // Check if the player won let cur_level = self.levels.get(context.current_level).unwrap(); if self.player.position.x > cur_level.zones.win.x { + // Save the progress + context + .flag_send + .send(Some(ControlFlag::SaveProgress)) + .unwrap(); + // If this is the last level, win the game if self.current_level_idx >= self.levels.len() - 1 { return Ok(ActionFlag::SwitchState(Scenes::WinScreen)); diff --git a/savegame.json b/savegame.json new file mode 100644 index 0000000..cdcc103 --- /dev/null +++ b/savegame.json @@ -0,0 +1 @@ +{"level_best_times":{}} \ No newline at end of file From 17049c7fa96ca5e4852aa534e2125a60379ca2ad Mon Sep 17 00:00:00 2001 From: Evan Pratten Date: Sun, 3 Oct 2021 12:15:44 -0400 Subject: [PATCH 2/3] rm savegame --- game/src/context.rs | 5 +++-- game/src/lib.rs | 6 ++++++ game/src/scenes/ingame_scene/mod.rs | 11 ++++++++++- savegame.json | 1 - 4 files changed, 19 insertions(+), 4 deletions(-) delete mode 100644 savegame.json diff --git a/game/src/context.rs b/game/src/context.rs index a27eb26..b08f8e8 100644 --- a/game/src/context.rs +++ b/game/src/context.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, sync::mpsc::Sender}; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Duration, Utc}; use discord_sdk::activity::ActivityBuilder; use crate::{progress::ProgressData, utilities::non_ref_raylib::HackedRaylibHandle, GameConfig}; @@ -10,7 +10,8 @@ pub enum ControlFlag { Quit, SwitchLevel(usize), UpdateLevelStart(DateTime), - SaveProgress + SaveProgress, + MaybeUpdateHighScore(usize, Duration) } #[derive(Debug)] diff --git a/game/src/lib.rs b/game/src/lib.rs index f1cb87f..31aee85 100644 --- a/game/src/lib.rs +++ b/game/src/lib.rs @@ -335,6 +335,12 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box { context.as_mut().player_progress.save(); } + context::ControlFlag::MaybeUpdateHighScore(level, time) => { + context + .as_mut() + .player_progress + .maybe_write_new_time(level, &time); + } } } } diff --git a/game/src/scenes/ingame_scene/mod.rs b/game/src/scenes/ingame_scene/mod.rs index 3645217..7762dc1 100644 --- a/game/src/scenes/ingame_scene/mod.rs +++ b/game/src/scenes/ingame_scene/mod.rs @@ -136,6 +136,16 @@ impl Action for InGameScreen { // Check if the player won let cur_level = self.levels.get(context.current_level).unwrap(); if self.player.position.x > cur_level.zones.win.x { + // Save the current time + let elapsed = Utc::now() - self.level_switch_timestamp; + context + .flag_send + .send(Some(ControlFlag::MaybeUpdateHighScore( + self.current_level_idx, + elapsed, + ))) + .unwrap(); + // Save the progress context .flag_send @@ -152,7 +162,6 @@ impl Action for InGameScreen { .send(Some(ControlFlag::SwitchLevel(self.current_level_idx + 1))) .unwrap(); - // TODO: This is where the timer should reset and publish state return Ok(ActionFlag::SwitchState(Scenes::NextLevelScreen)); } } diff --git a/savegame.json b/savegame.json deleted file mode 100644 index cdcc103..0000000 --- a/savegame.json +++ /dev/null @@ -1 +0,0 @@ -{"level_best_times":{}} \ No newline at end of file From 2bfdc35ab335df501fa4f11e11eb17dcd6fb209f Mon Sep 17 00:00:00 2001 From: Evan Pratten Date: Sun, 3 Oct 2021 12:28:34 -0400 Subject: [PATCH 3/3] implement the rest of the level timers Co-authored-by: Luna --- .gitignore | 1 + game/src/scenes/ingame_scene/mod.rs | 1 + game/src/scenes/next_level_screen.rs | 34 ++++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 99d1388..076443d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Cargo.lock **/*.rs.bk /*.gif +/savegame.json diff --git a/game/src/scenes/ingame_scene/mod.rs b/game/src/scenes/ingame_scene/mod.rs index 7762dc1..8577c83 100644 --- a/game/src/scenes/ingame_scene/mod.rs +++ b/game/src/scenes/ingame_scene/mod.rs @@ -133,6 +133,7 @@ impl Action for InGameScreen { // Render the HUD self.render_screen_space(&mut renderer, &context.config); + // Check if the player won let cur_level = self.levels.get(context.current_level).unwrap(); if self.player.position.x > cur_level.zones.win.x { diff --git a/game/src/scenes/next_level_screen.rs b/game/src/scenes/next_level_screen.rs index 5714600..b677855 100644 --- a/game/src/scenes/next_level_screen.rs +++ b/game/src/scenes/next_level_screen.rs @@ -1,6 +1,6 @@ use std::ops::{Div, Sub}; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Duration, Utc}; use dirty_fsm::{Action, ActionFlag}; use discord_sdk::activity::{ActivityBuilder, Assets}; use pkg_version::pkg_version_major; @@ -24,6 +24,9 @@ use tracing::{debug, error, info, trace}; #[derive(Debug)] pub struct NextLevelScreen { is_next_pressed: bool, + screen_load_time: DateTime, + attempt_time: String, + best_time: String, } impl NextLevelScreen { @@ -31,6 +34,9 @@ impl NextLevelScreen { pub fn new() -> Self { Self { is_next_pressed: false, + screen_load_time: Utc::now(), + attempt_time: String::new(), + best_time: String::new(), } } } @@ -43,6 +49,7 @@ impl Action for NextLevelScreen { fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> { debug!("Running NextLevelScreen for the first time"); + self.screen_load_time = Utc::now(); if let Err(e) = context.discord_rpc_send.send(Some( ActivityBuilder::default().details("accepting fate").assets( @@ -63,6 +70,22 @@ impl Action for NextLevelScreen { trace!("execute() called on NextLevelScreen"); self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config); + let attempt_elapsed = self.screen_load_time - context.level_start_time; + self.attempt_time = format!( + "{:02}:{:02}", + attempt_elapsed.num_minutes(), + attempt_elapsed.num_seconds() % 60 + ); + let best_time = context + .player_progress + .get_level_best_time(context.current_level) + .unwrap_or(attempt_elapsed); + self.best_time = format!( + "{:02}:{:02}", + best_time.num_minutes(), + best_time.num_seconds() % 60 + ); + if self.is_next_pressed { Ok(ActionFlag::SwitchState(Scenes::InGameScene)) } else { @@ -114,7 +137,14 @@ impl ScreenSpaceRender for NextLevelScreen { //Time raylib.draw_rgb_split_text( Vector2::new(80.0, screen_size.y / 2.0 - 40.0), - "YOUR TIME: ", + &format!("YOUR TIME: {}", self.attempt_time), + 20, + false, + Color::WHITE, + ); + raylib.draw_rgb_split_text( + Vector2::new(80.0, screen_size.y / 2.0 - 20.0), + &format!("BEST TIME: {}", self.best_time), 20, false, Color::WHITE,