From f68c68f167f152736c23224c675d697bec63f9af Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Sat, 4 Jan 2025 20:10:16 +0200 Subject: [PATCH 1/3] feat(dev): add floor display --- Cargo.lock | 51 +++++-------------------------- Cargo.toml | 2 +- justfile | 2 +- src/constants.rs | 1 + src/dev_tools/ui/maze_controls.rs | 10 ++++++ src/floor/systems/clear_events.rs | 1 + 6 files changed, 21 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80fa240..0c917b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2299,21 +2299,6 @@ dependencies = [ "libc", ] -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.31" @@ -2321,7 +2306,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -2330,17 +2314,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.31" @@ -2371,12 +2344,6 @@ dependencies = [ "syn", ] -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - [[package]] name = "futures-task" version = "0.3.31" @@ -2395,13 +2362,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -2675,9 +2638,9 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hexlab" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd7c21f4e2c11d40473d1ae673905f4deae3b12104fa6d70eeef9ef385aceb6" +checksum = "7d2fbc6c41965686841aa5ea0e1af448730d0902274e49251c7d1fb7c78fffb9" dependencies = [ "bevy", "bevy_reflect", @@ -4168,21 +4131,21 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rstest" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" +checksum = "03e905296805ab93e13c1ec3a03f4b6c4f35e9498a3d5fa96dc626d22c03cd89" dependencies = [ - "futures", "futures-timer", + "futures-util", "rstest_macros", "rustc_version", ] [[package]] name = "rstest_macros" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" +checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b" dependencies = [ "cfg-if", "glob", diff --git a/Cargo.toml b/Cargo.toml index 51fdb63..5ca5609 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ anyhow = "1" strum = { version = "0.26", features = ["derive"] } [dev-dependencies] -rstest = "0.23" +rstest = "0.24" rstest_reuse = "0.7" test-log = { version = "0.2.16", default-features = false, features = [ "trace", diff --git a/justfile b/justfile index 2d1d43f..1af2c54 100644 --- a/justfile +++ b/justfile @@ -4,7 +4,7 @@ default: # Run native dev native-dev: - RUST_BACKTRACE=full cargo run + RUSTC_WRAPPER=sccache RUST_BACKTRACE=full cargo run # Run native release native-release: diff --git a/src/constants.rs b/src/constants.rs index 5e5fb5b..d603828 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,3 +1,4 @@ pub const MOVEMENT_THRESHOLD: f32 = 0.01; pub const WALL_OVERLAP_MODIFIER: f32 = 1.25; pub const FLOOR_Y_OFFSET: u8 = 100; +pub const MOVEMENT_COOLDOWN: f32 = 1.0; // one second cooldown diff --git a/src/dev_tools/ui/maze_controls.rs b/src/dev_tools/ui/maze_controls.rs index 6c94755..6f68b9a 100644 --- a/src/dev_tools/ui/maze_controls.rs +++ b/src/dev_tools/ui/maze_controls.rs @@ -40,6 +40,16 @@ pub fn maze_controls_ui(world: &mut World) { if let Some(mut global_config) = world.get_resource_mut::() { ui.heading("Maze Configuration"); + // Display current floor as non-editable text + ui.horizontal(|ui| { + ui.label("Current floor:"); + let mut floor_text = floor_value.to_string(); + ui.add_enabled( + false, + TextEdit::singleline(&mut floor_text).desired_width(10.), + ); + }); + changed |= add_seed_control(ui, &mut maze_config.seed); changed |= add_drag_value_control(ui, "Radius:", &mut maze_config.radius, 1.0, 1..=100); changed |= diff --git a/src/floor/systems/clear_events.rs b/src/floor/systems/clear_events.rs index e69de29..8b13789 100644 --- a/src/floor/systems/clear_events.rs +++ b/src/floor/systems/clear_events.rs @@ -0,0 +1 @@ + From c4dcedd7239f8367dc5ce5d23f1ada843b6394bb Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Sat, 4 Jan 2025 22:34:08 +0200 Subject: [PATCH 2/3] fix(floor): descend --- src/floor/components.rs | 4 -- src/floor/events.rs | 4 +- src/floor/systems/clear_events.rs | 1 - src/floor/systems/mod.rs | 1 - src/floor/systems/movement.rs | 47 +++++++++++++---------- src/floor/systems/spawn.rs | 16 ++++---- src/maze/triggers/spawn.rs | 3 +- src/player/systems/vertical_transition.rs | 6 +-- 8 files changed, 41 insertions(+), 41 deletions(-) delete mode 100644 src/floor/systems/clear_events.rs diff --git a/src/floor/components.rs b/src/floor/components.rs index 65ab700..d0b0d7a 100644 --- a/src/floor/components.rs +++ b/src/floor/components.rs @@ -8,10 +8,6 @@ pub struct Floor(pub u8); #[reflect(Component)] pub struct CurrentFloor; -#[derive(Debug, Reflect, Component)] -#[reflect(Component)] -pub struct NextFloor; - #[derive(Debug, Reflect, Component, Deref, DerefMut)] #[reflect(Component)] pub struct FloorYTarget(pub f32); diff --git a/src/floor/events.rs b/src/floor/events.rs index 11b79ab..f83c8ff 100644 --- a/src/floor/events.rs +++ b/src/floor/events.rs @@ -38,8 +38,8 @@ impl From for f32 { impl From<&TransitionFloor> for f32 { fn from(value: &TransitionFloor) -> Self { match value { - TransitionFloor::Ascend => -1., - TransitionFloor::Descend => 1., + TransitionFloor::Ascend => -1., // When ascending, floors move down + TransitionFloor::Descend => 1., // When descending, floors move up } } } diff --git a/src/floor/systems/clear_events.rs b/src/floor/systems/clear_events.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/floor/systems/clear_events.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/floor/systems/mod.rs b/src/floor/systems/mod.rs index 2d424d4..8428a20 100644 --- a/src/floor/systems/mod.rs +++ b/src/floor/systems/mod.rs @@ -1,4 +1,3 @@ -mod clear_events; mod despawn; mod movement; mod spawn; diff --git a/src/floor/systems/movement.rs b/src/floor/systems/movement.rs index ec8ce20..bfe1e01 100644 --- a/src/floor/systems/movement.rs +++ b/src/floor/systems/movement.rs @@ -1,7 +1,7 @@ use crate::{ constants::{FLOOR_Y_OFFSET, MOVEMENT_THRESHOLD}, floor::{ - components::{CurrentFloor, FloorYTarget, NextFloor}, + components::{CurrentFloor, Floor, FloorYTarget}, events::TransitionFloor, }, maze::components::HexMaze, @@ -35,45 +35,52 @@ pub fn move_floors( pub fn handle_floor_transition_events( mut commands: Commands, - mut maze_query: Query<(Entity, &Transform, Option<&FloorYTarget>), With>, - current_query: Query>, - next_query: Query>, + mut maze_query: Query<(Entity, &Transform, &Floor, Option<&FloorYTarget>), With>, + current_query: Query<(Entity, &Floor), With>, mut event_reader: EventReader, ) { let is_moving = maze_query .iter() - .any(|(_, _, movement_state)| movement_state.is_some()); + .any(|(_, _, _, movement_state)| movement_state.is_some()); if is_moving { return; } for event in event_reader.read() { + dbg!(&event); + let Some((current_entity, current_floor)) = current_query.get_single().ok() else { + continue; + }; + + let target_floor_num = event.next_floor_num(current_floor); + + let target_entity = maze_query + .iter() + .find(|(_, _, floor, _)| floor.0 == target_floor_num) + .map(|(entity, ..)| entity); + + let Some(target_entity) = target_entity else { + continue; + }; + let direction = event.into(); - let Some(current_entity) = current_query.get_single().ok() else { - continue; - }; - let Some(next_entity) = next_query.get_single().ok() else { - continue; - }; - - for (entity, transforms, movement_state) in maze_query.iter_mut() { + for (entity, transforms, _, movement_state) in maze_query.iter_mut() { let target_y = (FLOOR_Y_OFFSET as f32).mul_add(direction, transforms.translation.y); + dbg!(movement_state, target_y); if movement_state.is_none() { commands.entity(entity).insert(FloorYTarget(target_y)); } } - update_current_next_floor(&mut commands, current_entity, next_entity); + update_current_next_floor(&mut commands, current_entity, target_entity); break; } + event_reader.clear(); } -fn update_current_next_floor(commands: &mut Commands, current_entity: Entity, next_entity: Entity) { - commands.entity(current_entity).remove::(); - commands - .entity(next_entity) - .remove::() - .insert(CurrentFloor); +fn update_current_next_floor(commands: &mut Commands, current: Entity, target: Entity) { + commands.entity(current).remove::(); + commands.entity(target).insert(CurrentFloor); } diff --git a/src/floor/systems/spawn.rs b/src/floor/systems/spawn.rs index e2a143b..74759f1 100644 --- a/src/floor/systems/spawn.rs +++ b/src/floor/systems/spawn.rs @@ -14,22 +14,24 @@ pub(super) fn spawn_floor( mut event_reader: EventReader, mut highest_floor: ResMut, ) { - let Ok(floor) = query.get_single() else { + let Ok(current_floor) = query.get_single() else { return; }; for event in event_reader.read() { - let floor = event.next_floor_num(floor); - - if floor == 1 && *event == TransitionFloor::Descend { + if current_floor.0 == 0 && *event == TransitionFloor::Descend { warn!("Cannot descend below floor 1"); return; } - highest_floor.0 = highest_floor.0.max(floor); + let next_floor = event.next_floor_num(current_floor); + highest_floor.0 = highest_floor.0.max(next_floor); - info!("Creating level for floor {}", floor); + info!("Creating level for floor {}", next_floor); - commands.trigger(SpawnMaze { floor, ..default() }); + commands.trigger(SpawnMaze { + floor: next_floor, + ..default() + }); } } diff --git a/src/maze/triggers/spawn.rs b/src/maze/triggers/spawn.rs index afd3cb1..9ed2179 100644 --- a/src/maze/triggers/spawn.rs +++ b/src/maze/triggers/spawn.rs @@ -1,7 +1,7 @@ use super::common::generate_maze; use crate::{ constants::FLOOR_Y_OFFSET, - floor::components::{CurrentFloor, Floor, NextFloor}, + floor::components::{CurrentFloor, Floor}, maze::{ assets::MazeAssets, components::{HexMaze, MazeConfig, Tile, Wall}, @@ -56,7 +56,6 @@ pub(super) fn spawn_maze( Visibility::Visible, )) .insert_if(CurrentFloor, || *floor == 1) - .insert_if(NextFloor, || *floor != 1) .id(); let assets = MazeAssets::new(&mut meshes, &mut materials, &global_config); diff --git a/src/player/systems/vertical_transition.rs b/src/player/systems/vertical_transition.rs index f56cc07..3619443 100644 --- a/src/player/systems/vertical_transition.rs +++ b/src/player/systems/vertical_transition.rs @@ -22,16 +22,14 @@ pub fn handle_floor_transition( for current_hex in player_query.iter() { // Check for ascending if current_hex.0 == config.end_pos { - dbg!("Ascending"); + info!("Ascending"); event_writer.send(TransitionFloor::Ascend); - return; } // Check for descending if current_hex.0 == config.start_pos && floor.0 != 1 { - dbg!("Descending"); + info!("Descending"); event_writer.send(TransitionFloor::Descend); - return; } } } From c587371544c92c81a22623edab547659484310d7 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Sat, 4 Jan 2025 23:24:47 +0200 Subject: [PATCH 3/3] 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); + } }