From 4398620ac832c4c77160e217f904ec9bf96600fc Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Fri, 17 Jan 2025 14:27:02 +0200 Subject: [PATCH 1/2] fix: scroll wheel sensitivity in WASM --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/camera.rs | 4 ++-- src/constants.rs | 5 +++++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcfcd1a..0e769ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3157,7 +3157,7 @@ dependencies = [ [[package]] name = "maze-ascension" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "bevy", diff --git a/Cargo.toml b/Cargo.toml index 3a10a92..196dd9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "maze-ascension" authors = ["Kristofers Solo "] -version = "1.1.0" +version = "1.1.1" edition = "2021" [dependencies] diff --git a/src/camera.rs b/src/camera.rs index ec8362c..5a21655 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,6 +1,6 @@ use bevy::{input::mouse::MouseWheel, prelude::*}; -use crate::constants::{BASE_ZOOM_SPEED, MAX_ZOOM, MIN_ZOOM}; +use crate::constants::{BASE_ZOOM_SPEED, MAX_ZOOM, MIN_ZOOM, SCROLL_MODIFIER}; pub(super) fn plugin(app: &mut App) { app.add_systems(Update, camera_zoom); @@ -53,7 +53,7 @@ fn camera_zoom( } for ev in scrool_evr.read() { - zoom_delta += ev.y * adjusted_zoom_speed; + zoom_delta += ev.y * adjusted_zoom_speed * SCROLL_MODIFIER; } if zoom_delta != 0.0 { diff --git a/src/constants.rs b/src/constants.rs index 771ec7c..7a7c8e0 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -14,6 +14,11 @@ pub const MIN_TIME_MULTIPLIER: f32 = 0.1; // Minimum score multiplier for time pub const TIME_REFERENCE_SECONDS: f32 = 60.0; // Reference time for score calculation // Constants for camera control + pub const BASE_ZOOM_SPEED: f32 = 10.0; +#[cfg(not(target_family = "wasm"))] +pub const SCROLL_MODIFIER: f32 = 1.; +#[cfg(target_family = "wasm")] +pub const SCROLL_MODIFIER: f32 = 0.01; pub const MIN_ZOOM: f32 = 50.0; pub const MAX_ZOOM: f32 = 2500.0; From 099d1633259130410fc8578a0b2ecd9a87834040 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Fri, 17 Jan 2025 15:16:40 +0200 Subject: [PATCH 2/2] refactor(score): change scoring algorithm --- src/constants.rs | 12 +-- src/stats/systems/mod.rs | 6 +- src/stats/systems/score.rs | 154 +++++++++++++++++++++++++++++++++++-- 3 files changed, 159 insertions(+), 13 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 7a7c8e0..1d838f4 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -5,13 +5,15 @@ pub const MOVEMENT_COOLDOWN: f32 = 1.0; // one second cooldown pub const TITLE: &str = "Maze Ascension: The Labyrinth of Echoes"; // Base score constants -pub const BASE_FLOOR_SCORE: usize = 1000; -pub const BASE_TIME_SCORE: usize = 100; +pub const BASE_FLOOR_SCORE: usize = 100; // Floor progression constants -pub const FLOOR_DIFFICULTY_MULTIPLIER: f32 = 1.2; // Higher floors are exponentially harder -pub const MIN_TIME_MULTIPLIER: f32 = 0.1; // Minimum score multiplier for time -pub const TIME_REFERENCE_SECONDS: f32 = 60.0; // Reference time for score calculation +pub const FLOOR_PROGRESSION_MULTIPLIER: f32 = 1.2; +pub const MIN_TIME_MULTIPLIER: f32 = 0.2; // Minimum score multiplier for time +pub const TIME_BONUS_MULTIPLIER: f32 = 1.5; +// Time scaling constants +pub const BASE_PERFECT_TIME: f32 = 10.0; // Base time for floor 1 +pub const TIME_INCREASE_FACTOR: f32 = 0.15; // Each floor adds 15% more time // Constants for camera control diff --git a/src/stats/systems/mod.rs b/src/stats/systems/mod.rs index 62e0e8e..0ee4eaa 100644 --- a/src/stats/systems/mod.rs +++ b/src/stats/systems/mod.rs @@ -20,7 +20,11 @@ pub(super) fn plugin(app: &mut App) { .add_systems( Update, ( - (update_score, update_score_display).chain(), + ( + update_score.before(update_floor_timer), + update_score_display, + ) + .chain(), (update_floor_timer, update_floor_timer_display).chain(), (update_total_timer, update_total_timer_display).chain(), update_floor_display, diff --git a/src/stats/systems/score.rs b/src/stats/systems/score.rs index d0a98b4..9559902 100644 --- a/src/stats/systems/score.rs +++ b/src/stats/systems/score.rs @@ -2,7 +2,8 @@ use bevy::prelude::*; use crate::{ constants::{ - BASE_FLOOR_SCORE, FLOOR_DIFFICULTY_MULTIPLIER, MIN_TIME_MULTIPLIER, TIME_REFERENCE_SECONDS, + BASE_FLOOR_SCORE, BASE_PERFECT_TIME, FLOOR_PROGRESSION_MULTIPLIER, MIN_TIME_MULTIPLIER, + TIME_BONUS_MULTIPLIER, TIME_INCREASE_FACTOR, }, floor::resources::HighestFloor, stats::{ @@ -20,7 +21,7 @@ pub fn update_score( return; } - score.0 = calculate_score( + score.0 += calculate_score( hightes_floor.0.saturating_sub(1), floor_timer.elapsed_secs(), ); @@ -38,13 +39,152 @@ pub fn update_score_display( } fn calculate_score(floor_number: u8, completion_time: f32) -> usize { - // Calculate base floor score with exponential scaling - let floor_multiplier = (floor_number as f32).powf(FLOOR_DIFFICULTY_MULTIPLIER); + let perfect_time = calculate_perfect_time(floor_number); + + // Floor progression using exponential scaling for better high-floor rewards + let floor_multiplier = (1.0 + floor_number as f32).powf(FLOOR_PROGRESSION_MULTIPLIER); let base_score = BASE_FLOOR_SCORE as f32 * floor_multiplier; - // Calculate time multiplier (decreases as time increases) - let time_factor = 1. / (1. + (completion_time / TIME_REFERENCE_SECONDS)); - let time_multiplier = time_factor.max(MIN_TIME_MULTIPLIER); + // Time bonus calculation + // Perfect time or better gets maximum bonus + // Longer times get diminishing returns but never below minimum + let time_multiplier = if completion_time <= perfect_time { + // Bonus for being faster than perfect time + let speed_ratio = perfect_time / completion_time; + speed_ratio * TIME_BONUS_MULTIPLIER + } else { + // Penalty for being slower than perfect time, with smooth degradation + let overtime_ratio = completion_time / perfect_time; + let time_factor = 1.0 / overtime_ratio; + time_factor.max(MIN_TIME_MULTIPLIER) * TIME_BONUS_MULTIPLIER + }; + + dbg!(base_score * time_multiplier); (base_score * time_multiplier) as usize } + +/// Perfect time increases with floor number +fn calculate_perfect_time(floor_number: u8) -> f32 { + BASE_PERFECT_TIME * (floor_number as f32 - 1.).mul_add(TIME_INCREASE_FACTOR, 1.) +} + +#[cfg(test)] +mod tests { + use claims::*; + use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; + use rstest::*; + + use super::*; + + #[fixture] + fn floors() -> Vec { + (1..=100).collect() + } + + #[fixture] + fn times() -> Vec { + vec![ + BASE_PERFECT_TIME * 0.5, // Much faster than perfect + BASE_PERFECT_TIME * 0.8, // Faster than perfect + BASE_PERFECT_TIME, // Perfect time + BASE_PERFECT_TIME * 1.5, // Slower than perfect + BASE_PERFECT_TIME * 2.0, // Much slower + ] + } + + #[rstest] + #[case(1, BASE_PERFECT_TIME)] + #[case(2, BASE_PERFECT_TIME * (1.0 + TIME_INCREASE_FACTOR))] + #[case(5, BASE_PERFECT_TIME * 4.0f32.mul_add(TIME_INCREASE_FACTOR, 1.))] + fn specific_perfect_times(#[case] floor: u8, #[case] expected_time: f32) { + let calculated_time = calculate_perfect_time(floor); + assert_le!( + (calculated_time - expected_time).abs(), + 0.001, + "Perfect time calculation mismatch" + ); + } + + #[rstest] + fn score_progression(floors: Vec, times: Vec) { + let floor_scores = floors + .par_iter() + .map(|floor| { + let scores = times + .par_iter() + .map(|&time| (*floor, time, calculate_score(*floor, time))) + .collect::>(); + (*floor, scores) + }) + .collect::>(); + + for (floor, scores) in floor_scores { + scores.windows(2).for_each(|window| { + let (_, time1, score1) = window[0]; + let (_, time2, score2) = window[1]; + + if time1 < time2 { + assert_gt!( + score1, + score2, + "Floor {}: Faster time ({}) should give higher score than slower time ({})", + floor, + time1, + time2 + ); + } + }) + } + } + + #[rstest] + fn perfect_time_progression(floors: Vec) { + let perfect_scores = floors + .par_iter() + .map(|&floor| { + let perfect_time = calculate_perfect_time(floor); + (floor, calculate_score(floor, perfect_time)) + }) + .collect::>(); + + perfect_scores.windows(2).for_each(|window| { + let (floor1, score1) = window[0]; + let (floor2, score2) = window[1]; + assert_gt!( + score2, + score1, + "Floor {} perfect score ({}) should be higher than floor {} perfect score ({})", + floor2, + score2, + floor1, + score1 + ); + }) + } + + #[rstest] + fn minimum_score_guarantee(floors: Vec) { + let very_slow_time = BASE_PERFECT_TIME * 10.0; + + // Test minimum scores in parallel + let min_scores = floors + .par_iter() + .map(|&floor| calculate_score(floor, very_slow_time)) + .collect::>(); + + // Verify minimum scores + min_scores.windows(2).for_each(|window| { + assert_gt!( + window[1], + window[0], + "Higher floor should give better minimum score" + ); + }); + + // Verify all scores are above zero + min_scores.iter().for_each(|&score| { + assert_gt!(score, 0, "Score should never be zero"); + }); + } +}