mirror of
https://github.com/kristoferssolo/Qualification-Thesis.git
synced 2025-10-21 20:10:37 +00:00
feat: add more code examples
This commit is contained in:
parent
c4bbd7c88c
commit
84b95fac6a
176
assets/code/hexlab/builder.rs
Normal file
176
assets/code/hexlab/builder.rs
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/// A builder pattern for creating hexagonal mazes.
|
||||||
|
///
|
||||||
|
/// This struct provides a fluent interface for configuring and building hexagonal mazes.
|
||||||
|
/// It offers flexibility in specifying the maze size, random seed, generation algorithm,
|
||||||
|
/// and starting position.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
/// ```
|
||||||
|
/// use hexlab::prelude::*;
|
||||||
|
///
|
||||||
|
/// let maze = MazeBuilder::new()
|
||||||
|
/// .with_radius(5)
|
||||||
|
/// .build()
|
||||||
|
/// .expect("Failed to create maze");
|
||||||
|
///
|
||||||
|
/// // A radius of 5 creates 61 hexagonal tiles
|
||||||
|
/// assert!(!maze.is_empty());
|
||||||
|
/// assert_eq!(maze.len(), 91);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Using a seed for reproducible results:
|
||||||
|
/// ```
|
||||||
|
/// use hexlab::prelude::*;
|
||||||
|
///
|
||||||
|
/// let maze1 = MazeBuilder::new()
|
||||||
|
/// .with_radius(3)
|
||||||
|
/// .with_seed(12345)
|
||||||
|
/// .build()
|
||||||
|
/// .expect("Failed to create maze");
|
||||||
|
///
|
||||||
|
/// let maze2 = MazeBuilder::new()
|
||||||
|
/// .with_radius(3)
|
||||||
|
/// .with_seed(12345)
|
||||||
|
/// .build()
|
||||||
|
/// .expect("Failed to create maze");
|
||||||
|
///
|
||||||
|
/// // Same seed should produce identical mazes
|
||||||
|
/// assert_eq!(maze1.len(), maze2.len());
|
||||||
|
/// assert_eq!(maze1, maze2);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Specifying a custom generator:
|
||||||
|
/// ```
|
||||||
|
/// use hexlab::prelude::*;
|
||||||
|
///
|
||||||
|
/// let maze = MazeBuilder::new()
|
||||||
|
/// .with_radius(7)
|
||||||
|
/// .with_generator(GeneratorType::RecursiveBacktracking)
|
||||||
|
/// .build()
|
||||||
|
/// .expect("Failed to create maze");
|
||||||
|
/// ```
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MazeBuilder {
|
||||||
|
radius: Option<u16>,
|
||||||
|
seed: Option<u64>,
|
||||||
|
generator_type: GeneratorType,
|
||||||
|
start_position: Option<Hex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MazeBuilder {
|
||||||
|
/// Creates a new [`MazeBuilder`] instance with default settings.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the radius for the hexagonal maze.
|
||||||
|
///
|
||||||
|
/// The radius determines the size of the maze, specifically the number of tiles
|
||||||
|
/// from the center (0,0) to the edge of the hexagon, not including the center tile.
|
||||||
|
/// For example, a radius of 3 would create a maze with 3 tiles from center to edge,
|
||||||
|
/// resulting in a total diameter of 7 tiles (3 + 1 + 3).
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `radius` - The number of tiles from the center to the edge of the hexagon.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn with_radius(mut self, radius: u16) -> Self {
|
||||||
|
self.radius = Some(radius);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the random seed for maze generation.
|
||||||
|
///
|
||||||
|
/// Using the same seed will produce identical mazes, allowing for reproducible results.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `seed` - The random seed value.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn with_seed(mut self, seed: u64) -> Self {
|
||||||
|
self.seed = Some(seed);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the generator algorithm for maze creation.
|
||||||
|
///
|
||||||
|
/// Different generators may produce different maze patterns and characteristics.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `generator_type` - The maze generation algorithm to use.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn with_generator(
|
||||||
|
mut self,
|
||||||
|
generator_type: GeneratorType,
|
||||||
|
) -> Self {
|
||||||
|
self.generator_type = generator_type;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the starting position for maze generation.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `pos` - The hexagonal coordinates for the starting position.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn with_start_position(mut self, pos: Hex) -> Self {
|
||||||
|
self.start_position = Some(pos);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the hexagonal maze based on the configured parameters.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`MazeBuilderError::NoRadius`] if no radius is specified.
|
||||||
|
/// Returns [`MazeBuilderError::InvalidStartPosition`] if the start position is outside maze
|
||||||
|
/// bounds.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use hexlab::prelude::*;
|
||||||
|
///
|
||||||
|
/// // Should fail without radius
|
||||||
|
/// let result = MazeBuilder::new().build();
|
||||||
|
/// assert!(result.is_err());
|
||||||
|
///
|
||||||
|
/// // Should succeed with radius
|
||||||
|
/// let result = MazeBuilder::new()
|
||||||
|
/// .with_radius(3)
|
||||||
|
/// .build();
|
||||||
|
/// assert!(result.is_ok());
|
||||||
|
///
|
||||||
|
/// let maze = result.unwrap();
|
||||||
|
/// assert!(!maze.is_empty());
|
||||||
|
/// ```
|
||||||
|
pub fn build(self) -> Result<Maze, MazeBuilderError> {
|
||||||
|
let radius = self.radius.ok_or(MazeBuilderError::NoRadius)?;
|
||||||
|
let mut maze = create_hex_maze(radius);
|
||||||
|
|
||||||
|
if let Some(start_pos) = self.start_position {
|
||||||
|
if maze.get(&start_pos).is_none() {
|
||||||
|
return Err(MazeBuilderError::InvalidStartPosition(start_pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !maze.is_empty() {
|
||||||
|
self.generator_type.generate(
|
||||||
|
&mut maze,
|
||||||
|
self.start_position,
|
||||||
|
self.seed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(maze)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,35 +0,0 @@
|
|||||||
pub fn generate_backtracking(
|
|
||||||
maze: &mut HexMaze,
|
|
||||||
start_pos: Option<Hex>,
|
|
||||||
seed: Option<u64>,
|
|
||||||
) {
|
|
||||||
if maze.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let start = start_pos.unwrap_or(Hex::ZERO);
|
|
||||||
let mut visited = HashSet::new();
|
|
||||||
let mut rng: Box<dyn RngCore> = seed.map_or_else(
|
|
||||||
|| Box::new(thread_rng()) as Box<dyn RngCore>,
|
|
||||||
|seed| Box::new(ChaCha8Rng::seed_from_u64(seed)) as Box<dyn RngCore>,
|
|
||||||
);
|
|
||||||
recursive_backtrack(maze, start, &mut visited, &mut rng);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recursive_backtrack<R: Rng>(
|
|
||||||
maze: &mut HexMaze,
|
|
||||||
current: Hex,
|
|
||||||
visited: &mut HashSet<Hex>,
|
|
||||||
rng: &mut R,
|
|
||||||
) {
|
|
||||||
visited.insert(current);
|
|
||||||
let mut directions = EdgeDirection::ALL_DIRECTIONS;
|
|
||||||
directions.shuffle(rng);
|
|
||||||
for direction in directions {
|
|
||||||
let neighbor = current + direction;
|
|
||||||
if maze.get_tile(&neighbor).is_some() && !visited.contains(&neighbor) {
|
|
||||||
maze.remove_tile_wall(¤t, direction);
|
|
||||||
maze.remove_tile_wall(&neighbor, direction.const_neg());
|
|
||||||
recursive_backtrack(maze, neighbor, visited, rng);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
#[derive(Default)]
|
|
||||||
pub struct MazeBuilder {
|
|
||||||
radius: Option<u32>,
|
|
||||||
seed: Option<u64>,
|
|
||||||
generator_type: GeneratorType,
|
|
||||||
start_position: Option<Hex>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
|
||||||
pub enum GeneratorType {
|
|
||||||
#[default]
|
|
||||||
RecursiveBacktracking,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
||||||
pub struct HexMaze(HashMap<Hex, HexTile>);
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "bevy", derive(Reflect, Component))]
|
|
||||||
#[cfg_attr(feature = "bevy", reflect(Component))]
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
||||||
pub struct HexTile {
|
|
||||||
pub(crate) pos: Hex,
|
|
||||||
pub(crate) walls: Walls,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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))]
|
|
||||||
pub struct Walls(u8);
|
|
||||||
197
assets/code/hexlab/walls.rs
Normal file
197
assets/code/hexlab/walls.rs
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
/// A bit-flag representation of walls in a hexagonal tile.
|
||||||
|
///
|
||||||
|
/// `Walls` uses an efficient bit-flag system to track the presence or absence of walls
|
||||||
|
/// along each edge of a hexagonal tile. Each of the six possible walls is represented
|
||||||
|
/// by a single bit in an 8-bit integer, allowing for fast operations and minimal memory usage.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Creating and manipulating walls:
|
||||||
|
/// ```
|
||||||
|
/// use hexlab::prelude::*;
|
||||||
|
///
|
||||||
|
/// // Create a hexagon with all walls
|
||||||
|
/// let walls = Walls::new();
|
||||||
|
/// assert!(walls.is_enclosed());
|
||||||
|
///
|
||||||
|
/// // Create a hexagon with no walls
|
||||||
|
/// let mut walls = Walls::empty();
|
||||||
|
/// assert!(walls.is_empty());
|
||||||
|
///
|
||||||
|
/// // Add specific walls
|
||||||
|
/// walls.insert(EdgeDirection::FLAT_NORTH);
|
||||||
|
/// walls.insert(EdgeDirection::FLAT_SOUTH);
|
||||||
|
/// assert_eq!(walls.count(), 2);
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||||
|
#[cfg_attr(feature = "bevy", derive(Component))]
|
||||||
|
#[cfg_attr(feature = "bevy", reflect(Component))]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Walls(u8);
|
||||||
|
|
||||||
|
impl Walls {
|
||||||
|
/// Insert a wall in the specified direction.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `direction` - The direction in which to insert the wall.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns `true` if a wall was present, `false` otherwise.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use hexlab::prelude::*;
|
||||||
|
///
|
||||||
|
/// let mut walls = Walls::empty();
|
||||||
|
/// assert_eq!(walls.count(), 0);
|
||||||
|
///
|
||||||
|
/// assert!(!walls.insert(1));
|
||||||
|
/// assert_eq!(walls.count(), 1);
|
||||||
|
///
|
||||||
|
/// assert!(walls.insert(1));
|
||||||
|
/// assert_eq!(walls.count(), 1);
|
||||||
|
///
|
||||||
|
/// assert!(!walls.insert(EdgeDirection::FLAT_NORTH));
|
||||||
|
/// assert_eq!(walls.count(), 2);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn insert<T>(&mut self, direction: T) -> bool
|
||||||
|
where
|
||||||
|
T: Into<Self>,
|
||||||
|
{
|
||||||
|
let mask = direction.into().0;
|
||||||
|
let was_present = self.0 & mask != 0;
|
||||||
|
self.0 |= mask;
|
||||||
|
was_present
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a wall in the specified direction.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `direction` - The direction from which to remove the wall.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns `true` if a wall was present and removed, `false` otherwise.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use hexlab::prelude::*;
|
||||||
|
///
|
||||||
|
/// let mut walls = Walls::new();
|
||||||
|
///
|
||||||
|
/// assert!(walls.remove(1));
|
||||||
|
/// assert_eq!(walls.count(), 5);
|
||||||
|
///
|
||||||
|
/// assert!(!walls.remove(1));
|
||||||
|
/// assert_eq!(walls.count(), 5);
|
||||||
|
///
|
||||||
|
/// assert!(walls.remove(EdgeDirection::FLAT_NORTH));
|
||||||
|
/// assert_eq!(walls.count(), 4);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn remove<T>(&mut self, direction: T) -> bool
|
||||||
|
where
|
||||||
|
T: Into<Self>,
|
||||||
|
{
|
||||||
|
let mask = direction.into().0;
|
||||||
|
let was_present = self.0 & mask != 0;
|
||||||
|
self.0 &= !mask;
|
||||||
|
was_present
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if there is a wall in the specified direction.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `other` - The direction to check for a wall.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use hexlab::prelude::*;
|
||||||
|
///
|
||||||
|
/// let mut walls = Walls::empty();
|
||||||
|
/// walls.insert(EdgeDirection::FLAT_NORTH);
|
||||||
|
///
|
||||||
|
/// assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||||
|
/// assert!(!walls.contains(EdgeDirection::FLAT_SOUTH));
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn contains<T>(&self, direction: T) -> bool
|
||||||
|
where
|
||||||
|
T: Into<Self>,
|
||||||
|
{
|
||||||
|
self.0 & direction.into().0 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles a wall in the specified direction.
|
||||||
|
///
|
||||||
|
/// If a wall exists in the given direction, it will be removed.
|
||||||
|
/// If no wall exists, one will be added.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `direction` - The direction in which to toggle the wall.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The previous state (`true` if a wall was present before toggling, `false` otherwise).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use hexlab::prelude::*;
|
||||||
|
///
|
||||||
|
/// let mut walls = Walls::empty();
|
||||||
|
///
|
||||||
|
/// assert!(!walls.toggle(0));
|
||||||
|
/// assert_eq!(walls.count(), 1);
|
||||||
|
///
|
||||||
|
/// assert!(walls.toggle(0));
|
||||||
|
/// assert_eq!(walls.count(), 0);
|
||||||
|
/// ```
|
||||||
|
pub fn toggle<T>(&mut self, direction: T) -> bool
|
||||||
|
where
|
||||||
|
T: Into<Self> + Copy,
|
||||||
|
{
|
||||||
|
let mask = direction.into().0;
|
||||||
|
let was_present = self.0 & mask != 0;
|
||||||
|
self.0 ^= mask;
|
||||||
|
was_present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EdgeDirection> for Walls {
|
||||||
|
fn from(value: EdgeDirection) -> Self {
|
||||||
|
Self(1 << value.index())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Walls {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
Self(1 << value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<EdgeDirection> for Walls {
|
||||||
|
fn from_iter<T: IntoIterator<Item = EdgeDirection>>(iter: T) -> Self {
|
||||||
|
let mut walls = 0u8;
|
||||||
|
for direction in iter {
|
||||||
|
walls |= 1 << direction.index();
|
||||||
|
}
|
||||||
|
Self(walls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> From<[EdgeDirection; N]> for Walls {
|
||||||
|
fn from(value: [EdgeDirection; N]) -> Self {
|
||||||
|
value.into_iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
69
assets/code/maze-ascension/floor.rs
Normal file
69
assets/code/maze-ascension/floor.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/// Move floor entities to their target Y positions based on movement speed
|
||||||
|
///
|
||||||
|
/// # Behavior
|
||||||
|
/// - Calculates movement distance based on player speed and delta time
|
||||||
|
/// - Moves floors towards their target Y position
|
||||||
|
/// - Removes FloorYTarget component when floor reaches destination
|
||||||
|
pub fn move_floors(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut maze_query: Query<
|
||||||
|
(Entity, &mut Transform, &FloorYTarget),
|
||||||
|
(With<HexMaze>, With<FloorYTarget>),
|
||||||
|
>,
|
||||||
|
player_query: Query<&MovementSpeed, With<Player>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
let speed = player_query.get_single().map_or(100., |s| s.0);
|
||||||
|
let movement_distance = speed * time.delta_secs();
|
||||||
|
for (entity, mut transform, movement_state) in maze_query.iter_mut() {
|
||||||
|
let delta = movement_state.0 - transform.translation.y;
|
||||||
|
if delta.abs() > MOVEMENT_THRESHOLD {
|
||||||
|
let movement = delta.signum() * movement_distance.min(delta.abs());
|
||||||
|
transform.translation.y += movement;
|
||||||
|
} else {
|
||||||
|
transform.translation.y = movement_state.0; // snap to final position
|
||||||
|
commands.entity(entity).remove::<FloorYTarget>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle floor transition events by setting up floor movement targets
|
||||||
|
///
|
||||||
|
/// # Behavior
|
||||||
|
/// - Checks if any floors are currently moving
|
||||||
|
/// - Processes floor transition events
|
||||||
|
/// - Sets target Y positions for all maze entities
|
||||||
|
/// - Updates current and next floor designations
|
||||||
|
pub fn handle_floor_transition_events(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut maze_query: Query<
|
||||||
|
(Entity, &Transform, Option<&FloorYTarget>),
|
||||||
|
With<HexMaze>
|
||||||
|
>,
|
||||||
|
current_query: Query<Entity, With<CurrentFloor>>,
|
||||||
|
next_query: Query<Entity, With<NextFloor>>,
|
||||||
|
mut event_reader: EventReader<TransitionFloor>,
|
||||||
|
) {
|
||||||
|
let is_moving = maze_query
|
||||||
|
.iter()
|
||||||
|
.any(|(_, _, movement_state)| movement_state.is_some());
|
||||||
|
if is_moving {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for event in event_reader.read() {
|
||||||
|
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() {
|
||||||
|
let target_y = (FLOOR_Y_OFFSET as f32).mul_add(direction, transforms.translation.y);
|
||||||
|
if movement_state.is_none() {
|
||||||
|
commands.entity(entity).insert(FloorYTarget(target_y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_current_next_floor(&mut commands, current_entity, next_entity);
|
||||||
|
break;
|
||||||
|
}
|
||||||
@ -1,88 +1,159 @@
|
|||||||
pub(super) fn setup(
|
/// Spawns a new maze floor in response to a SpawnMaze trigger event
|
||||||
|
pub(super) fn spawn_maze(
|
||||||
|
trigger: Trigger<SpawnMaze>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
config: Res<MazeConfig>,
|
maze_query: Query<(Entity, &Floor, &Maze)>,
|
||||||
layout: Res<Layout>,
|
global_config: Res<GlobalMazeConfig>,
|
||||||
) {
|
) {
|
||||||
let maze = MazeBuilder::new()
|
let SpawnMaze { floor, config } = trigger.event();
|
||||||
.with_radius(config.radius)
|
|
||||||
.with_seed(0)
|
|
||||||
.with_generator(GeneratorType::RecursiveBacktracking)
|
|
||||||
.build()
|
|
||||||
.expect("Something went wrong while creating maze");
|
|
||||||
|
|
||||||
let assets = create_base_assets(&mut meshes, &mut materials, &config);
|
if maze_query.iter().any(|(_, f, _)| f.0 == *floor) {
|
||||||
commands
|
warn!("Floor {} already exists, skipping creation", floor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let maze = match generate_maze(config) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to generate maze for floor {floor}: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate vertical offset based on floor number
|
||||||
|
let y_offset = match *floor {
|
||||||
|
1 => 0, // Ground/Initial floor (floor 1) is at y=0
|
||||||
|
_ => FLOOR_Y_OFFSET, // Other floors are offset vertically
|
||||||
|
} as f32;
|
||||||
|
|
||||||
|
let entity = commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Name::new("Floor"),
|
Name::new(format!("Floor {}", floor)),
|
||||||
SpatialBundle {
|
HexMaze,
|
||||||
transform: Transform::from_translation(Vec3::ZERO),
|
maze.clone(),
|
||||||
..default()
|
Floor(*floor),
|
||||||
},
|
config.clone(),
|
||||||
|
Transform::from_translation(Vec3::ZERO.with_y(y_offset)),
|
||||||
|
Visibility::Visible,
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.insert_if(CurrentFloor, || *floor == 1) // Only floor 1 gets CurrentFloor
|
||||||
|
.insert_if(NextFloor, || *floor != 1) // All other floors get NextFloor
|
||||||
|
.id();
|
||||||
|
|
||||||
|
let assets = MazeAssets::new(&mut meshes, &mut materials, &global_config);
|
||||||
|
spawn_maze_tiles(
|
||||||
|
&mut commands,
|
||||||
|
entity,
|
||||||
|
&maze,
|
||||||
|
&assets,
|
||||||
|
config,
|
||||||
|
&global_config,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns all tiles for a maze as children of the parent maze entity
|
||||||
|
pub fn spawn_maze_tiles(
|
||||||
|
commands: &mut Commands,
|
||||||
|
parent_entity: Entity,
|
||||||
|
maze: &Maze,
|
||||||
|
assets: &MazeAssets,
|
||||||
|
maze_config: &MazeConfig,
|
||||||
|
global_config: &GlobalMazeConfig,
|
||||||
|
) {
|
||||||
|
commands.entity(parent_entity).with_children(|parent| {
|
||||||
for tile in maze.values() {
|
for tile in maze.values() {
|
||||||
spawn_single_hex_tile(
|
spawn_single_hex_tile(
|
||||||
parent,
|
parent,
|
||||||
&assets,
|
assets,
|
||||||
tile,
|
tile,
|
||||||
&layout.0,
|
maze_config,
|
||||||
config.height,
|
global_config,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_single_hex_tile(
|
/// Spawns a single hexagonal tile with appropriate transforms and materials
|
||||||
|
pub(super) fn spawn_single_hex_tile(
|
||||||
parent: &mut ChildBuilder,
|
parent: &mut ChildBuilder,
|
||||||
assets: &MazeAssets,
|
assets: &MazeAssets,
|
||||||
tile: &HexTile,
|
tile: &HexTile,
|
||||||
layout: &HexLayout,
|
maze_config: &MazeConfig,
|
||||||
hex_height: f32,
|
global_config: &GlobalMazeConfig,
|
||||||
) {
|
) {
|
||||||
dbg!(tile);
|
let world_pos = tile.to_vec3(&maze_config.layout);
|
||||||
let world_pos = tile.to_vec3(layout);
|
let rotation = match maze_config.layout.orientation {
|
||||||
let rotation = match layout.orientation {
|
// Pointy hexagons don't need additional rotation (0 degrees)
|
||||||
HexOrientation::Pointy => Quat::from_rotation_y(0.0),
|
HexOrientation::Pointy => Quat::from_rotation_y(0.0),
|
||||||
HexOrientation::Flat => Quat::from_rotation_y(FRAC_PI_6), // 30 degrees rotation
|
// Flat-top hexagons need 30 degrees (pi/6) rotation around Y axis
|
||||||
|
HexOrientation::Flat => Quat::from_rotation_y(FRAC_PI_6),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Select material based on tile position: start, end, or default
|
||||||
|
let material = match tile.pos() {
|
||||||
|
pos if pos == maze_config.start_pos => assets
|
||||||
|
.custom_materials
|
||||||
|
.get(&RosePine::Pine)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
pos if pos == maze_config.end_pos => assets
|
||||||
|
.custom_materials
|
||||||
|
.get(&RosePine::Love)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
_ => assets.hex_material.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
parent
|
parent
|
||||||
.spawn((
|
.spawn((
|
||||||
Name::new(format!("Hex {}", tile.to_string())),
|
Name::new(format!("Hex {}", tile)),
|
||||||
PbrBundle {
|
Tile,
|
||||||
mesh: assets.hex_mesh.clone(),
|
Mesh3d(assets.hex_mesh.clone()),
|
||||||
material: assets.hex_material.clone(),
|
MeshMaterial3d(material),
|
||||||
transform: Transform::from_translation(world_pos)
|
Transform::from_translation(world_pos).with_rotation(rotation),
|
||||||
.with_rotation(rotation),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
spawn_walls(parent, assets, hex_height / 2., &tile.walls())
|
spawn_walls(parent, assets, tile.walls(), global_config)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawns walls around a hexagonal tile based on the walls configuration
|
||||||
fn spawn_walls(
|
fn spawn_walls(
|
||||||
parent: &mut ChildBuilder,
|
parent: &mut ChildBuilder,
|
||||||
assets: &MazeAssets,
|
assets: &MazeAssets,
|
||||||
y_offset: f32,
|
|
||||||
walls: &Walls,
|
walls: &Walls,
|
||||||
|
global_config: &GlobalMazeConfig,
|
||||||
) {
|
) {
|
||||||
|
// Base rotation for wall alignment (90 degrees counter-clockwise)
|
||||||
let z_rotation = Quat::from_rotation_z(-FRAC_PI_2);
|
let z_rotation = Quat::from_rotation_z(-FRAC_PI_2);
|
||||||
|
let y_offset = global_config.height / 2.;
|
||||||
|
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
if !walls.contains(i) {
|
if !walls.contains(i) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the angle for this wall
|
||||||
|
// FRAC_PI_3 = 60 deg
|
||||||
|
// Negative because going clockwise
|
||||||
|
// i * 60 produces: 0, 60, 120, 180, 240, 300
|
||||||
let wall_angle = -FRAC_PI_3 * i as f32;
|
let wall_angle = -FRAC_PI_3 * i as f32;
|
||||||
|
|
||||||
let x_offset = (HEX_SIZE - WALL_SIZE) * f32::cos(wall_angle);
|
// cos(angle) gives x coordinate on unit circle
|
||||||
let z_offset = (HEX_SIZE - WALL_SIZE) * f32::sin(wall_angle);
|
// sin(angle) gives z coordinate on unit circle
|
||||||
|
// Multiply by wall_offset to get actual distance from center
|
||||||
|
let x_offset = global_config.wall_offset() * f32::cos(wall_angle);
|
||||||
|
let z_offset = global_config.wall_offset() * f32::sin(wall_angle);
|
||||||
|
|
||||||
|
// x: distance along x-axis from center
|
||||||
|
// y: vertical offset from center
|
||||||
|
// z: distance along z-axis from center
|
||||||
let pos = Vec3::new(x_offset, y_offset, z_offset);
|
let pos = Vec3::new(x_offset, y_offset, z_offset);
|
||||||
|
|
||||||
|
// 1. Rotate around x-axis to align wall with angle
|
||||||
|
// 2. Add FRAC_PI_2 (90) to make wall perpendicular to angle
|
||||||
let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2);
|
let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2);
|
||||||
let final_rotation = z_rotation * x_rotation;
|
let final_rotation = z_rotation * x_rotation;
|
||||||
|
|
||||||
@ -90,52 +161,18 @@ fn spawn_walls(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawns a single wall segment with the specified rotation and position
|
||||||
fn spawn_single_wall(
|
fn spawn_single_wall(
|
||||||
parent: &mut ChildBuilder,
|
parent: &mut ChildBuilder,
|
||||||
asstets: &MazeAssets,
|
assets: &MazeAssets,
|
||||||
rotation: Quat,
|
rotation: Quat,
|
||||||
offset: Vec3,
|
offset: Vec3,
|
||||||
) {
|
) {
|
||||||
parent.spawn((
|
parent.spawn((
|
||||||
Name::new("Wall"),
|
Name::new("Wall"),
|
||||||
PbrBundle {
|
Wall,
|
||||||
mesh: asstets.wall_mesh.clone(),
|
Mesh3d(assets.wall_mesh.clone()),
|
||||||
material: asstets.wall_material.clone(),
|
MeshMaterial3d(assets.wall_material.clone()),
|
||||||
transform: Transform::from_translation(offset)
|
Transform::from_translation(offset).with_rotation(rotation),
|
||||||
.with_rotation(rotation),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_base_assets(
|
|
||||||
meshes: &mut ResMut<Assets<Mesh>>,
|
|
||||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
|
||||||
config: &Res<MazeConfig>,
|
|
||||||
) -> MazeAssets {
|
|
||||||
MazeAssets {
|
|
||||||
hex_mesh: meshes.add(generate_hex_mesh(HEX_SIZE, config.height)),
|
|
||||||
wall_mesh: meshes.add(generate_square_mesh(HEX_SIZE)),
|
|
||||||
hex_material: materials.add(white_material()),
|
|
||||||
wall_material: materials.add(Color::BLACK),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_hex_mesh(radius: f32, depth: f32) -> Mesh {
|
|
||||||
let hexagon = RegularPolygon {
|
|
||||||
sides: 6,
|
|
||||||
circumcircle: Circle::new(radius),
|
|
||||||
};
|
|
||||||
let prism_shape = Extrusion::new(hexagon, depth);
|
|
||||||
let rotation = Quat::from_rotation_x(FRAC_PI_2);
|
|
||||||
|
|
||||||
Mesh::from(prism_shape).rotated_by(rotation)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_square_mesh(depth: f32) -> Mesh {
|
|
||||||
let square = Rectangle::new(WALL_SIZE, WALL_SIZE);
|
|
||||||
let rectangular_prism = Extrusion::new(square, depth);
|
|
||||||
let rotation = Quat::from_rotation_x(FRAC_PI_2);
|
|
||||||
|
|
||||||
Mesh::from(rectangular_prism).rotated_by(rotation)
|
|
||||||
}
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 428 KiB |
@ -7,7 +7,7 @@ typst:
|
|||||||
- Haug
|
- Haug
|
||||||
- Martin
|
- Martin
|
||||||
- Typst Projekta Izstrādātāji
|
- Typst Projekta Izstrādātāji
|
||||||
url: {value: "https://typst.app/", date: 2024-12-20}
|
url: "https://typst.app/"
|
||||||
hex-grid:
|
hex-grid:
|
||||||
type: Web
|
type: Web
|
||||||
title: Hexagonal Grids
|
title: Hexagonal Grids
|
||||||
@ -44,11 +44,6 @@ maze-generation:
|
|||||||
title: Maze Generation
|
title: Maze Generation
|
||||||
author:
|
author:
|
||||||
url: https://rosettacode.org/wiki/Maze_generation
|
url: https://rosettacode.org/wiki/Maze_generation
|
||||||
bevy-quickstart:
|
|
||||||
type: Web
|
|
||||||
title: Bevy New 2D
|
|
||||||
author:
|
|
||||||
url: https://github.com/TheBevyFlock/bevy_new_2d
|
|
||||||
sem-ver:
|
sem-ver:
|
||||||
type: Web
|
type: Web
|
||||||
title: Semantiskā versiju veidošana
|
title: Semantiskā versiju veidošana
|
||||||
@ -84,7 +79,7 @@ the-rust-performance-book:
|
|||||||
url: https://nnethercote.github.io/perf-book
|
url: https://nnethercote.github.io/perf-book
|
||||||
cargo-tarpaulin:
|
cargo-tarpaulin:
|
||||||
type: Web
|
type: Web
|
||||||
title: Tarpaulin
|
title: Tarpaulin rīks
|
||||||
author: xd009642
|
author: xd009642
|
||||||
url: {value: "https://crates.io/crates/cargo-tarpaulin", date: 2024-12-18}
|
url: {value: "https://crates.io/crates/cargo-tarpaulin", date: 2024-12-18}
|
||||||
ecs:
|
ecs:
|
||||||
@ -94,7 +89,7 @@ ecs:
|
|||||||
url: {value: "https://en.wikipedia.org/wiki/Entity_component_system", date: 2024-09-12}
|
url: {value: "https://en.wikipedia.org/wiki/Entity_component_system", date: 2024-09-12}
|
||||||
bevy-ecs:
|
bevy-ecs:
|
||||||
type: Web
|
type: Web
|
||||||
title: Bevy ECS
|
title: Ievads Bevy ECS
|
||||||
author: Bevy Projekta Izstādātāji
|
author: Bevy Projekta Izstādātāji
|
||||||
url: {value: "https://bevyengine.org/learn/quick-start/getting-started/ecs/", date: 2024-09-12}
|
url: {value: "https://bevyengine.org/learn/quick-start/getting-started/ecs/", date: 2024-09-12}
|
||||||
SRP:
|
SRP:
|
||||||
@ -117,12 +112,12 @@ begginer-patterns:
|
|||||||
url: "https://pressbooks.lib.jmu.edu/programmingpatterns/"
|
url: "https://pressbooks.lib.jmu.edu/programmingpatterns/"
|
||||||
clippy:
|
clippy:
|
||||||
type: Web
|
type: Web
|
||||||
title: Clippy
|
title: Clippy dokumentācija
|
||||||
author: Rust Projekta Izstādātāji
|
author: Rust Projekta Izstādātāji
|
||||||
url: https://doc.rust-lang.org/stable/clippy/
|
url: https://doc.rust-lang.org/stable/clippy/
|
||||||
cargo-doc:
|
cargo-doc:
|
||||||
type: Web
|
type: Web
|
||||||
title: cargo-doc
|
title: cargo-doc dokumentācija
|
||||||
author: Rust Projekta Izstādātāji
|
author: Rust Projekta Izstādātāji
|
||||||
url: https://doc.rust-lang.org/stable/cargo/
|
url: https://doc.rust-lang.org/stable/cargo/
|
||||||
rust-style:
|
rust-style:
|
||||||
@ -137,12 +132,12 @@ rust-lang-doc:
|
|||||||
url: https://doc.rust-lang.org/stable/
|
url: https://doc.rust-lang.org/stable/
|
||||||
rustfmt:
|
rustfmt:
|
||||||
type: Web
|
type: Web
|
||||||
title: Rustfmt
|
title: Rustfmt dokumentācija
|
||||||
author: Rust Projekta Izstādātāji
|
author: Rust Projekta Izstādātāji
|
||||||
url: https://github.com/rust-lang/rustfmt
|
url: https://github.com/rust-lang/rustfmt
|
||||||
gh-release:
|
gh-release:
|
||||||
type: Web
|
type: Web
|
||||||
title: About Releases
|
title: About Releases dokumentācija
|
||||||
author: GitHub komanda
|
author: GitHub komanda
|
||||||
url: https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases
|
url: https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases
|
||||||
gh-actions:
|
gh-actions:
|
||||||
@ -152,13 +147,13 @@ gh-actions:
|
|||||||
url: https://docs.github.com/en/actions
|
url: https://docs.github.com/en/actions
|
||||||
tokei:
|
tokei:
|
||||||
type: Web
|
type: Web
|
||||||
title: tokei
|
title: Tokei rīks
|
||||||
author: XAMPPRocky
|
author: XAMPPRocky
|
||||||
url: https://crates.io/crates/tokei
|
url: https://crates.io/crates/tokei
|
||||||
QSM:
|
QSM:
|
||||||
type: Web
|
type: Web
|
||||||
title: Software Project Performance Benchmark Tables
|
title: Software Project Performance Benchmark Tables
|
||||||
author: QSM, Inc.
|
author: QSM
|
||||||
url: https://www.qsm.com/resources/qsm-benchmark-tables
|
url: https://www.qsm.com/resources/qsm-benchmark-tables
|
||||||
bevy-0.15:
|
bevy-0.15:
|
||||||
type: Web
|
type: Web
|
||||||
|
|||||||
30
code.typ
30
code.typ
@ -1,18 +1,20 @@
|
|||||||
#import "utils.typ": *
|
#import "utils.typ": *
|
||||||
|
|
||||||
"hexlab" bibliotēkas datu struktūras un svarīgās funkcijas.
|
#codeblock(
|
||||||
|
[Labirinta būvētāja implementācijas piemērs],
|
||||||
|
"assets/code/hexlab/builder.rs",
|
||||||
|
)
|
||||||
|
#codeblock(
|
||||||
|
[Labirinta sienu reprezentācijas piemērs],
|
||||||
|
"assets/code/hexlab/walls.rs",
|
||||||
|
)
|
||||||
|
|
||||||
|
#codeblock(
|
||||||
|
[Labirinta stāvu implementācijas piemērs],
|
||||||
|
"assets/code/maze-ascension/floor.rs",
|
||||||
|
)
|
||||||
|
|
||||||
#context {
|
#codeblock(
|
||||||
set par.line(numbering: "1")
|
[Labirinta ģenerācijas implementācijas piemērs],
|
||||||
codeblock("./assets/code/hexlab/structs.rs", "rust")
|
"assets/code/maze-ascension/maze_generation.rs",
|
||||||
codeblock("./assets/code/hexlab/generation.rs", "rust")
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Spēles datu struktūras un svarīgās funkcijas.
|
|
||||||
|
|
||||||
#context {
|
|
||||||
set par.line(numbering: "1")
|
|
||||||
codeblock("./assets/code/maze-ascension/components.rs", "rust")
|
|
||||||
codeblock("./assets/code/maze-ascension/maze_generation.rs", "rust")
|
|
||||||
}
|
|
||||||
|
|||||||
18
doc.typ
18
doc.typ
@ -9,11 +9,13 @@
|
|||||||
#heading(numbering: none, outlined: false, "Dokumentārā lapa")
|
#heading(numbering: none, outlined: false, "Dokumentārā lapa")
|
||||||
|
|
||||||
Kvalifikācijas darbs "*Spēles izstrāde, izmantojot Bevy spēļu dzinēju*" ir
|
Kvalifikācijas darbs "*Spēles izstrāde, izmantojot Bevy spēļu dzinēju*" ir
|
||||||
izstrādāts Latvijas Universitātes eksakto zinātņu un tehnoloģiju fakultātē.
|
izstrādāts Latvijas Universitātes Eksakto zinātņu un tehnoloģiju fakultātē,
|
||||||
|
Datorikas nodaļā.
|
||||||
|
|
||||||
|
#v(vspace / 3)
|
||||||
Ar savu parakstu apliecinu, ka darbs izstrādāts patstāvīgi, izmantoti tikai tajā
|
Ar savu parakstu apliecinu, ka darbs izstrādāts patstāvīgi, izmantoti tikai tajā
|
||||||
norādītie informācijas avoti un iesniegtā darba elektroniskā kopija atbilst
|
norādītie informācijas avoti un iesniegtā darba elektroniskā kopija atbilst
|
||||||
izdrukai.
|
izdrukai un/vai recenzentam uzrādītajai darba versijai.
|
||||||
|
|
||||||
|
|
||||||
#context {
|
#context {
|
||||||
@ -23,8 +25,8 @@ izdrukai.
|
|||||||
hanging-indent: 1cm,
|
hanging-indent: 1cm,
|
||||||
)
|
)
|
||||||
|
|
||||||
v(vspace)
|
v(vspace / 2)
|
||||||
[Darba autors: *Kristiāns Francis Cagulis, kc22015 ~~\_\_.01.2025.*]
|
[Autors: *Kristiāns Francis Cagulis, kc22015 ~~\_\_.01.2025.*]
|
||||||
|
|
||||||
v(vspace)
|
v(vspace)
|
||||||
[Rekomendēju darbu aizstāvēšanai\
|
[Rekomendēju darbu aizstāvēšanai\
|
||||||
@ -36,14 +38,8 @@ izdrukai.
|
|||||||
|
|
||||||
v(vspace)
|
v(vspace)
|
||||||
[Darbs iesniegs *\_\_.01.2025.*\
|
[Darbs iesniegs *\_\_.01.2025.*\
|
||||||
Kvalifikācijas darbu pārbaudījumu komisijas sekretārs(-e): #long-underline
|
Kvalifikācijas darbu pārbaudījumu komisijas sekretārs (elektronisks paraksts)
|
||||||
]
|
]
|
||||||
|
|
||||||
v(vspace)
|
v(vspace)
|
||||||
[Darbs aizstāvēts kvalifikācijas darbu pārbaudījuma komisijas sēdē\
|
|
||||||
\_\_.01.2025. prot. Nr. #long-underline
|
|
||||||
]
|
|
||||||
v(vspace / 2)
|
|
||||||
[Komisijas sekretārs(-e): #long-underline]
|
|
||||||
v(vspace)
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
layout.typ
18
layout.typ
@ -39,9 +39,10 @@
|
|||||||
lang: "lv",
|
lang: "lv",
|
||||||
region: "lv",
|
region: "lv",
|
||||||
)
|
)
|
||||||
show raw: set text(font: "JetBrainsMono NF")
|
show raw: set text(
|
||||||
|
font: "Fira Code",
|
||||||
show raw.where(lang: "pintora"): it => pintorita.render(it.text)
|
features: (calt: 0),
|
||||||
|
)
|
||||||
|
|
||||||
show math.equation: set text(weight: 400)
|
show math.equation: set text(weight: 400)
|
||||||
|
|
||||||
@ -172,8 +173,7 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
// WARNING: remove before sending
|
// WARNING: remove before sending
|
||||||
outline(title: "TODOs", target: figure.where(kind: "todo"))
|
// outline(title: "TODOs", target: figure.where(kind: "todo"))
|
||||||
|
|
||||||
/* --- Figure/Table config start --- */
|
/* --- Figure/Table config start --- */
|
||||||
show heading: i-figured.reset-counters
|
show heading: i-figured.reset-counters
|
||||||
show figure: i-figured.show-figure.with(numbering: "1.1.")
|
show figure: i-figured.show-figure.with(numbering: "1.1.")
|
||||||
@ -183,6 +183,8 @@
|
|||||||
show figure.where(kind: "i-figured-table"): set block(breakable: true)
|
show figure.where(kind: "i-figured-table"): set block(breakable: true)
|
||||||
show figure.where(kind: "i-figured-table"): set figure.caption(position: top)
|
show figure.where(kind: "i-figured-table"): set figure.caption(position: top)
|
||||||
show figure.where(kind: "attachment"): set figure.caption(position: top)
|
show figure.where(kind: "attachment"): set figure.caption(position: top)
|
||||||
|
show figure.where(kind: raw): set figure.caption(position: top)
|
||||||
|
|
||||||
|
|
||||||
show figure: set par(justify: false) // disable justify for figures (tables)
|
show figure: set par(justify: false) // disable justify for figures (tables)
|
||||||
show figure.where(kind: table): set par(leading: 1em)
|
show figure.where(kind: table): set par(leading: 1em)
|
||||||
@ -208,7 +210,10 @@
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if it.kind == "i-figured-\"attachment\"" {
|
if it.kind in (
|
||||||
|
"i-figured-raw",
|
||||||
|
"i-figured-\"attachment\"",
|
||||||
|
) {
|
||||||
return align(
|
return align(
|
||||||
end,
|
end,
|
||||||
it.counter.display() + ". pielikums. " + text(it.body),
|
it.counter.display() + ". pielikums. " + text(it.body),
|
||||||
@ -283,7 +288,6 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Default case for non-figure elements
|
// Default case for non-figure elements
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
|||||||
61
main.typ
61
main.typ
@ -20,8 +20,6 @@
|
|||||||
)
|
)
|
||||||
#set heading(numbering: none)
|
#set heading(numbering: none)
|
||||||
= Apzīmējumu saraksts
|
= Apzīmējumu saraksts
|
||||||
/ Šūna:
|
|
||||||
|
|
||||||
/ Audio: Skaņas komponentes, kas ietver gan skaņas efektus, gan fona mūziku;
|
/ Audio: Skaņas komponentes, kas ietver gan skaņas efektus, gan fona mūziku;
|
||||||
/ CI/CD: nepārtraukta integrācija un nepārtraukta izvietošana;
|
/ CI/CD: nepārtraukta integrācija un nepārtraukta izvietošana;
|
||||||
/ DPD: datu plūsmas diagramma;
|
/ DPD: datu plūsmas diagramma;
|
||||||
@ -38,7 +36,8 @@
|
|||||||
/ Režģis: Strukturēts šūnu izkārtojums, kas veido spēles pasaules pamata struktūru;
|
/ Režģis: Strukturēts šūnu izkārtojums, kas veido spēles pasaules pamata struktūru;
|
||||||
/ Spēlētājs: lietotāja ieraksts vienas virtuālās istabas kontekstā;
|
/ Spēlētājs: lietotāja ieraksts vienas virtuālās istabas kontekstā;
|
||||||
/ Sēkla: Skaitliska vērtība, ko izmanto nejaušo skaitļu ģeneratora inicializēšanai;
|
/ Sēkla: Skaitliska vērtība, ko izmanto nejaušo skaitļu ģeneratora inicializēšanai;
|
||||||
/ Šūna: Sešstūraina režģa viena pozīcija, kas definē telpu, kuru var aizņemt viena plāksne;
|
/ Šūna: Sešstūraina režģa viena pozīcija, kas definē telpu, kuru var aizņemt viena plāksne.
|
||||||
|
/ WASM: WebAssembly -- zema līmeņa assemblera tipa kods, kas var darboties modernos tīmekļa pārlūkos.
|
||||||
|
|
||||||
= Ievads
|
= Ievads
|
||||||
== Nolūks
|
== Nolūks
|
||||||
@ -51,13 +50,6 @@ procedurālu labirintu ģenerēšanu, spēlētāju navigācijas sistēmu, papild
|
|||||||
integrāciju un vertikālās progresijas mehāniku, vienlaikus ievērojot minimālisma
|
integrāciju un vertikālās progresijas mehāniku, vienlaikus ievērojot minimālisma
|
||||||
dizaina filozofiju.
|
dizaina filozofiju.
|
||||||
|
|
||||||
// Spēles pamatā ir sešstūra formas šūnas, kas, savukārt, veido sešstūra
|
|
||||||
// formas labirintus, kuri rada atšķirīgu vizuālo un navigācijas izaicinājumu.
|
|
||||||
// Spēlētāju uzdevums ir pārvietoties pa šiem labirintiem, lai sasniegtu katra
|
|
||||||
// līmeņa beigas. Spēlētājiem progresējot, tie sastopas ar arvien sarežģītākiem
|
|
||||||
// labirintiem, kuros nepieciešama stratēģiska domāšana, izpēte un papildspēju
|
|
||||||
// izmantošana.
|
|
||||||
|
|
||||||
Spēles pamatā ir procedurāli ģenerēti sešstūra labirinti, kas katrā spēlē rada
|
Spēles pamatā ir procedurāli ģenerēti sešstūra labirinti, kas katrā spēlē rada
|
||||||
unikālu vizuālo un navigācijas izaicinājumu. Procedurālās ģenerēšanas sistēma
|
unikālu vizuālo un navigācijas izaicinājumu. Procedurālās ģenerēšanas sistēma
|
||||||
nodrošina, ka:
|
nodrošina, ka:
|
||||||
@ -96,8 +88,8 @@ tehnisko iespējamību.
|
|||||||
|
|
||||||
== Saistība ar citiem dokumentiem
|
== Saistība ar citiem dokumentiem
|
||||||
PPS ir izstrādāta, ievērojot LVS 68:1996 "Programmatūras prasību specifikācijas
|
PPS ir izstrādāta, ievērojot LVS 68:1996 "Programmatūras prasību specifikācijas
|
||||||
ceļvedis" un LVS 72:1996 "Ieteicamā prakse programmatūras projektējuma
|
ceļvedis" @lvs_68 un LVS 72:1996 "Ieteicamā prakse programmatūras projektējuma
|
||||||
aprakstīšanai" standarta prasības @lvs_68 @lvs_72.
|
aprakstīšanai" standarta prasības @lvs_72.
|
||||||
|
|
||||||
== Pārskats
|
== Pārskats
|
||||||
Šis dokuments sniedz detalizētu programmatūras prasību specifikāciju spēlei
|
Šis dokuments sniedz detalizētu programmatūras prasību specifikāciju spēlei
|
||||||
@ -248,14 +240,18 @@ Ar lietotājiem saistītās datu plūsmas ir attēlotas sistēmas nultā līmeņ
|
|||||||
+ Programmēšanas valodas un Bevy spēles dzinēja tehniskie ierobežojumi;
|
+ Programmēšanas valodas un Bevy spēles dzinēja tehniskie ierobežojumi;
|
||||||
+ Responsivitāte;
|
+ Responsivitāte;
|
||||||
+ Starpplatformu savietojamība: Linux, macOS, Windows un WebAssembly.
|
+ Starpplatformu savietojamība: Linux, macOS, Windows un WebAssembly.
|
||||||
// + Izplatīšanas un izvietošanas ierobežojumi:
|
|
||||||
// + CI/CD darbplūsma.
|
#indent-par[
|
||||||
|
Dokumentācijas izstrādei ir izmantots Typst rīks, kas nodrošina efektīvu darbu
|
||||||
|
ar tehnisko dokumentāciju, ieskaitot matemātiskas formulas, diagrammas un koda
|
||||||
|
fragmentus @typst.
|
||||||
|
]
|
||||||
|
|
||||||
== Pieņēmumi un atkarības
|
== Pieņēmumi un atkarības
|
||||||
- Tehniskie pieņēmumi:
|
- Tehniskie pieņēmumi:
|
||||||
- Spēlētāja ierīcei jāatbilst minimālajām aparatūras prasībām, lai varētu
|
- Spēlētāja ierīcei jāatbilst minimālajām aparatūras prasībām, lai varētu
|
||||||
palaist uz Bevy spēles dzinēja balstītas spēles.
|
palaist uz Bevy spēles dzinēja balstītas spēles.
|
||||||
- ierīcei jāatbalsta WebGL2,#footnote("https://registry.khronos.org/webgl/specs/latest/2.0/")
|
- ierīcei jāatbalsta WebGL2 #footnote("https://registry.khronos.org/webgl/specs/latest/2.0/"),
|
||||||
lai nodrošinātu pareizu atveidošanu @webgl2.
|
lai nodrošinātu pareizu atveidošanu @webgl2.
|
||||||
- tīmekļa spēļu spēlēšanai (WebAssembly versija) pārlūkprogrammai jābūt mūsdienīgai un saderīgai ar WebAssembly.
|
- tīmekļa spēļu spēlēšanai (WebAssembly versija) pārlūkprogrammai jābūt mūsdienīgai un saderīgai ar WebAssembly.
|
||||||
- ekrāna izšķirtspējai jābūt vismaz 800x600 pikseļu, lai spēle būtu optimāla.
|
- ekrāna izšķirtspējai jābūt vismaz 800x600 pikseļu, lai spēle būtu optimāla.
|
||||||
@ -1700,9 +1696,7 @@ projekta lietojuma gadījumam sekojošu iemeslu dēļ:
|
|||||||
|
|
||||||
== Saskarņu projektējums
|
== Saskarņu projektējums
|
||||||
Spēles saskarņu projektējums ietver divus galvenos skatus (sk. @fig:ui-flow) --
|
Spēles saskarņu projektējums ietver divus galvenos skatus (sk. @fig:ui-flow) --
|
||||||
galveno izvēlni, spēles saskarni -- un izstrādes rīkus.
|
galveno izvēlni un spēles saskarni -- un izstrādes rīkus.
|
||||||
Katra saskarne ir veidota, ņemot vērā tās specifisko lietojuma gadījumu un
|
|
||||||
lietotāju vajadzības.
|
|
||||||
|
|
||||||
#figure(
|
#figure(
|
||||||
caption: "Ekrānskatu plūsmu diagramma",
|
caption: "Ekrānskatu plūsmu diagramma",
|
||||||
@ -1714,7 +1708,7 @@ lietotāju vajadzības.
|
|||||||
stroke: 1pt,
|
stroke: 1pt,
|
||||||
"<|-|>",
|
"<|-|>",
|
||||||
)
|
)
|
||||||
action-node((1, 0), [Galvenais ekrāns], inset: 2em)
|
action-node((1, 0), [Spēles ekrāns], inset: 2em)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
) <ui-flow>
|
) <ui-flow>
|
||||||
@ -1725,8 +1719,6 @@ Galvenā izvēlne ir pirmais skats, ar ko saskaras lietotājs, uzsākot spēli (
|
|||||||
@fig:main-menu).
|
@fig:main-menu).
|
||||||
Tā sastāv no spēles nosaukuma, "Play" -- sākt spēli pogas un "Quit" -- iziet
|
Tā sastāv no spēles nosaukuma, "Play" -- sākt spēli pogas un "Quit" -- iziet
|
||||||
pogas.
|
pogas.
|
||||||
Izvēlnes dizains ir minimālistisks un intuitīvs, izmantojot kontrastējošas
|
|
||||||
krāsas un skaidru vizuālo hierarhiju.
|
|
||||||
|
|
||||||
#figure(
|
#figure(
|
||||||
caption: "Galvenās izvēlnes skats",
|
caption: "Galvenās izvēlnes skats",
|
||||||
@ -1957,8 +1949,8 @@ no drošina piemēra koda pareizību, moduļu testi pārbauda iekšējo
|
|||||||
funkcionalitāti, savukārt testu mapē esošie vienībtesti un integrācijas testi
|
funkcionalitāti, savukārt testu mapē esošie vienībtesti un integrācijas testi
|
||||||
pārbauda sarežģītākus gadījumus.
|
pārbauda sarežģītākus gadījumus.
|
||||||
Automatizēto testu izpildes rezultātu kopsavilkums ir redzams
|
Automatizēto testu izpildes rezultātu kopsavilkums ir redzams
|
||||||
@fig:tests-hexlab[attēlā], savukārt detalizēts testu izpildes pārskats ir
|
|
||||||
pieejams @tests-hexlab-full[pielikumā].
|
pieejams @tests-hexlab-full[pielikumā].
|
||||||
|
@fig:tests-hexlab[attēlā], savukārt detalizēts testu izpildes pārskats ir
|
||||||
|
|
||||||
Izmantojot "cargo-tarpaulin", testu pārklājums ir $81.69%$ (116 no 142
|
Izmantojot "cargo-tarpaulin", testu pārklājums ir $81.69%$ (116 no 142
|
||||||
iekļautajām rindiņām) (sk. @tarpaulin-hexlab[pielikumu]), tomēr šis rādītājs
|
iekļautajām rindiņām) (sk. @tarpaulin-hexlab[pielikumu]), tomēr šis rādītājs
|
||||||
@ -1971,15 +1963,14 @@ funkcijām un citi tehniski ierobežojumi @cargo-tarpaulin.
|
|||||||
image("assets/images/tests/hexlab-minimized.png"),
|
image("assets/images/tests/hexlab-minimized.png"),
|
||||||
)<tests-hexlab>
|
)<tests-hexlab>
|
||||||
|
|
||||||
|
#indent-par[
|
||||||
Arī spēles kods saglabā stabilu testēšanas stratēģiju.
|
Arī spēles kods saglabā stabilu testēšanas stratēģiju.
|
||||||
Dokumentācijas testi tiek rakstīti tieši koda dokumentācijā, kalpojot diviem
|
|
||||||
mērķiem -- tie pārbauda koda pareizību un vienlaikus sniedz skaidrus lietošanas
|
|
||||||
piemērus turpmākai uzturēšanai.
|
|
||||||
Moduļu testi ir stratēģiski izvietoti līdzās implementācijas kodam tajā pašā
|
Moduļu testi ir stratēģiski izvietoti līdzās implementācijas kodam tajā pašā
|
||||||
failā, nodrošinot, ka katras komponentes funkcionalitāte tiek pārbaudīta
|
failā, nodrošinot, ka katras komponentes funkcionalitāte tiek pārbaudīta
|
||||||
izolēti.
|
izolēti.
|
||||||
Šie testi attiecas uz tādām svarīgām spēles sistēmām kā spēlētāju kustība,
|
Šie testi attiecas uz tādām svarīgām spēles sistēmām kā spēlētāju kustība,
|
||||||
sadursmju noteikšana, spēles stāvokļa pārvaldība u.c.
|
sadursmju noteikšana, spēles stāvokļa pārvaldība u.c.
|
||||||
|
]
|
||||||
|
|
||||||
Visi testi tiek automātiski izpildīti kā nepārtrauktas integrācijas procesa
|
Visi testi tiek automātiski izpildīti kā nepārtrauktas integrācijas procesa
|
||||||
daļa, nodrošinot tūlītēju atgriezenisko saiti par sistēmas stabilitāti un
|
daļa, nodrošinot tūlītēju atgriezenisko saiti par sistēmas stabilitāti un
|
||||||
@ -2014,11 +2005,10 @@ dokumentētas#footnote[https://docs.rs/hexlab/latest/hexlab/]<hexlab-docs>.
|
|||||||
Šajā dokumentācijā ir ietverti detalizēti apraksti un lietošanas piemēri, kas ne
|
Šajā dokumentācijā ir ietverti detalizēti apraksti un lietošanas piemēri, kas ne
|
||||||
tikai palīdz saprast kodu, bet programmatūras prasības specifikācija ir
|
tikai palīdz saprast kodu, bet programmatūras prasības specifikācija ir
|
||||||
izstrādāta, ievērojot LVS 68:1996 standarta "Programmatūras prasību
|
izstrādāta, ievērojot LVS 68:1996 standarta "Programmatūras prasību
|
||||||
specifikācijas ceļvedis" un LVS 72:1996 standarta "Ieteicamā prakse
|
specifikācijas ceļvedis" @lvs_68 un LVS 72:1996 standarta "Ieteicamā prakse
|
||||||
programmatūras projektējuma aprakstīšanai" standarta prasības @lvs_68 @lvs_72.
|
programmatūras projektējuma aprakstīšanai" standarta prasības @lvs_72.
|
||||||
// Programmatūras projektējuma aprakstā iekļautās
|
Programmatūras projektējuma aprakstā iekļautās aktivitāšu diagrammas ir veidotas
|
||||||
// aktivitāšu diagrammas ir izstrādātas, ievērojot UML 2.5 versijas
|
atbilstoši UML (Unified Modeling Language) 2.5 specifikācijai @omg-uml.
|
||||||
// specifikāciju@omg-uml.
|
|
||||||
|
|
||||||
== Konfigurācijas pārvaldība
|
== Konfigurācijas pārvaldība
|
||||||
|
|
||||||
@ -2029,7 +2019,7 @@ Rīku konfigurācija ir definēta vairākos failos:
|
|||||||
laidiena komandas dažādām vidēm:
|
laidiena komandas dažādām vidēm:
|
||||||
- atkļūdošanas kompilācijas ar iespējotu pilnu atpakaļsekošanu;
|
- atkļūdošanas kompilācijas ar iespējotu pilnu atpakaļsekošanu;
|
||||||
- laidiena kompilācijas ar iespējotu optimizāciju.
|
- laidiena kompilācijas ar iespējotu optimizāciju.
|
||||||
- "GitHub Actions"@gh-actions darbplūsmas, kas apstrādā:
|
- "GitHub Actions" darbplūsmas, kas apstrādā @gh-actions:
|
||||||
- koda kvalitātes pārbaudes (vienībtesti, statiskie testi, formatēšana,
|
- koda kvalitātes pārbaudes (vienībtesti, statiskie testi, formatēšana,
|
||||||
dokumentācijas izveide).
|
dokumentācijas izveide).
|
||||||
- kompilācijas un izvietotošanas darbplūsma, kas:
|
- kompilācijas un izvietotošanas darbplūsma, kas:
|
||||||
@ -2051,10 +2041,11 @@ kas parādija, ka "Maze Ascension" projekts satur $1927$ koda rindiņas, bet
|
|||||||
saistītā "hexlab" bibliotēka -- $979$ rindiņas, kopā veidojot $2906$ loģiskās koda
|
saistītā "hexlab" bibliotēka -- $979$ rindiņas, kopā veidojot $2906$ loģiskās koda
|
||||||
rindiņas, neiekļaujot tukšās rindiņas un komentārus (sk. @tokei-maze-ascension[]
|
rindiņas, neiekļaujot tukšās rindiņas un komentārus (sk. @tokei-maze-ascension[]
|
||||||
un @tokei-hexlab[pielikumus]).
|
un @tokei-hexlab[pielikumus]).
|
||||||
|
|
||||||
Saskaņā ar QSM etalontabulu "Business Systems Implementation Unit (New and
|
Saskaņā ar QSM etalontabulu "Business Systems Implementation Unit (New and
|
||||||
Modified IU) Benchmarks", pirmās kvartiles projekti ($25%$ mazākie no $550$
|
Modified IU) Benchmarks", pirmās kvartiles projekti ($25%$ mazākie no $550$
|
||||||
biznesa sistēmu projektiem) vidēji ilgst $3.2$ mēnešus, ar vidēji $1.57$
|
biznesa sistēmu projektiem) vidēji ilgst $3.2$ mēnešus, ar vidēji $1.57$
|
||||||
izstrādātājiem un mediāno projekta apjomu -- $1889$ koda rindiņas.
|
izstrādātājiem un mediāno projekta apjomu -- $1889$ koda rindiņas @QSM.
|
||||||
Ņemot vērā, ka projekta autors ir students ar ierobežotu pieredzi, tiek
|
Ņemot vērā, ka projekta autors ir students ar ierobežotu pieredzi, tiek
|
||||||
izmantota pirmās kvartiles $50%$ diapazona augšējā robeža -- $466$ rindiņas
|
izmantota pirmās kvartiles $50%$ diapazona augšējā robeža -- $466$ rindiņas
|
||||||
personmēnesī.
|
personmēnesī.
|
||||||
@ -2118,8 +2109,8 @@ Projekta turpmākās attīstības iespējas ietver:
|
|||||||
)
|
)
|
||||||
|
|
||||||
#include "attachments.typ"
|
#include "attachments.typ"
|
||||||
// #include "code.typ"
|
#include "code.typ"
|
||||||
#include "doc.typ"
|
#include "doc.typ"
|
||||||
|
|
||||||
#pagebreak()
|
// #pagebreak()
|
||||||
#total-words words
|
// #total-words words
|
||||||
|
|||||||
11
utils.typ
11
utils.typ
@ -142,11 +142,20 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#let codeblock(filename, lang) = {
|
#let codeblock(caption, filename, lang: "rust") = {
|
||||||
|
show figure: set block(breakable: true)
|
||||||
|
show raw: set par.line(numbering: "1")
|
||||||
|
set figure(numbering: "1")
|
||||||
|
|
||||||
|
figure(
|
||||||
|
caption: caption,
|
||||||
|
kind: "attachment",
|
||||||
|
supplement: "pielikums",
|
||||||
raw(
|
raw(
|
||||||
read(filename),
|
read(filename),
|
||||||
block: true,
|
block: true,
|
||||||
lang: lang,
|
lang: lang,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user