From c587371544c92c81a22623edab547659484310d7 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Sat, 4 Jan 2025 23:24:47 +0200 Subject: [PATCH] fix(floor): #8 --- justfile | 4 + src/maze/components.rs | 206 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 204 insertions(+), 6 deletions(-) diff --git a/justfile b/justfile index 1af2c54..67f4ddd 100644 --- a/justfile +++ b/justfile @@ -17,3 +17,7 @@ web-dev: # Run web release web-release: trunk serve --release --no-default-features + +# Run tests +test: + RUSTC_WRAPPER=sccache RUST_BACKTRACE=full cargo nextest run --no-default-features --all-targets diff --git a/src/maze/components.rs b/src/maze/components.rs index 8cc9edc..d5b6ad0 100644 --- a/src/maze/components.rs +++ b/src/maze/components.rs @@ -39,8 +39,18 @@ impl MazeConfig { let seed = seed.unwrap_or_else(|| thread_rng().gen()); let mut rng = StdRng::seed_from_u64(seed); - let start_pos = generate_pos(radius, &mut rng); - let end_pos = generate_pos(radius, &mut rng); + // Generate start and end positions ensuring they're different + let mut start_pos; + let mut end_pos; + + loop { + start_pos = generate_pos(radius, &mut rng); + end_pos = generate_pos(radius, &mut rng); + + if start_pos != end_pos { + break; + } + } info!( "Start pos: (q={}, r={}). End pos: (q={}, r={})", @@ -75,8 +85,192 @@ impl Default for MazeConfig { fn generate_pos(radius: u16, rng: &mut R) -> Hex { let radius = radius as i32; - Hex::new( - rng.gen_range(-radius..radius), - rng.gen_range(-radius..radius), - ) + loop { + let q = rng.gen_range(-radius..=radius); + let r = rng.gen_range(-radius..=radius); + + let s = -q - r; // Calculate third coordinate (axial coordinates: q + r + s = 0) + + // Check if the position is within the hexagonal radius + // Using the formula: max(abs(q), abs(r), abs(s)) <= radius + if q.abs().max(r.abs()).max(s.abs()) <= radius { + return Hex::new(q, r); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::*; + + fn is_within_radius(hex: Hex, radius: u16) -> bool { + let q = hex.x; + let r = hex.y; + let s = -q - r; + q.abs().max(r.abs()).max(s.abs()) <= radius as i32 + } + + #[fixture] + fn test_radius() -> Vec { + vec![1, 2, 5, 8] + } + + #[rstest] + #[case(1)] + #[case(2)] + #[case(5)] + #[case(8)] + fn maze_config_new(#[case] radius: u16) { + let orientation = HexOrientation::Flat; + let seed = Some(12345); + let global_config = GlobalMazeConfig::default(); + + let config = MazeConfig::new(radius, orientation, seed, &global_config); + + assert_eq!(config.radius, radius); + assert_eq!(config.seed, 12345); + assert_eq!(config.layout.orientation, orientation); + + assert!( + is_within_radius(config.start_pos, radius), + "Start pos {:?} outside radius {}", + config.start_pos, + radius + ); + assert!( + is_within_radius(config.end_pos, radius), + "End pos {:?} outside radius {}", + config.end_pos, + radius + ); + assert_ne!(config.start_pos, config.end_pos); + } + + #[rstest] + #[case(100)] + fn maze_config_default(#[case] iterations: u32) { + for _ in 0..iterations { + let config = MazeConfig::default(); + + assert_eq!(config.radius, 8); + assert_eq!(config.layout.orientation, HexOrientation::Flat); + assert!(is_within_radius(config.start_pos, 8)); + assert!(is_within_radius(config.end_pos, 8)); + assert_ne!(config.start_pos, config.end_pos); + } + } + + #[rstest] + fn maze_config_default_with_seeds() { + let test_seeds = [ + None, + Some(0), + Some(1), + Some(12345), + Some(u64::MAX), + Some(thread_rng().gen()), + ]; + + for seed in test_seeds { + let config = + MazeConfig::new(8, HexOrientation::Flat, seed, &GlobalMazeConfig::default()); + + assert_eq!(config.radius, 8); + assert_eq!(config.layout.orientation, HexOrientation::Flat); + assert!(is_within_radius(config.start_pos, 8)); + assert!(is_within_radius(config.end_pos, 8)); + assert_ne!(config.start_pos, config.end_pos); + } + } + + #[rstest] + #[case(1.0)] + #[case(2.0)] + #[case(5.0)] + fn maze_config_update(#[case] new_size: f32) { + let mut config = MazeConfig::default(); + let global_config = GlobalMazeConfig { + hex_size: new_size, + ..default() + }; + + config.update(&global_config); + + assert_eq!(config.layout.hex_size.x, new_size); + assert_eq!(config.layout.hex_size.y, new_size); + } + + #[rstest] + #[case(5, 1)] + #[case(5, 12345)] + #[case(8, 67890)] + fn generate_pos_with_seed(#[case] radius: u16, #[case] seed: u64) { + let mut rng = StdRng::seed_from_u64(seed); + + for _ in 0..10 { + let pos = generate_pos(radius, &mut rng); + assert!( + is_within_radius(pos, radius), + "Position {:?} outside radius {}", + pos, + radius + ); + } + } + + #[test] + fn different_seeds_different_positions() { + let config1 = MazeConfig::new( + 8, + HexOrientation::Flat, + Some(1), + &GlobalMazeConfig::default(), + ); + let config2 = MazeConfig::new( + 8, + HexOrientation::Flat, + Some(2), + &GlobalMazeConfig::default(), + ); + + assert_ne!(config1.start_pos, config2.start_pos); + assert_ne!(config1.end_pos, config2.end_pos); + } + + #[test] + fn same_seed_same_positions() { + let seed = Some(12345); + let config1 = MazeConfig::new(8, HexOrientation::Flat, seed, &GlobalMazeConfig::default()); + let config2 = MazeConfig::new(8, HexOrientation::Flat, seed, &GlobalMazeConfig::default()); + + assert_eq!(config1.start_pos, config2.start_pos); + assert_eq!(config1.end_pos, config2.end_pos); + } + + #[test] + fn orientation_pointy() { + let config = MazeConfig::new( + 8, + HexOrientation::Pointy, + None, + &GlobalMazeConfig::default(), + ); + assert_eq!(config.layout.orientation, HexOrientation::Pointy); + } + + #[test] + fn hex_size_zero() { + let config = MazeConfig::new( + 8, + HexOrientation::Flat, + None, + &GlobalMazeConfig { + hex_size: 0.0, + ..default() + }, + ); + assert_eq!(config.layout.hex_size.x, 0.0); + assert_eq!(config.layout.hex_size.y, 0.0); + } }