mirror of
https://github.com/kristoferssolo/hexlab.git
synced 2025-10-21 19:40:34 +00:00
feat(bevy): add bevy feature support
This commit is contained in:
parent
971e1c760e
commit
37b63365f1
4121
Cargo.lock
generated
4121
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,23 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hexlab"
|
name = "hexlab"
|
||||||
|
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A hexagonal maze library"
|
description = "A hexagonal maze library"
|
||||||
|
repository = "https://github.com/kristoferssolo/hexlab"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
keywords = ["maze", "hex", "hexagons"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bevy = { version = "0.14", optional = true }
|
||||||
hexx = { version = "0.18" }
|
hexx = { version = "0.18" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
rand_chacha = "0.3"
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
serde = ["dep:serde", "hexx/serde"]
|
serde = ["dep:serde", "hexx/serde", "rand_chacha/serde"]
|
||||||
|
bevy = ["dep:bevy", "hexx/bevy_reflect"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1.0"
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use hexx::{EdgeDirection, Hex};
|
use hexx::{EdgeDirection, Hex};
|
||||||
use rand::{rngs::ThreadRng, seq::SliceRandom, thread_rng};
|
use rand::{seq::SliceRandom, thread_rng, Rng, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
|
||||||
use crate::HexMaze;
|
use crate::HexMaze;
|
||||||
|
|
||||||
@ -17,22 +18,40 @@ impl HexMaze {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_from_seed(&mut self, generator_type: GeneratorType, seed: u64) {
|
||||||
|
match generator_type {
|
||||||
|
GeneratorType::BackTracking => self.generate_backtracking_from_seed(seed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_backtracking(&mut self) {
|
pub fn generate_backtracking(&mut self) {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let start = *self.tiles().keys().next().unwrap();
|
let start = *self.keys().next().unwrap();
|
||||||
|
|
||||||
let mut visited = HashSet::new();
|
let mut visited = HashSet::new();
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
self.recursive_backtrack(start, &mut visited, &mut rng);
|
self.recursive_backtrack(start, &mut visited, &mut rng);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recursive_backtrack(
|
pub fn generate_backtracking_from_seed(&mut self, seed: u64) {
|
||||||
|
if self.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// let start = *self.keys().next().unwrap();
|
||||||
|
let start = Hex::ZERO;
|
||||||
|
|
||||||
|
let mut visited = HashSet::new();
|
||||||
|
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||||
|
self.recursive_backtrack(start, &mut visited, &mut rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recursive_backtrack<R: Rng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
current: Hex,
|
current: Hex,
|
||||||
visited: &mut HashSet<Hex>,
|
visited: &mut HashSet<Hex>,
|
||||||
rng: &mut ThreadRng,
|
rng: &mut R,
|
||||||
) {
|
) {
|
||||||
visited.insert(current);
|
visited.insert(current);
|
||||||
|
|
||||||
@ -42,8 +61,7 @@ impl HexMaze {
|
|||||||
for direction in directions {
|
for direction in directions {
|
||||||
let neighbor = current + direction;
|
let neighbor = current + direction;
|
||||||
|
|
||||||
if let Some(_) = self.get_tile(&neighbor) {
|
if self.get_tile(&neighbor).is_some() && !visited.contains(&neighbor) {
|
||||||
if !visited.contains(&neighbor) {
|
|
||||||
self.remove_tile_wall(¤t, direction);
|
self.remove_tile_wall(¤t, direction);
|
||||||
self.remove_tile_wall(&neighbor, direction.const_neg());
|
self.remove_tile_wall(&neighbor, direction.const_neg());
|
||||||
|
|
||||||
@ -52,20 +70,17 @@ impl HexMaze {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use hexx::HexLayout;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn backtracking_generation() {
|
fn backtracking_generation() {
|
||||||
let mut maze = HexMaze::default().with_radius(2);
|
let mut maze = HexMaze::with_radius(2);
|
||||||
|
|
||||||
// Before generation
|
// Before generation
|
||||||
for tile in maze.tiles.values() {
|
for tile in maze.values() {
|
||||||
assert_eq!(tile.walls.as_bits(), 0b111111);
|
assert_eq!(tile.walls.as_bits(), 0b111111);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,16 +88,13 @@ mod tests {
|
|||||||
maze.generate(GeneratorType::BackTracking);
|
maze.generate(GeneratorType::BackTracking);
|
||||||
|
|
||||||
// After generation
|
// After generation
|
||||||
let all_walls = maze
|
let all_walls = maze.values().all(|tile| tile.walls.as_bits() == 0b111111);
|
||||||
.tiles
|
|
||||||
.values()
|
|
||||||
.all(|tile| tile.walls.as_bits() == 0b111111);
|
|
||||||
assert!(!all_walls, "Some walls should be removed");
|
assert!(!all_walls, "Some walls should be removed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_maze() {
|
fn empty_maze() {
|
||||||
let mut maze = HexMaze::with_layout(HexLayout::default());
|
let mut maze = HexMaze::default();
|
||||||
maze.generate(GeneratorType::BackTracking);
|
maze.generate(GeneratorType::BackTracking);
|
||||||
assert!(maze.is_empty(), "Empty maze should remain empty");
|
assert!(maze.is_empty(), "Empty maze should remain empty");
|
||||||
}
|
}
|
||||||
|
|||||||
122
src/maze.rs
122
src/maze.rs
@ -1,39 +1,28 @@
|
|||||||
use std::collections::HashMap;
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
use hexx::{EdgeDirection, Hex, HexLayout};
|
use hexx::{EdgeDirection, Hex};
|
||||||
|
|
||||||
use super::{HexTile, Walls};
|
use super::{HexTile, Walls};
|
||||||
|
|
||||||
/// Represents a hexagonal maze with tiles and walls
|
/// Represents a hexagonal maze with tiles and walls
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct HexMaze {
|
pub struct HexMaze(HashMap<Hex, HexTile>);
|
||||||
pub tiles: HashMap<Hex, HexTile>,
|
|
||||||
layout: HexLayout,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HexMaze {
|
impl HexMaze {
|
||||||
/// Creates a new empty maze with the specified layout
|
/// Creates a new empty maze
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(layout: HexLayout) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self::default()
|
||||||
tiles: HashMap::new(),
|
|
||||||
layout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new empty maze with the specified layout
|
|
||||||
pub fn with_layout(layout: HexLayout) -> Self {
|
|
||||||
Self {
|
|
||||||
tiles: HashMap::new(),
|
|
||||||
layout,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a hexagonal maze with the given radius
|
/// Creates a hexagonal maze with the given radius
|
||||||
/// Uses axial coordinates (q, r) to create a perfect hexagon
|
/// Uses axial coordinates (q, r) to create a perfect hexagon
|
||||||
#[inline]
|
pub fn with_radius(radius: u32) -> Self {
|
||||||
pub fn with_radius(mut self, radius: u32) -> Self {
|
let mut maze = Self::default();
|
||||||
let radius = radius as i32;
|
let radius = radius as i32;
|
||||||
for q in -radius..=radius {
|
for q in -radius..=radius {
|
||||||
let r1 = (-radius).max(-q - radius);
|
let r1 = (-radius).max(-q - radius);
|
||||||
@ -41,24 +30,22 @@ impl HexMaze {
|
|||||||
for r in r1..=r2 {
|
for r in r1..=r2 {
|
||||||
let pos = Hex::new(q, r);
|
let pos = Hex::new(q, r);
|
||||||
let tile = HexTile::new(pos);
|
let tile = HexTile::new(pos);
|
||||||
self.tiles.insert(pos, tile);
|
maze.0.insert(pos, tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
maze
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new tile at the specified coordinates
|
/// Adds a new tile at the specified coordinates
|
||||||
#[inline]
|
|
||||||
pub fn add_tile(&mut self, coords: Hex) {
|
pub fn add_tile(&mut self, coords: Hex) {
|
||||||
let tile = HexTile::new(coords);
|
let tile = HexTile::new(coords);
|
||||||
self.tiles.insert(coords, tile);
|
self.0.insert(coords, tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a wall in the specified direction at the given coordinates
|
/// Adds a wall in the specified direction at the given coordinates
|
||||||
#[inline]
|
|
||||||
pub fn add_wall(&mut self, coord: Hex, direction: EdgeDirection) {
|
pub fn add_wall(&mut self, coord: Hex, direction: EdgeDirection) {
|
||||||
if let Some(tile) = self.tiles.get_mut(&coord) {
|
if let Some(tile) = self.0.get_mut(&coord) {
|
||||||
tile.walls.add(direction)
|
tile.walls.add(direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,47 +53,46 @@ impl HexMaze {
|
|||||||
/// Returns a reference to the tile at the specified coordinates
|
/// Returns a reference to the tile at the specified coordinates
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_tile(&self, coord: &Hex) -> Option<&HexTile> {
|
pub fn get_tile(&self, coord: &Hex) -> Option<&HexTile> {
|
||||||
self.tiles.get(coord)
|
self.0.get(coord)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the walls at the specified coordinates
|
/// Returns a reference to the walls at the specified coordinates
|
||||||
#[inline]
|
|
||||||
pub fn get_walls(&self, coord: &Hex) -> Option<&Walls> {
|
pub fn get_walls(&self, coord: &Hex) -> Option<&Walls> {
|
||||||
self.tiles.get(coord).map(|tile| tile.walls())
|
self.0.get(coord).map(|tile| tile.walls())
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the layout of the maze
|
|
||||||
#[inline]
|
|
||||||
pub fn layout(&self) -> &HexLayout {
|
|
||||||
&self.layout
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator over all tiles
|
|
||||||
#[inline]
|
|
||||||
pub fn tiles(&self) -> &HashMap<Hex, HexTile> {
|
|
||||||
&self.tiles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of tiles in the maze
|
/// Returns the number of tiles in the maze
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.tiles.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the maze is empty
|
/// Returns true if the maze is empty
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.tiles.is_empty()
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn remove_tile_wall(&mut self, coord: &Hex, direction: EdgeDirection) {
|
pub fn remove_tile_wall(&mut self, coord: &Hex, direction: EdgeDirection) {
|
||||||
if let Some(tile) = self.tiles.get_mut(coord) {
|
if let Some(tile) = self.0.get_mut(coord) {
|
||||||
tile.walls.remove(direction);
|
tile.walls.remove(direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for HexMaze {
|
||||||
|
type Target = HashMap<Hex, HexTile>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for HexMaze {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -155,7 +141,7 @@ mod tests {
|
|||||||
for &direction in &directions {
|
for &direction in &directions {
|
||||||
maze.add_wall(coord, direction);
|
maze.add_wall(coord, direction);
|
||||||
assert!(
|
assert!(
|
||||||
maze.get_walls(&coord).unwrap().has(direction),
|
maze.get_walls(&coord).unwrap().contains(direction),
|
||||||
"Wall should exist after adding"
|
"Wall should exist after adding"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -172,11 +158,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test iterator
|
// Test iterator
|
||||||
let collected = maze
|
let collected = maze.iter().map(|(_, tile)| tile).collect::<Vec<_>>();
|
||||||
.tiles()
|
|
||||||
.iter()
|
|
||||||
.map(|(_, tile)| tile)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
collected.len(),
|
collected.len(),
|
||||||
coords.len(),
|
coords.len(),
|
||||||
@ -202,7 +184,7 @@ mod tests {
|
|||||||
cloned_maze
|
cloned_maze
|
||||||
.get_walls(&coord)
|
.get_walls(&coord)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.has(EdgeDirection::FLAT_TOP),
|
.contains(EdgeDirection::FLAT_TOP),
|
||||||
"Cloned maze should preserve wall state"
|
"Cloned maze should preserve wall state"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -259,11 +241,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify iterator
|
// Verify iterator
|
||||||
let iter_coords = maze
|
let iter_coords = maze.iter().map(|(coord, _)| *coord).collect::<Vec<_>>();
|
||||||
.tiles()
|
|
||||||
.iter()
|
|
||||||
.map(|(coord, _)| *coord)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
iter_coords.len(),
|
iter_coords.len(),
|
||||||
coords.len(),
|
coords.len(),
|
||||||
@ -281,8 +259,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn maze_builder() {
|
fn maze_builder() {
|
||||||
// Test builder pattern
|
// Test builder pattern
|
||||||
let layout = HexLayout::default();
|
let maze = HexMaze::with_radius(2);
|
||||||
let maze = HexMaze::with_layout(layout).with_radius(2);
|
|
||||||
|
|
||||||
assert_eq!(maze.len(), 19, "Radius 2 should create 19 hexes");
|
assert_eq!(maze.len(), 19, "Radius 2 should create 19 hexes");
|
||||||
assert!(
|
assert!(
|
||||||
@ -291,30 +268,9 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn different_layouts() {
|
|
||||||
// Test with different layouts
|
|
||||||
let layouts = [
|
|
||||||
HexLayout {
|
|
||||||
orientation: hexx::HexOrientation::Flat,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
HexLayout {
|
|
||||||
orientation: hexx::HexOrientation::Pointy,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for layout in layouts {
|
|
||||||
let maze = HexMaze::with_layout(layout).with_radius(1);
|
|
||||||
assert_eq!(maze.len(), 7, "Should work with different layouts");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_maze() {
|
fn empty_maze() {
|
||||||
let layout = HexLayout::default();
|
let maze = HexMaze::default();
|
||||||
let maze = HexMaze::with_layout(layout);
|
|
||||||
assert!(maze.is_empty(), "New maze should be empty");
|
assert!(maze.is_empty(), "New maze should be empty");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/tile.rs
53
src/tile.rs
@ -1,10 +1,19 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use hexx::Hex;
|
use hexx::Hex;
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy")]
|
||||||
|
use hexx::HexLayout;
|
||||||
|
|
||||||
use super::Walls;
|
use super::Walls;
|
||||||
|
#[cfg(feature = "bevy")]
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
/// Represents a single hexagonal tile in the maze
|
/// Represents a single hexagonal tile in the maze
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[cfg_attr(feature = "bevy", derive(Reflect, Component))]
|
||||||
|
#[cfg_attr(feature = "bevy", reflect(Component))]
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
pub struct HexTile {
|
pub struct HexTile {
|
||||||
pub pos: Hex,
|
pub pos: Hex,
|
||||||
pub walls: Walls,
|
pub walls: Walls,
|
||||||
@ -12,7 +21,6 @@ pub struct HexTile {
|
|||||||
|
|
||||||
impl HexTile {
|
impl HexTile {
|
||||||
/// Creates a new tile with pos and default walls
|
/// Creates a new tile with pos and default walls
|
||||||
#[inline]
|
|
||||||
pub fn new(pos: Hex) -> Self {
|
pub fn new(pos: Hex) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pos,
|
pos,
|
||||||
@ -31,6 +39,17 @@ impl HexTile {
|
|||||||
pub fn pos(&self) -> Hex {
|
pub fn pos(&self) -> Hex {
|
||||||
self.pos
|
self.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy")]
|
||||||
|
pub fn to_vec2(&self, layout: &HexLayout) -> Vec2 {
|
||||||
|
layout.hex_to_world_pos(self.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy")]
|
||||||
|
pub fn to_vec3(&self, layout: &HexLayout) -> Vec3 {
|
||||||
|
let pos = self.to_vec2(layout);
|
||||||
|
Vec3::new(pos.x, 0., pos.y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Hex> for HexTile {
|
impl From<Hex> for HexTile {
|
||||||
@ -42,6 +61,12 @@ impl From<Hex> for HexTile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for HexTile {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "({},{})", self.pos.x, self.pos.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use hexx::EdgeDirection;
|
use hexx::EdgeDirection;
|
||||||
@ -82,37 +107,17 @@ mod tests {
|
|||||||
// Modify walls
|
// Modify walls
|
||||||
tile.walls.remove(EdgeDirection::FLAT_TOP);
|
tile.walls.remove(EdgeDirection::FLAT_TOP);
|
||||||
assert!(
|
assert!(
|
||||||
!tile.walls.has(EdgeDirection::FLAT_TOP),
|
!tile.walls.contains(EdgeDirection::FLAT_TOP),
|
||||||
"Wall should be removed"
|
"Wall should be removed"
|
||||||
);
|
);
|
||||||
|
|
||||||
tile.walls.add(EdgeDirection::FLAT_TOP);
|
tile.walls.add(EdgeDirection::FLAT_TOP);
|
||||||
assert!(
|
assert!(
|
||||||
tile.walls.has(EdgeDirection::FLAT_TOP),
|
tile.walls.contains(EdgeDirection::FLAT_TOP),
|
||||||
"Wall should be added back"
|
"Wall should be added back"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tile_copy() {
|
|
||||||
let pos = Hex::new(-1, 2);
|
|
||||||
let tile = HexTile::new(pos);
|
|
||||||
|
|
||||||
// Test Copy trait
|
|
||||||
let copied_tile = tile;
|
|
||||||
assert_eq!(tile, copied_tile, "Copied tile should equal original");
|
|
||||||
|
|
||||||
// Verify both tiles are still usable
|
|
||||||
assert_eq!(
|
|
||||||
tile.pos, copied_tile.pos,
|
|
||||||
"Positions should match after copy"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
tile.walls, copied_tile.walls,
|
|
||||||
"Walls should match after copy"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tile_clone() {
|
fn tile_clone() {
|
||||||
let pos = Hex::new(0, -2);
|
let pos = Hex::new(0, -2);
|
||||||
|
|||||||
65
src/walls.rs
65
src/walls.rs
@ -1,19 +1,20 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy")]
|
||||||
|
use bevy::prelude::*;
|
||||||
use hexx::EdgeDirection;
|
use hexx::EdgeDirection;
|
||||||
|
|
||||||
/// Represents the walls of a hexagonal tile using bit flags
|
/// Represents the walls of a hexagonal tile using bit flags
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "bevy", derive(Reflect, Component))]
|
||||||
|
#[cfg_attr(feature = "bevy", reflect(Component))]
|
||||||
pub struct Walls(u8);
|
pub struct Walls(u8);
|
||||||
|
|
||||||
impl Walls {
|
impl Walls {
|
||||||
/// Creates a new walls configuration with all walls
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a wall in the specified direction
|
/// Adds a wall in the specified direction
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn add(&mut self, direction: EdgeDirection) {
|
pub fn add(&mut self, direction: EdgeDirection) {
|
||||||
@ -28,8 +29,11 @@ impl Walls {
|
|||||||
|
|
||||||
/// Returns true if there is a wall in the specified direction
|
/// Returns true if there is a wall in the specified direction
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn has(&self, direction: EdgeDirection) -> bool {
|
pub fn contains<T>(&self, other: T) -> bool
|
||||||
self.0 & Self::from(direction).0 != 0
|
where
|
||||||
|
T: Into<Self>,
|
||||||
|
{
|
||||||
|
self.0 & other.into().0 != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the raw bit representation of the walls
|
/// Returns the raw bit representation of the walls
|
||||||
@ -41,8 +45,13 @@ impl Walls {
|
|||||||
|
|
||||||
impl From<EdgeDirection> for Walls {
|
impl From<EdgeDirection> for Walls {
|
||||||
fn from(value: EdgeDirection) -> Self {
|
fn from(value: EdgeDirection) -> Self {
|
||||||
let bits = 1 << value.index();
|
Self(1 << value.index())
|
||||||
Self(bits)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Walls {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
Self(1 << value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,11 +81,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_walls() {
|
fn new_walls() {
|
||||||
let walls = Walls::new();
|
let walls = Walls::default();
|
||||||
// All walls should be present by default
|
// All walls should be present by default
|
||||||
for direction in EdgeDirection::iter() {
|
for direction in EdgeDirection::iter() {
|
||||||
assert!(
|
assert!(
|
||||||
walls.has(direction),
|
walls.contains(direction),
|
||||||
"Wall should exist in direction {:?}",
|
"Wall should exist in direction {:?}",
|
||||||
direction
|
direction
|
||||||
);
|
);
|
||||||
@ -85,42 +94,42 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_remove_single_wall() {
|
fn add_remove_single_wall() {
|
||||||
let mut walls = Walls::new();
|
let mut walls = Walls::default();
|
||||||
|
|
||||||
// Remove and verify each wall
|
// Remove and verify each wall
|
||||||
walls.remove(EdgeDirection::FLAT_TOP);
|
walls.remove(EdgeDirection::FLAT_TOP);
|
||||||
assert!(!walls.has(EdgeDirection::FLAT_TOP));
|
assert!(!walls.contains(EdgeDirection::FLAT_TOP));
|
||||||
|
|
||||||
// Add back and verify
|
// Add back and verify
|
||||||
walls.add(EdgeDirection::FLAT_TOP);
|
walls.add(EdgeDirection::FLAT_TOP);
|
||||||
assert!(walls.has(EdgeDirection::FLAT_TOP));
|
assert!(walls.contains(EdgeDirection::FLAT_TOP));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiple_operations() {
|
fn multiple_operations() {
|
||||||
let mut walls = Walls::new();
|
let mut walls = Walls::default();
|
||||||
|
|
||||||
// Remove multiple walls
|
// Remove multiple walls
|
||||||
walls.remove(EdgeDirection::FLAT_TOP);
|
walls.remove(EdgeDirection::FLAT_TOP);
|
||||||
walls.remove(EdgeDirection::FLAT_BOTTOM);
|
walls.remove(EdgeDirection::FLAT_BOTTOM);
|
||||||
|
|
||||||
// Verify removed walls
|
// Verify removed walls
|
||||||
assert!(!walls.has(EdgeDirection::FLAT_TOP));
|
assert!(!walls.contains(EdgeDirection::FLAT_TOP));
|
||||||
assert!(!walls.has(EdgeDirection::FLAT_BOTTOM));
|
assert!(!walls.contains(EdgeDirection::FLAT_BOTTOM));
|
||||||
|
|
||||||
// Verify other walls still exist
|
// Verify other walls still exist
|
||||||
assert!(walls.has(EdgeDirection::FLAT_TOP_RIGHT));
|
assert!(walls.contains(EdgeDirection::FLAT_TOP_RIGHT));
|
||||||
assert!(walls.has(EdgeDirection::FLAT_TOP_LEFT));
|
assert!(walls.contains(EdgeDirection::FLAT_TOP_LEFT));
|
||||||
|
|
||||||
// Add back one wall
|
// Add back one wall
|
||||||
walls.add(EdgeDirection::FLAT_TOP);
|
walls.add(EdgeDirection::FLAT_TOP);
|
||||||
assert!(walls.has(EdgeDirection::FLAT_TOP));
|
assert!(walls.contains(EdgeDirection::FLAT_TOP));
|
||||||
assert!(!walls.has(EdgeDirection::FLAT_BOTTOM));
|
assert!(!walls.contains(EdgeDirection::FLAT_BOTTOM));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bit_patterns() {
|
fn bit_patterns() {
|
||||||
let mut walls = Walls::new();
|
let mut walls = Walls::default();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
walls.as_bits(),
|
walls.as_bits(),
|
||||||
0b111111,
|
0b111111,
|
||||||
@ -136,7 +145,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_all_walls() {
|
fn remove_all_walls() {
|
||||||
let mut walls = Walls::new();
|
let mut walls = Walls::default();
|
||||||
|
|
||||||
// Remove all walls
|
// Remove all walls
|
||||||
for direction in EdgeDirection::iter() {
|
for direction in EdgeDirection::iter() {
|
||||||
@ -149,7 +158,7 @@ mod tests {
|
|||||||
// Verify each direction
|
// Verify each direction
|
||||||
for direction in EdgeDirection::iter() {
|
for direction in EdgeDirection::iter() {
|
||||||
assert!(
|
assert!(
|
||||||
!walls.has(direction),
|
!walls.contains(direction),
|
||||||
"No wall should exist in direction {:?}",
|
"No wall should exist in direction {:?}",
|
||||||
direction
|
direction
|
||||||
);
|
);
|
||||||
@ -158,7 +167,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deref_operations() {
|
fn deref_operations() {
|
||||||
let mut walls = Walls::new();
|
let mut walls = Walls::default();
|
||||||
|
|
||||||
// Test Deref
|
// Test Deref
|
||||||
let bits: &u8 = walls.deref();
|
let bits: &u8 = walls.deref();
|
||||||
@ -171,7 +180,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn idempotent_operations() {
|
fn idempotent_operations() {
|
||||||
let mut walls = Walls::new();
|
let mut walls = Walls::default();
|
||||||
|
|
||||||
// Adding twice shouldn't change the result
|
// Adding twice shouldn't change the result
|
||||||
walls.add(EdgeDirection::FLAT_TOP);
|
walls.add(EdgeDirection::FLAT_TOP);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user