mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-15 06:12:34 -06:00
refactor: add ticks to DeltaTime, rewrite Blinking system for tick-based calculations with absolute calculations, rewrite Blinking/Direction tests
This commit is contained in:
@@ -1,40 +1,205 @@
|
||||
use pacman::texture::blinking::BlinkingTexture;
|
||||
use speculoos::prelude::*;
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
||||
let mut texture = BlinkingTexture::new(tile, 30); // 30 ticks = 0.5 seconds at 60 FPS
|
||||
|
||||
assert!(texture.is_on());
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
|
||||
texture.tick(0.5);
|
||||
assert!(!texture.is_on());
|
||||
texture.tick(30);
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
|
||||
texture.tick(0.5);
|
||||
assert!(texture.is_on());
|
||||
texture.tick(30);
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
|
||||
texture.tick(0.5);
|
||||
assert!(!texture.is_on());
|
||||
texture.tick(30);
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_partial_duration() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
||||
let mut texture = BlinkingTexture::new(tile, 30); // 30 ticks
|
||||
|
||||
texture.tick(0.625);
|
||||
assert!(!texture.is_on());
|
||||
assert_eq!(texture.time_bank(), 0.125);
|
||||
texture.tick(37); // 37 ticks, should complete 1 interval (30 ticks) with 7 remaining
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_negative_time() {
|
||||
fn test_blinking_texture_zero_interval() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
||||
let mut texture = BlinkingTexture::new(tile, 0);
|
||||
|
||||
texture.tick(-0.1);
|
||||
assert!(texture.is_on());
|
||||
assert_eq!(texture.time_bank(), -0.1);
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
assert_that(&texture.interval_ticks()).is_equal_to(0);
|
||||
|
||||
// With zero interval, any positive ticks should toggle
|
||||
texture.tick(1);
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(1);
|
||||
|
||||
texture.tick(1);
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_multiple_cycles() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 15); // 15 ticks
|
||||
|
||||
// Test multiple complete cycles
|
||||
for i in 0..10 {
|
||||
let expected_state = i % 2 == 0;
|
||||
assert_that(&texture.is_on()).is_equal_to(expected_state);
|
||||
texture.tick(15);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_accumulated_ticks() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 60); // 60 ticks = 1 second at 60 FPS
|
||||
|
||||
// Test that ticks accumulate correctly
|
||||
texture.tick(18); // 18 ticks
|
||||
assert_that(&texture.tick_timer()).is_equal_to(18);
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
|
||||
texture.tick(18); // 36 total ticks
|
||||
assert_that(&texture.tick_timer()).is_equal_to(36);
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
|
||||
texture.tick(18); // 54 total ticks
|
||||
assert_that(&texture.tick_timer()).is_equal_to(54);
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
|
||||
texture.tick(12); // 66 total ticks, should complete 1 interval (60 ticks) with 6 remaining
|
||||
assert_that(&texture.tick_timer()).is_equal_to(6);
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_large_tick_step() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 30); // 30 ticks
|
||||
|
||||
// Test with a tick step larger than the interval
|
||||
texture.tick(90); // 90 ticks, should complete 3 intervals (90/30 = 3)
|
||||
assert_that(&texture.is_on()).is_false(); // 3 toggles: true -> false -> true -> false
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0); // 90 % 30 = 0
|
||||
|
||||
texture.tick(60); // 60 ticks, should complete 2 intervals (60/30 = 2)
|
||||
assert_that(&texture.is_on()).is_false(); // 2 more toggles: false -> true -> false (no change)
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0); // 60 % 30 = 0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_small_steps() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 6); // 6 ticks
|
||||
|
||||
// Test with small tick steps
|
||||
for _ in 0..6 {
|
||||
texture.tick(1);
|
||||
}
|
||||
|
||||
// After 6 ticks, should have completed one cycle
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0);
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_clone() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture1 = BlinkingTexture::new(tile, 30);
|
||||
let texture2 = texture1.clone();
|
||||
|
||||
// Both should have the same initial state
|
||||
assert_that(&texture1.is_on()).is_equal_to(texture2.is_on());
|
||||
assert_that(&texture1.tick_timer()).is_equal_to(texture2.tick_timer());
|
||||
assert_that(&texture1.interval_ticks()).is_equal_to(texture2.interval_ticks());
|
||||
|
||||
// Modifying one shouldn't affect the other
|
||||
texture1.tick(18);
|
||||
assert_that(&texture1.tick_timer()).is_not_equal_to(texture2.tick_timer());
|
||||
assert_that(&texture2.tick_timer()).is_equal_to(0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_edge_cases() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 60); // 60 ticks
|
||||
|
||||
// Test exactly at the interval
|
||||
texture.tick(60);
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0);
|
||||
|
||||
// Test just under the interval
|
||||
texture.tick(59);
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(59);
|
||||
|
||||
// Test just over the interval
|
||||
texture.tick(2);
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_very_small_interval() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 1); // 1 tick
|
||||
|
||||
// Test with very small interval
|
||||
texture.tick(1);
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0);
|
||||
|
||||
texture.tick(1);
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_very_large_interval() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 60000); // 60000 ticks = 1000 seconds at 60 FPS
|
||||
|
||||
// Test with very large interval
|
||||
texture.tick(30000);
|
||||
assert_that(&texture.is_on()).is_true();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(30000);
|
||||
|
||||
texture.tick(30000);
|
||||
assert_that(&texture.is_on()).is_false();
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blinking_texture_multiple_toggles_no_op() {
|
||||
let tile = common::mock_atlas_tile(1);
|
||||
let mut texture = BlinkingTexture::new(tile, 10); // 10 ticks
|
||||
|
||||
// Test that multiple toggles work correctly (key feature)
|
||||
// 2x ticks than the interval should do nothing at all, because toggling twice is a no-op
|
||||
texture.tick(20); // 20 ticks = 2 complete intervals
|
||||
assert_that(&texture.is_on()).is_true(); // Should still be true (2 toggles = no-op)
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0);
|
||||
|
||||
// 3x ticks should toggle once (3 toggles = 1 toggle)
|
||||
texture.tick(30); // 30 ticks = 3 complete intervals
|
||||
assert_that(&texture.is_on()).is_false(); // Should be false (3 toggles = 1 toggle)
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0);
|
||||
|
||||
// 4x ticks should do nothing (4 toggles = no-op)
|
||||
texture.tick(40); // 40 ticks = 4 complete intervals
|
||||
assert_that(&texture.is_on()).is_false(); // Should still be false (4 toggles = no-op)
|
||||
assert_that(&texture.tick_timer()).is_equal_to(0);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,10 @@ pub fn create_test_world() -> World {
|
||||
world.insert_resource(AudioState::default());
|
||||
world.insert_resource(GlobalState { exit: false });
|
||||
world.insert_resource(DebugState::default());
|
||||
world.insert_resource(DeltaTime(1.0 / 60.0)); // 60 FPS
|
||||
world.insert_resource(DeltaTime {
|
||||
seconds: 1.0 / 60.0,
|
||||
ticks: 1,
|
||||
}); // 60 FPS
|
||||
world.insert_resource(create_test_map());
|
||||
|
||||
world
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use glam::I8Vec2;
|
||||
use pacman::map::direction::*;
|
||||
use speculoos::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_direction_opposite() {
|
||||
@@ -11,21 +11,47 @@ fn test_direction_opposite() {
|
||||
];
|
||||
|
||||
for (dir, expected) in test_cases {
|
||||
assert_eq!(dir.opposite(), expected);
|
||||
assert_that(&dir.opposite()).is_equal_to(expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direction_as_ivec2() {
|
||||
let test_cases = [
|
||||
(Direction::Up, -I8Vec2::Y),
|
||||
(Direction::Down, I8Vec2::Y),
|
||||
(Direction::Left, -I8Vec2::X),
|
||||
(Direction::Right, I8Vec2::X),
|
||||
];
|
||||
|
||||
for (dir, expected) in test_cases {
|
||||
assert_eq!(dir.as_ivec2(), expected);
|
||||
assert_eq!(I8Vec2::from(dir), expected);
|
||||
fn test_direction_opposite_symmetry() {
|
||||
// Test that opposite() is symmetric: opposite(opposite(d)) == d
|
||||
for &dir in &Direction::DIRECTIONS {
|
||||
assert_that(&dir.opposite().opposite()).is_equal_to(dir);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direction_opposite_exhaustive() {
|
||||
// Test that every direction has a unique opposite
|
||||
let mut opposites = std::collections::HashSet::new();
|
||||
for &dir in &Direction::DIRECTIONS {
|
||||
let opposite = dir.opposite();
|
||||
assert_that(&opposites.insert(opposite)).is_true();
|
||||
}
|
||||
assert_that(&opposites).has_length(4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direction_as_usize_exhaustive() {
|
||||
// Test that as_usize() returns unique values for all directions
|
||||
let mut usizes = std::collections::HashSet::new();
|
||||
for &dir in &Direction::DIRECTIONS {
|
||||
let usize_val = dir.as_usize();
|
||||
assert_that(&usizes.insert(usize_val)).is_true();
|
||||
}
|
||||
assert_that(&usizes).has_length(4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direction_as_ivec2_exhaustive() {
|
||||
// Test that as_ivec2() returns unique values for all directions
|
||||
let mut ivec2s = std::collections::HashSet::new();
|
||||
for &dir in &Direction::DIRECTIONS {
|
||||
let ivec2_val = dir.as_ivec2();
|
||||
assert_that(&ivec2s.insert(ivec2_val)).is_true();
|
||||
}
|
||||
assert_that(&ivec2s).has_length(4);
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ fn test_player_movement_system_buffered_direction_expires() {
|
||||
});
|
||||
|
||||
// Set delta time to expire the buffered direction
|
||||
world.insert_resource(DeltaTime(0.02));
|
||||
world.insert_resource(DeltaTime::from_seconds(0.02));
|
||||
|
||||
// Run the system
|
||||
world
|
||||
@@ -410,7 +410,7 @@ fn test_buffered_direction_timing() {
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Run movement system multiple times with small delta times
|
||||
world.insert_resource(DeltaTime(0.1)); // 0.1 seconds
|
||||
world.insert_resource(DeltaTime::from_seconds(0.1)); // 0.1 seconds
|
||||
|
||||
// First run - buffered direction should still be active
|
||||
world
|
||||
@@ -428,7 +428,7 @@ fn test_buffered_direction_timing() {
|
||||
}
|
||||
|
||||
// Run again to fully expire the buffered direction
|
||||
world.insert_resource(DeltaTime(0.2)); // Total 0.3 seconds, should expire
|
||||
world.insert_resource(DeltaTime::from_seconds(0.2)); // Total 0.3 seconds, should expire
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
Reference in New Issue
Block a user