mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2025-10-21 19:20:34 +00:00
Merge pull request #39 from kristoferssolo/fix/web-zoom
This commit is contained in:
commit
c8e968e76e
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3157,7 +3157,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "maze-ascension"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bevy",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "maze-ascension"
|
||||
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -5,15 +5,22 @@ 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
|
||||
|
||||
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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<u8> {
|
||||
(1..=100).collect()
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn times() -> Vec<f32> {
|
||||
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<u8>, times: Vec<f32>) {
|
||||
let floor_scores = floors
|
||||
.par_iter()
|
||||
.map(|floor| {
|
||||
let scores = times
|
||||
.par_iter()
|
||||
.map(|&time| (*floor, time, calculate_score(*floor, time)))
|
||||
.collect::<Vec<_>>();
|
||||
(*floor, scores)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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<u8>) {
|
||||
let perfect_scores = floors
|
||||
.par_iter()
|
||||
.map(|&floor| {
|
||||
let perfect_time = calculate_perfect_time(floor);
|
||||
(floor, calculate_score(floor, perfect_time))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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<u8>) {
|
||||
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::<Vec<_>>();
|
||||
|
||||
// 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");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user