mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2025-10-21 19:20:34 +00:00
feat(maze): add Coordinates trait
This commit is contained in:
parent
0f4899319d
commit
e9f02e362a
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -1614,6 +1614,12 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "claims"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bba18ee93d577a8428902687bcc2b6b45a56b1981a1f6d779731c86cc4c5db18"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
@ -3135,6 +3141,7 @@ dependencies = [
|
||||
"bevy",
|
||||
"bevy-inspector-egui",
|
||||
"bevy_egui",
|
||||
"claims",
|
||||
"hexlab",
|
||||
"hexx",
|
||||
"log",
|
||||
|
||||
@ -26,6 +26,7 @@ anyhow = "1"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
claims = "0.8.0"
|
||||
rstest = "0.24"
|
||||
rstest_reuse = "0.7"
|
||||
test-log = { version = "0.2.16", default-features = false, features = [
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
//! Module defines the core components and configuration structures used
|
||||
//! for maze generation and rendering, including hexagonal maze layouts,
|
||||
//! tiles, walls, and maze configuration.
|
||||
use super::GlobalMazeConfig;
|
||||
use super::{coordinates::is_within_radius, GlobalMazeConfig};
|
||||
use crate::floor::components::Floor;
|
||||
|
||||
use bevy::prelude::*;
|
||||
@ -110,15 +110,16 @@ impl Default for MazeConfig {
|
||||
/// A valid Hex coordinate within the specified radius
|
||||
fn generate_pos<R: Rng>(radius: u16, rng: &mut R) -> Hex {
|
||||
let radius = radius as i32;
|
||||
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);
|
||||
loop {
|
||||
// Generate coordinates using cube coordinate bounds
|
||||
let q = rng.gen_range(-radius..=radius);
|
||||
let r = rng.gen_range((-radius).max(-q - radius)..=radius.min(-q + radius));
|
||||
|
||||
if let Ok(is_valid) = is_within_radius(radius, &(q, r)) {
|
||||
if is_valid {
|
||||
return Hex::new(q, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,20 +127,9 @@ fn generate_pos<R: Rng>(radius: u16, rng: &mut R) -> Hex {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use claims::*;
|
||||
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<u16> {
|
||||
vec![1, 2, 5, 8]
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(1)]
|
||||
#[case(2)]
|
||||
@ -156,18 +146,8 @@ mod tests {
|
||||
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_ok!(is_within_radius(radius, &config.start_pos),);
|
||||
assert_ok!(is_within_radius(radius, &config.end_pos));
|
||||
assert_ne!(config.start_pos, config.end_pos);
|
||||
}
|
||||
|
||||
@ -178,13 +158,13 @@ mod tests {
|
||||
let config = MazeConfig::default();
|
||||
let radius = config.radius;
|
||||
|
||||
assert!(is_within_radius(config.start_pos, radius));
|
||||
assert!(is_within_radius(config.end_pos, radius));
|
||||
assert_ok!(is_within_radius(radius, &config.start_pos));
|
||||
assert_ok!(is_within_radius(radius, &config.end_pos));
|
||||
assert_ne!(config.start_pos, config.end_pos);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test]
|
||||
fn maze_config_default_with_seeds() {
|
||||
let test_seeds = [
|
||||
None,
|
||||
@ -206,8 +186,8 @@ mod tests {
|
||||
|
||||
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_ok!(is_within_radius(8, &config.start_pos));
|
||||
assert_ok!(is_within_radius(8, &config.end_pos));
|
||||
assert_ne!(config.start_pos, config.end_pos);
|
||||
}
|
||||
}
|
||||
@ -238,12 +218,7 @@ mod tests {
|
||||
|
||||
for _ in 0..10 {
|
||||
let pos = generate_pos(radius, &mut rng);
|
||||
assert!(
|
||||
is_within_radius(pos, radius),
|
||||
"Position {:?} outside radius {}",
|
||||
pos,
|
||||
radius
|
||||
);
|
||||
assert_ok!(is_within_radius(radius, &pos),);
|
||||
}
|
||||
}
|
||||
|
||||
@ -317,4 +292,51 @@ mod tests {
|
||||
assert_eq!(config.layout.hex_size.x, 0.0);
|
||||
assert_eq!(config.layout.hex_size.y, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_generation() {
|
||||
let mut rng = thread_rng();
|
||||
let radius = 2;
|
||||
let hex = generate_pos(radius, &mut rng);
|
||||
|
||||
// Test that generated position is within radius
|
||||
assert_ok!(is_within_radius(radius as i32, &(hex.x, hex.y)));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(1)]
|
||||
#[case(2)]
|
||||
#[case(3)]
|
||||
#[case(6)]
|
||||
fn multiple_radii(#[case] radius: u16) {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
// Generate multiple points for each radius
|
||||
for _ in 0..100 {
|
||||
let hex = generate_pos(radius, &mut rng);
|
||||
assert_ok!(is_within_radius(radius, &hex));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_radius() {
|
||||
let mut rng = thread_rng();
|
||||
let hex = generate_pos(0, &mut rng);
|
||||
|
||||
// With radius 0, only (0,0) should be possible
|
||||
assert_eq!(hex.x, 0);
|
||||
assert_eq!(hex.y, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_radius() {
|
||||
let mut rng = thread_rng();
|
||||
let radius = 100;
|
||||
let iterations = 100;
|
||||
|
||||
for _ in 0..iterations {
|
||||
let hex = generate_pos(radius, &mut rng);
|
||||
assert_ok!(is_within_radius(radius, &hex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
139
src/maze/coordinates.rs
Normal file
139
src/maze/coordinates.rs
Normal file
@ -0,0 +1,139 @@
|
||||
use hexx::Hex;
|
||||
|
||||
use super::errors::RadiusError;
|
||||
|
||||
pub trait Coordinates {
|
||||
fn get_coords(&self) -> (i32, i32);
|
||||
}
|
||||
|
||||
impl Coordinates for (i32, i32) {
|
||||
fn get_coords(&self) -> (i32, i32) {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl Coordinates for Hex {
|
||||
fn get_coords(&self) -> (i32, i32) {
|
||||
(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_within_radius<R, C>(radius: R, coords: &C) -> Result<bool, RadiusError>
|
||||
where
|
||||
R: Into<i32>,
|
||||
C: Coordinates,
|
||||
{
|
||||
let radius = radius.into();
|
||||
|
||||
if radius < 0 {
|
||||
return Err(RadiusError::NegativeRadius(radius));
|
||||
}
|
||||
|
||||
let (q, r) = coords.get_coords();
|
||||
let s = -q - r; // Calculate third axial coordinate (q + r + s = 0)
|
||||
|
||||
Ok(q.abs().max(r.abs()).max(s.abs()) <= radius)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use claims::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
// Original test cases
|
||||
#[case(0, (0, 0), true)] // Center point
|
||||
#[case(1, (1, 0), true)] // Point at radius 1
|
||||
#[case(1, (2, 0), false)] // Point outside radius 1
|
||||
#[case(2, (2, 0), true)] // East
|
||||
#[case(2, (0, 2), true)] // Southeast
|
||||
#[case(2, (-2, 2), true)] // Southwest
|
||||
#[case(2, (-2, 0), true)] // West
|
||||
#[case(2, (0, -2), true)] // Northwest
|
||||
#[case(2, (2, -2), true)] // Northeast
|
||||
#[case(2, (3, 0), false)] // Just outside radius 2
|
||||
// Large radius test cases
|
||||
#[case(6, (6, 0), true)] // East at radius 6
|
||||
#[case(6, (0, 6), true)] // Southeast at radius 6
|
||||
#[case(6, (-6, 6), true)] // Southwest at radius 6
|
||||
#[case(6, (-6, 0), true)] // West at radius 6
|
||||
#[case(6, (0, -6), true)] // Northwest at radius 6
|
||||
#[case(6, (6, -6), true)] // Northeast at radius 6
|
||||
#[case(6, (7, 0), false)] // Just outside radius 6 east
|
||||
#[case(6, (4, 4), false)] // Outside radius 6 diagonal
|
||||
#[case(6, (5, 5), false)] // Outside radius 6 diagonal
|
||||
// Edge cases with large radius
|
||||
#[case(6, (6, -3), true)] // Complex position within radius 6
|
||||
#[case(6, (-3, 6), true)] // Complex position within radius 6
|
||||
#[case(6, (3, -6), true)] // Complex position within radius 6
|
||||
#[case(6, (7, -7), false)] // Outside radius 6 corner
|
||||
fn valid_radius_tuple(#[case] radius: i32, #[case] pos: (i32, i32), #[case] expected: bool) {
|
||||
let result = is_within_radius(radius, &pos);
|
||||
assert_ok_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
// Large radius test cases for Hex struct
|
||||
#[case(6, (6, 0), true)] // East at radius 6
|
||||
#[case(6, (0, 6), true)] // Southeast at radius 6
|
||||
#[case(6, (-6, 6), true)] // Southwest at radius 6
|
||||
#[case(6, (-6, 0), true)] // West at radius 6
|
||||
#[case(6, (0, -6), true)] // Northwest at radius 6
|
||||
#[case(6, (6, -6), true)] // Northeast at radius 6
|
||||
#[case(6, (4, 4), false)] // Outside radius 6 diagonal
|
||||
#[case(6, (5, 5), false)] // Outside radius 6 diagonal
|
||||
fn valid_radius_hex(#[case] radius: i32, #[case] pos: (i32, i32), #[case] expected: bool) {
|
||||
let hex = Hex::from(pos);
|
||||
let result = is_within_radius(radius, &hex);
|
||||
assert_ok_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(-1)]
|
||||
#[case(-2)]
|
||||
#[case(-5)]
|
||||
fn negative_radius(#[case] radius: i32) {
|
||||
let result = is_within_radius(radius, &(0, 0));
|
||||
assert_err!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boundary_points() {
|
||||
let radius = 3;
|
||||
// Test points exactly on the boundary of radius 3
|
||||
assert_ok_eq!(is_within_radius(radius, &(3, 0)), true); // East boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(0, 3)), true); // Southeast boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(-3, 3)), true); // Southwest boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(-3, 0)), true); // West boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(0, -3)), true); // Northwest boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(3, -3)), true); // Northeast boundary
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_boundary_points() {
|
||||
let radius = 6;
|
||||
// Test points exactly on the boundary of radius 6
|
||||
assert_ok_eq!(is_within_radius(radius, &(6, 0)), true); // East boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(0, 6)), true); // Southeast boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(-6, 6)), true); // Southwest boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(-6, 0)), true); // West boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(0, -6)), true); // Northwest boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(6, -6)), true); // Northeast boundary
|
||||
|
||||
// Test points just outside the boundary
|
||||
assert_ok_eq!(is_within_radius(radius, &(7, 0)), false); // Just outside east
|
||||
assert_ok_eq!(is_within_radius(radius, &(0, 7)), false); // Just outside southeast
|
||||
assert_ok_eq!(is_within_radius(radius, &(-7, 7)), false); // Just outside southwest
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_coordinate_types() {
|
||||
// Test with tuple coordinates
|
||||
assert_ok_eq!(is_within_radius(2, &(1, 1)), true);
|
||||
|
||||
// Test with Hex struct
|
||||
let hex = Hex { x: 1, y: 1 };
|
||||
assert_ok_eq!(is_within_radius(2, &hex), true);
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,11 @@ pub enum MazeError {
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub type MazeResult<T> = Result<T, MazeError>;
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RadiusError {
|
||||
#[error("Radius cannot be negative: {0}")]
|
||||
NegativeRadius(i32),
|
||||
}
|
||||
|
||||
impl MazeError {
|
||||
pub fn config_error(msg: impl Into<String>) -> Self {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
mod assets;
|
||||
pub mod commands;
|
||||
pub mod components;
|
||||
pub mod coordinates;
|
||||
pub mod errors;
|
||||
pub mod resources;
|
||||
mod systems;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user