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 meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
config: Res<MazeConfig>,
|
||||
layout: Res<Layout>,
|
||||
maze_query: Query<(Entity, &Floor, &Maze)>,
|
||||
global_config: Res<GlobalMazeConfig>,
|
||||
) {
|
||||
let maze = MazeBuilder::new()
|
||||
.with_radius(config.radius)
|
||||
.with_seed(0)
|
||||
.with_generator(GeneratorType::RecursiveBacktracking)
|
||||
.build()
|
||||
.expect("Something went wrong while creating maze");
|
||||
let SpawnMaze { floor, config } = trigger.event();
|
||||
|
||||
let assets = create_base_assets(&mut meshes, &mut materials, &config);
|
||||
commands
|
||||
if maze_query.iter().any(|(_, f, _)| f.0 == *floor) {
|
||||
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((
|
||||
Name::new("Floor"),
|
||||
SpatialBundle {
|
||||
transform: Transform::from_translation(Vec3::ZERO),
|
||||
..default()
|
||||
},
|
||||
Name::new(format!("Floor {}", floor)),
|
||||
HexMaze,
|
||||
maze.clone(),
|
||||
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() {
|
||||
spawn_single_hex_tile(
|
||||
parent,
|
||||
&assets,
|
||||
assets,
|
||||
tile,
|
||||
&layout.0,
|
||||
config.height,
|
||||
)
|
||||
maze_config,
|
||||
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,
|
||||
assets: &MazeAssets,
|
||||
tile: &HexTile,
|
||||
layout: &HexLayout,
|
||||
hex_height: f32,
|
||||
maze_config: &MazeConfig,
|
||||
global_config: &GlobalMazeConfig,
|
||||
) {
|
||||
dbg!(tile);
|
||||
let world_pos = tile.to_vec3(layout);
|
||||
let rotation = match layout.orientation {
|
||||
let world_pos = tile.to_vec3(&maze_config.layout);
|
||||
let rotation = match maze_config.layout.orientation {
|
||||
// Pointy hexagons don't need additional rotation (0 degrees)
|
||||
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
|
||||
.spawn((
|
||||
Name::new(format!("Hex {}", tile.to_string())),
|
||||
PbrBundle {
|
||||
mesh: assets.hex_mesh.clone(),
|
||||
material: assets.hex_material.clone(),
|
||||
transform: Transform::from_translation(world_pos)
|
||||
.with_rotation(rotation),
|
||||
..default()
|
||||
},
|
||||
Name::new(format!("Hex {}", tile)),
|
||||
Tile,
|
||||
Mesh3d(assets.hex_mesh.clone()),
|
||||
MeshMaterial3d(material),
|
||||
Transform::from_translation(world_pos).with_rotation(rotation),
|
||||
))
|
||||
.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(
|
||||
parent: &mut ChildBuilder,
|
||||
assets: &MazeAssets,
|
||||
y_offset: f32,
|
||||
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 y_offset = global_config.height / 2.;
|
||||
|
||||
for i in 0..6 {
|
||||
if !walls.contains(i) {
|
||||
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 x_offset = (HEX_SIZE - WALL_SIZE) * f32::cos(wall_angle);
|
||||
let z_offset = (HEX_SIZE - WALL_SIZE) * f32::sin(wall_angle);
|
||||
// cos(angle) gives x coordinate on unit circle
|
||||
// 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);
|
||||
|
||||
// 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 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(
|
||||
parent: &mut ChildBuilder,
|
||||
asstets: &MazeAssets,
|
||||
assets: &MazeAssets,
|
||||
rotation: Quat,
|
||||
offset: Vec3,
|
||||
) {
|
||||
parent.spawn((
|
||||
Name::new("Wall"),
|
||||
PbrBundle {
|
||||
mesh: asstets.wall_mesh.clone(),
|
||||
material: asstets.wall_material.clone(),
|
||||
transform: Transform::from_translation(offset)
|
||||
.with_rotation(rotation),
|
||||
..default()
|
||||
},
|
||||
Wall,
|
||||
Mesh3d(assets.wall_mesh.clone()),
|
||||
MeshMaterial3d(assets.wall_material.clone()),
|
||||
Transform::from_translation(offset).with_rotation(rotation),
|
||||
));
|
||||
}
|
||||
|
||||
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
|
||||
- Martin
|
||||
- Typst Projekta Izstrādātāji
|
||||
url: {value: "https://typst.app/", date: 2024-12-20}
|
||||
url: "https://typst.app/"
|
||||
hex-grid:
|
||||
type: Web
|
||||
title: Hexagonal Grids
|
||||
@ -44,11 +44,6 @@ maze-generation:
|
||||
title: Maze Generation
|
||||
author:
|
||||
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:
|
||||
type: Web
|
||||
title: Semantiskā versiju veidošana
|
||||
@ -84,7 +79,7 @@ the-rust-performance-book:
|
||||
url: https://nnethercote.github.io/perf-book
|
||||
cargo-tarpaulin:
|
||||
type: Web
|
||||
title: Tarpaulin
|
||||
title: Tarpaulin rīks
|
||||
author: xd009642
|
||||
url: {value: "https://crates.io/crates/cargo-tarpaulin", date: 2024-12-18}
|
||||
ecs:
|
||||
@ -94,7 +89,7 @@ ecs:
|
||||
url: {value: "https://en.wikipedia.org/wiki/Entity_component_system", date: 2024-09-12}
|
||||
bevy-ecs:
|
||||
type: Web
|
||||
title: Bevy ECS
|
||||
title: Ievads Bevy ECS
|
||||
author: Bevy Projekta Izstādātāji
|
||||
url: {value: "https://bevyengine.org/learn/quick-start/getting-started/ecs/", date: 2024-09-12}
|
||||
SRP:
|
||||
@ -117,12 +112,12 @@ begginer-patterns:
|
||||
url: "https://pressbooks.lib.jmu.edu/programmingpatterns/"
|
||||
clippy:
|
||||
type: Web
|
||||
title: Clippy
|
||||
title: Clippy dokumentācija
|
||||
author: Rust Projekta Izstādātāji
|
||||
url: https://doc.rust-lang.org/stable/clippy/
|
||||
cargo-doc:
|
||||
type: Web
|
||||
title: cargo-doc
|
||||
title: cargo-doc dokumentācija
|
||||
author: Rust Projekta Izstādātāji
|
||||
url: https://doc.rust-lang.org/stable/cargo/
|
||||
rust-style:
|
||||
@ -137,12 +132,12 @@ rust-lang-doc:
|
||||
url: https://doc.rust-lang.org/stable/
|
||||
rustfmt:
|
||||
type: Web
|
||||
title: Rustfmt
|
||||
title: Rustfmt dokumentācija
|
||||
author: Rust Projekta Izstādātāji
|
||||
url: https://github.com/rust-lang/rustfmt
|
||||
gh-release:
|
||||
type: Web
|
||||
title: About Releases
|
||||
title: About Releases dokumentācija
|
||||
author: GitHub komanda
|
||||
url: https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases
|
||||
gh-actions:
|
||||
@ -152,13 +147,13 @@ gh-actions:
|
||||
url: https://docs.github.com/en/actions
|
||||
tokei:
|
||||
type: Web
|
||||
title: tokei
|
||||
title: Tokei rīks
|
||||
author: XAMPPRocky
|
||||
url: https://crates.io/crates/tokei
|
||||
QSM:
|
||||
type: Web
|
||||
title: Software Project Performance Benchmark Tables
|
||||
author: QSM, Inc.
|
||||
author: QSM
|
||||
url: https://www.qsm.com/resources/qsm-benchmark-tables
|
||||
bevy-0.15:
|
||||
type: Web
|
||||
|
||||
30
code.typ
30
code.typ
@ -1,18 +1,20 @@
|
||||
#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 {
|
||||
set par.line(numbering: "1")
|
||||
codeblock("./assets/code/hexlab/structs.rs", "rust")
|
||||
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")
|
||||
}
|
||||
#codeblock(
|
||||
[Labirinta ģenerācijas implementācijas piemērs],
|
||||
"assets/code/maze-ascension/maze_generation.rs",
|
||||
)
|
||||
|
||||
18
doc.typ
18
doc.typ
@ -9,11 +9,13 @@
|
||||
#heading(numbering: none, outlined: false, "Dokumentārā lapa")
|
||||
|
||||
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ā
|
||||
norādītie informācijas avoti un iesniegtā darba elektroniskā kopija atbilst
|
||||
izdrukai.
|
||||
izdrukai un/vai recenzentam uzrādītajai darba versijai.
|
||||
|
||||
|
||||
#context {
|
||||
@ -23,8 +25,8 @@ izdrukai.
|
||||
hanging-indent: 1cm,
|
||||
)
|
||||
|
||||
v(vspace)
|
||||
[Darba autors: *Kristiāns Francis Cagulis, kc22015 ~~\_\_.01.2025.*]
|
||||
v(vspace / 2)
|
||||
[Autors: *Kristiāns Francis Cagulis, kc22015 ~~\_\_.01.2025.*]
|
||||
|
||||
v(vspace)
|
||||
[Rekomendēju darbu aizstāvēšanai\
|
||||
@ -36,14 +38,8 @@ izdrukai.
|
||||
|
||||
v(vspace)
|
||||
[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)
|
||||
[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",
|
||||
region: "lv",
|
||||
)
|
||||
show raw: set text(font: "JetBrainsMono NF")
|
||||
|
||||
show raw.where(lang: "pintora"): it => pintorita.render(it.text)
|
||||
show raw: set text(
|
||||
font: "Fira Code",
|
||||
features: (calt: 0),
|
||||
)
|
||||
|
||||
show math.equation: set text(weight: 400)
|
||||
|
||||
@ -172,8 +173,7 @@
|
||||
)
|
||||
|
||||
// WARNING: remove before sending
|
||||
outline(title: "TODOs", target: figure.where(kind: "todo"))
|
||||
|
||||
// outline(title: "TODOs", target: figure.where(kind: "todo"))
|
||||
/* --- Figure/Table config start --- */
|
||||
show heading: i-figured.reset-counters
|
||||
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 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.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(
|
||||
end,
|
||||
it.counter.display() + ". pielikums. " + text(it.body),
|
||||
@ -283,7 +288,6 @@
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Default case for non-figure elements
|
||||
it
|
||||
}
|
||||
|
||||
61
main.typ
61
main.typ
@ -20,8 +20,6 @@
|
||||
)
|
||||
#set heading(numbering: none)
|
||||
= Apzīmējumu saraksts
|
||||
/ Šūna:
|
||||
|
||||
/ 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;
|
||||
/ DPD: datu plūsmas diagramma;
|
||||
@ -38,7 +36,8 @@
|
||||
/ 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ā;
|
||||
/ 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
|
||||
== 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
|
||||
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
|
||||
unikālu vizuālo un navigācijas izaicinājumu. Procedurālās ģenerēšanas sistēma
|
||||
nodrošina, ka:
|
||||
@ -96,8 +88,8 @@ tehnisko iespējamību.
|
||||
|
||||
== Saistība ar citiem dokumentiem
|
||||
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
|
||||
aprakstīšanai" standarta prasības @lvs_68 @lvs_72.
|
||||
ceļvedis" @lvs_68 un LVS 72:1996 "Ieteicamā prakse programmatūras projektējuma
|
||||
aprakstīšanai" standarta prasības @lvs_72.
|
||||
|
||||
== Pārskats
|
||||
Š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;
|
||||
+ Responsivitāte;
|
||||
+ 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
|
||||
- Tehniskie pieņēmumi:
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
@ -1700,9 +1696,7 @@ projekta lietojuma gadījumam sekojošu iemeslu dēļ:
|
||||
|
||||
== Saskarņu projektējums
|
||||
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.
|
||||
Katra saskarne ir veidota, ņemot vērā tās specifisko lietojuma gadījumu un
|
||||
lietotāju vajadzības.
|
||||
galveno izvēlni un spēles saskarni -- un izstrādes rīkus.
|
||||
|
||||
#figure(
|
||||
caption: "Ekrānskatu plūsmu diagramma",
|
||||
@ -1714,7 +1708,7 @@ lietotāju vajadzības.
|
||||
stroke: 1pt,
|
||||
"<|-|>",
|
||||
)
|
||||
action-node((1, 0), [Galvenais ekrāns], inset: 2em)
|
||||
action-node((1, 0), [Spēles ekrāns], inset: 2em)
|
||||
},
|
||||
),
|
||||
) <ui-flow>
|
||||
@ -1725,8 +1719,6 @@ Galvenā izvēlne ir pirmais skats, ar ko saskaras lietotājs, uzsākot spēli (
|
||||
@fig:main-menu).
|
||||
Tā sastāv no spēles nosaukuma, "Play" -- sākt spēli pogas un "Quit" -- iziet
|
||||
pogas.
|
||||
Izvēlnes dizains ir minimālistisks un intuitīvs, izmantojot kontrastējošas
|
||||
krāsas un skaidru vizuālo hierarhiju.
|
||||
|
||||
#figure(
|
||||
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
|
||||
pārbauda sarežģītākus gadījumus.
|
||||
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ā].
|
||||
@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
|
||||
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"),
|
||||
)<tests-hexlab>
|
||||
|
||||
#indent-par[
|
||||
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šā
|
||||
failā, nodrošinot, ka katras komponentes funkcionalitāte tiek pārbaudīta
|
||||
izolēti.
|
||||
Š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.
|
||||
]
|
||||
|
||||
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
|
||||
@ -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
|
||||
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
|
||||
specifikācijas ceļvedis" 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ā iekļautās
|
||||
// aktivitāšu diagrammas ir izstrādātas, ievērojot UML 2.5 versijas
|
||||
// specifikāciju@omg-uml.
|
||||
specifikācijas ceļvedis" @lvs_68 un LVS 72:1996 standarta "Ieteicamā prakse
|
||||
programmatūras projektējuma aprakstīšanai" standarta prasības @lvs_72.
|
||||
Programmatūras projektējuma aprakstā iekļautās aktivitāšu diagrammas ir veidotas
|
||||
atbilstoši UML (Unified Modeling Language) 2.5 specifikācijai @omg-uml.
|
||||
|
||||
== 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:
|
||||
- atkļūdošanas kompilācijas ar iespējotu pilnu atpakaļsekošanu;
|
||||
- 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,
|
||||
dokumentācijas izveide).
|
||||
- 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
|
||||
rindiņas, neiekļaujot tukšās rindiņas un komentārus (sk. @tokei-maze-ascension[]
|
||||
un @tokei-hexlab[pielikumus]).
|
||||
|
||||
Saskaņā ar QSM etalontabulu "Business Systems Implementation Unit (New and
|
||||
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$
|
||||
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
|
||||
izmantota pirmās kvartiles $50%$ diapazona augšējā robeža -- $466$ rindiņas
|
||||
personmēnesī.
|
||||
@ -2118,8 +2109,8 @@ Projekta turpmākās attīstības iespējas ietver:
|
||||
)
|
||||
|
||||
#include "attachments.typ"
|
||||
// #include "code.typ"
|
||||
#include "code.typ"
|
||||
#include "doc.typ"
|
||||
|
||||
#pagebreak()
|
||||
#total-words words
|
||||
// #pagebreak()
|
||||
// #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(
|
||||
read(filename),
|
||||
block: true,
|
||||
lang: lang,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user