Compare commits

...

50 Commits
v0.3.0 ... main

Author SHA1 Message Date
3d925fd4ce fix: CI/CD 2025-01-15 13:00:57 +02:00
f170000db1 fix: gif path 2025-01-13 23:38:09 +02:00
9262f98e19 finish presentation 2025-01-13 23:33:38 +02:00
7f7314de81 fix: font path 2025-01-13 19:12:57 +02:00
4ec2ebecf0 fix: font path 2025-01-13 19:11:20 +02:00
e1e9346adf CI: add presentation 2025-01-13 19:07:58 +02:00
09b3e38b52 Init presentation 2025-01-09 20:30:47 +02:00
01f37de1df feat: add clippy images 2025-01-06 13:48:50 +02:00
a3ac544af6 chore: minor changes 2025-01-06 13:22:53 +02:00
e6a51832ab chore: update images 2025-01-05 23:25:53 +02:00
c9d987f5b6 fix: typo 2025-01-04 20:10:01 +02:00
bda058ff59 fix: typos 2025-01-03 18:15:35 +02:00
7217cd110d fix: typos 2025-01-03 18:01:35 +02:00
e71ddd761a fix: CI 2025-01-03 16:54:51 +02:00
a2dece79d6 fix: fonts 2025-01-03 16:06:02 +02:00
4efbf66eb3 fix: install mscorefonts 2025-01-03 15:10:21 +02:00
5b7d2d0e35 fix: fonts 2025-01-03 15:05:38 +02:00
4a095f9631 fix: mono fonts 2025-01-03 14:58:36 +02:00
331854ccac chore: update workflow 2025-01-03 14:45:15 +02:00
fb90f28a0d refactor: update documentary page 2025-01-03 14:14:52 +02:00
69fae7946d reafctor: update file structure 2025-01-03 14:08:51 +02:00
47cdcca4cd fix: typos 2025-01-03 00:39:31 +02:00
e16cab9182 fix: fork 2025-01-03 00:34:55 +02:00
2d5d6da161 fix: raw font 2025-01-03 00:26:37 +02:00
84b95fac6a feat: add more code examples 2025-01-03 00:24:47 +02:00
c4bbd7c88c feat: add tests images 2025-01-02 22:36:44 +02:00
2721ef6034 feat: add conclusion 2025-01-02 19:10:45 +02:00
297dba61a8 feat: add UI/UX 2025-01-02 18:51:37 +02:00
a2c0e4499d feat: add partial functions 2025-01-02 16:55:46 +02:00
df975053a0 feat: add activity diagrams 2025-01-02 15:38:42 +02:00
58fc131f38 feat: add project estimation 2025-01-01 23:21:32 +02:00
a78266ce61 chore: remove dpd files 2025-01-01 22:04:12 +02:00
d50ffe4eff feat: add document overview 2025-01-01 22:03:39 +02:00
4209d94e24 feat: add poweup module 2025-01-01 21:47:33 +02:00
935343f7d2 feat: add maze module 2025-01-01 20:22:02 +02:00
84cedbed37 feat: add player module 2025-01-01 19:51:10 +02:00
e297e1ab9a feat: finish screen module 2025-01-01 18:32:06 +02:00
8277cf452a feat: add dev tools dpd2 2025-01-01 18:03:58 +02:00
96f6289552 refactor(dpd): update dpd1 2025-01-01 17:32:27 +02:00
c68065abc7 feat(diagrams): add fletcher template 2025-01-01 04:15:12 +02:00
d04531d1c1 fix: citations 2024-12-31 20:10:01 +02:00
25387397c4 feat: add doc page 2024-12-30 22:13:53 +02:00
cd602f30c1 feat: describe components 2024-12-30 21:23:04 +02:00
db6ce3325a feat(tests): add testing chapter 2024-12-30 00:19:42 +02:00
eb71f9261b feat(screen): add module 2024-12-29 17:16:30 +02:00
645a14aee5 feat(dev_tools): add module 2024-12-28 14:27:04 +02:00
2ff2c82385 feat(examples): add code snippets 2024-12-28 10:18:17 +02:00
1d0d45b6ee add sources 2024-12-02 18:00:40 +02:00
18d9970131 proofreading 2024-12-02 17:34:54 +02:00
684b56bd9a Add abstract and project organization 2024-12-02 17:25:39 +02:00
68 changed files with 4556 additions and 386 deletions

31
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Deploy to GitHub Pages
on:
push:
branches:
- main
permissions:
contents: write
jobs:
deploy:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Clone fonts repository
run: git clone https://github.com/touying-typ/fonts.git fonts --depth=1
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install Touying Exporter
run: pip install touying
- name: Build HTML File
run: |
mkdir build
touying compile presentation.typ --output build/index.html --format html --font-paths fonts --font-paths assets/fonts
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: build

View File

@ -11,19 +11,24 @@ on:
type: string
permissions:
contents: write
packages: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Typst
uses: lvignoli/typst-action@main
uses: actions/checkout@v4
- name: Install Typst
uses: typst-community/setup-typst@v3
with:
source_file: |
main.typ
typst-version: 0.12
cache-dependency-path: requirements.typ
- name: Compile Typst files
run: |
typst compile --font-path=assets/fonts main.typ Cagulis_Kristians.Francis_kc22015.pdf
typst compile --font-path=assets/fonts documentary_page.typ reg_lapa_Cagulis_Kristians.Francis_kc22015.pdf
- name: Upload PDF file
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: PDF
path: "*.pdf"
@ -35,4 +40,6 @@ jobs:
if: github.ref_type == 'tag'
with:
name: "${{ github.ref_name }} — ${{ env.DATE }}"
files: main.pdf
files: |
Cagulis_Kristians.Francis_kc22015.pdf
reg_lapa_Cagulis_Kristians.Francis_kc22015.pdf

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
/files
*.pdf

View File

@ -1,59 +0,0 @@
#import "@preview/dashy-todo:0.0.1": todo
#pagebreak()
#heading(
level: 1,
outlined: false,
numbering: none,
"Anotācija",
)
#lorem(100)
#todo("Uzrakstīt anotāciju (līdz 850 rakstzīmēm)")
#par(
first-line-indent: 0cm,
[*Atslēgvārdi:*],
)
Labirints
datorspēle,
sistēmas prasības,
programmatūras prasību specifikācija,
Bevy,
ECS,
papilspējas.
#text(
hyphenate: auto,
lang: "en",
[
#pagebreak()
#heading(
level: 1,
outlined: false,
numbering: none,
"Abstract",
)
_"Maze Ascension" is a minimalist maze exploration game built using the Bevy
engine. The game features simple visuals with hexagonal tiles forming the maze
structure on a white background with black borders, and a stickman-style player
character. Players navigate through multiple levels of increasing difficulty,
progressing vertically as they climb up through levels. The game includes
power-ups and abilities hidden throughout the maze, and later introduces the
ability to move between levels freely._
#todo("līdz 850 rakstzīmēm")
#par(
first-line-indent: 0cm,
[*Keywords:*],
)
Maze,
comtuper game,
system requirements,
software requirements specification,
Bevy,
ECS,
power-ups.
],
)

View 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)
}
}

197
assets/code/hexlab/walls.rs Normal file
View 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()
}
}

View 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;
}

View File

@ -0,0 +1,177 @@
/// 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>>,
maze_query: Query<(Entity, &Floor, &Maze)>,
global_config: Res<GlobalMazeConfig>,
) {
let SpawnMaze { floor, config } = trigger.event();
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(format!("Floor {}", floor)),
HexMaze,
maze.clone(),
Floor(*floor),
config.clone(),
Transform::from_translation(Vec3::ZERO.with_y(y_offset)),
Visibility::Visible,
))
.insert_if(CurrentFloor, || *floor == 1) // Only floor 1 gets CurrentFloor
.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,
tile,
maze_config,
global_config,
);
}
});
}
/// Spawns a single hexagonal tile with appropriate transforms and materials
pub(super) fn spawn_single_hex_tile(
parent: &mut ChildBuilder,
assets: &MazeAssets,
tile: &HexTile,
maze_config: &MazeConfig,
global_config: &GlobalMazeConfig,
) {
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),
// 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)),
Tile,
Mesh3d(assets.hex_mesh.clone()),
MeshMaterial3d(material),
Transform::from_translation(world_pos).with_rotation(rotation),
))
.with_children(|parent| {
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,
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;
// 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;
spawn_single_wall(parent, assets, final_rotation, pos);
}
}
/// Spawns a single wall segment with the specified rotation and position
fn spawn_single_wall(
parent: &mut ChildBuilder,
assets: &MazeAssets,
rotation: Quat,
offset: Vec3,
) {
parent.spawn((
Name::new("Wall"),
Wall,
Mesh3d(assets.wall_mesh.clone()),
MeshMaterial3d(assets.wall_material.clone()),
Transform::from_translation(offset).with_rotation(rotation),
));
}

1
assets/code/rustfmt.toml Normal file
View File

@ -0,0 +1 @@
max_width = 80

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 56 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 304 KiB

112
assets/images/fork.svg Normal file
View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="32"
height="32"
viewBox="-0.5 -0.5 32 32"
id="svg6"
sodipodi:docname="fork.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="4"
inkscape:cx="-9.25"
inkscape:cy="33.875"
inkscape:window-width="1916"
inkscape:window-height="1055"
inkscape:window-x="1920"
inkscape:window-y="21"
inkscape:window-maximized="1"
inkscape:current-layer="g6" />
<defs
id="defs1" />
<g
id="g6"
transform="translate(0.5,-1)">
<g
id="g1" />
<g
id="g5"
transform="matrix(1,0,0,1.28,-7.67,-18.7)">
<path
d="M 22.67,27.5 V 15"
fill="none"
stroke="#000000"
stroke-width="2"
stroke-miterlimit="10"
pointer-events="stroke"
id="path4" />
</g>
<g
id="g7"
transform="matrix(1,0,0,1.28,-22.67,-2.7)">
<path
d="M 22.67,27.5 V 15"
fill="none"
stroke="#000000"
stroke-width="2"
stroke-miterlimit="10"
pointer-events="stroke"
id="path6" />
</g>
<g
id="g8"
transform="matrix(1,0,0,1.28,-7.67,-2.7)">
<path
d="M 22.67,27.5 V 15"
fill="none"
stroke="#000000"
stroke-width="2"
stroke-miterlimit="10"
pointer-events="stroke"
id="path7" />
</g>
<g
id="g9"
transform="matrix(0,1,-1.28,0,34.2,-6.17)">
<path
d="M 22.67,27.5 V 15"
fill="none"
stroke="#000000"
stroke-width="2"
stroke-miterlimit="10"
pointer-events="stroke"
id="path8" />
</g>
<g
id="g10"
transform="matrix(0,1,-1.28,0,50.2,-6.17)">
<path
d="M 22.67,27.5 V 15"
fill="none"
stroke="#000000"
stroke-width="2"
stroke-miterlimit="10"
pointer-events="stroke"
id="path9" />
</g>
<g
id="g11"
transform="matrix(1,0,0,1.28,7.33,-2.7)">
<path
d="M 22.67,27.5 V 15"
fill="none"
stroke="#000000"
stroke-width="2"
stroke-miterlimit="10"
pointer-events="stroke"
id="path10" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
assets/images/game/grid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
assets/images/game/tile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 650 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

View File

@ -7,22 +7,22 @@ typst:
- Haug
- Martin
- Typst Projekta Izstrādātāji
url: https://typst.app/
hex_grid:
url: "https://typst.app/"
hex-grid:
type: Web
title: Hexagonal Grids
author: Red Blob Games
url: {value: 'https://www.redblobgames.com/grids/hexagons/', date: 2024-09-10}
bevy_examples:
url: {value: "https://www.redblobgames.com/grids/hexagons/", date: 2024-12-20}
bevy-examples:
type: Web
title: Bevy Examples
author:
url: {value: 'https://bevyengine.org/examples/', date: 2024-09-10}
bevy_cheatbook:
author: Bevy Projekta Izstrādātāji
url: {value: "https://bevyengine.org/examples/", date: 2024-09-14}
bevy-cheatbook:
type: Web
title: Unofficial Bevy Cheat Book
author:
url: {value: 'https://bevy-cheatbook.github.io/', date: 2024-09-10}
url: {value: "https://bevy-cheatbook.github.io/", date: 2024-09-14}
lvs_68:
type: Book
title: Programmatūras prasību specifikācijas ceļvedis
@ -39,31 +39,16 @@ lvs_72:
date: 1996-03-27
organization: Latvijas Nacionālais standartizācijas un metroloģijas centrs
page-total: 13
pipeline:
type: Web
title: "CI/CD: The what, why, and how"
author:
url: https://github.com/resources/articles/devops/ci-cd
backtracking:
type: Web
title: Backtracking
url: https://en.wikipedia.org/wiki/Backtracking
maze_generation:
maze-generation:
type: Web
title: Maze Generation
author:
url: https://rosettacode.org/wiki/Maze_generation
bevy_quickstart:
type: Web
title: Bevy New 2D
url: https://github.com/TheBevyFlock/bevy_new_2d
git:
type: Web
title: Versijas kontroles sistēmas git dokumentācija
url: https://git-scm.com/doc
sem_ver:
sem-ver:
type: Web
title: Semantiskā versiju veidošana
url: https://semver.org/
author: Tom Preston-Werner
url: {value: "https://semver.org/", date: 2024-09-17}
omg-uml:
type: Book
title: OMG Unified Modeling Language (OMG UML)
@ -71,3 +56,107 @@ omg-uml:
date: 2015-03
url: https://www.omg.org/spec/UML/2.5/PDF
page-total: 794
webgl2:
type: Web
title: Bevy + WebGPU
author: Carter Anderson
url: {value: "https://bevyengine.org/news/bevy-webgpu/", date: 2024-09-20}
bevy-egui:
type: Web
title: Bevy Egui bibliotēkas dokumentācija
author: Vladyslav Batyrenko
url: {value: "https://docs.rs/bevy_egui/latest/bevy_egui/", date: 2024-09-26}
bevy-inspector-egui:
type: Web
title: Bevy Inspector Egui bibliotēkas dokumentācija
author: Jakob Hellermann
url: {value: "https://docs.rs/bevy-inspector-egui/latest/bevy_inspector_egui/", date: 2024-09-26}
the-rust-performance-book:
type: Book
title: The Rust Performance Book
author: Nicholas Nethercote
date: 2020-11
url: https://nnethercote.github.io/perf-book
cargo-tarpaulin:
type: Web
title: Tarpaulin rīks
author: xd009642
url: {value: "https://crates.io/crates/cargo-tarpaulin", date: 2024-12-18}
ecs:
type: Web
title: ECS
author:
url: {value: "https://en.wikipedia.org/wiki/Entity_component_system", date: 2024-09-12}
bevy-ecs:
type: Web
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:
type: Web
title: Single-responsibility principle
author:
url: {value: "https://en.wikipedia.org/wiki/Single-responsibility_principle"}
SoC:
type: Web
title: Separation of concerns
author:
url: {value: "https://en.wikipedia.org/wiki/Separation_of_concerns"}
begginer-patterns:
type: Book
title: Patterns for Beginning Programmers
author: David Bernstein
chapter: Bit Flags
page-total: 197
page-range: 58-64
url: "https://pressbooks.lib.jmu.edu/programmingpatterns/"
clippy:
type: Web
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 dokumentācija
author: Rust Projekta Izstādātāji
url: https://doc.rust-lang.org/stable/cargo/
rust-style:
type: Web
title: Rust stila ceļvedis
author: Rust Projekta Izstādātāji
url: https://doc.rust-lang.org/stable/style-guide/
rust-lang-doc:
type: Web
title: Rust Dokumentācija
author: Rust Projekta Izstādātāji
url: https://doc.rust-lang.org/stable/
rustfmt:
type: Web
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 dokumentācija
author: GitHub komanda
url: https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases
gh-actions:
type: Web
title: GitHub Actions dokumentācija
author: GitHub komanda
url: https://docs.github.com/en/actions
tokei:
type: Web
title: Tokei rīks
author: XAMPPRocky
url: https://crates.io/crates/tokei
QSM:
type: Web
title: Software Project Performance Benchmark Tables
author: QSM
url: https://www.qsm.com/resources/qsm-benchmark-tables
bevy-0.15:
type: Web
title: Bevy 0.15
author: Bevy Projekta Izstādātāji
url: https://bevyengine.org/news/bevy-0-15/

87
documentary_page.typ Normal file
View File

@ -0,0 +1,87 @@
#import "@preview/tablex:0.0.9": tablex
#import "src/layout.typ": project, indent-par
#import "src/layout.typ": indent
#set page(
margin: (
left: 30mm,
right: 20mm,
top: 20mm,
bottom: 20mm,
),
number-align: center,
paper: "a4",
)
#set text(
font: "Times New Roman",
size: 12pt,
hyphenate: auto,
lang: "lv",
region: "lv",
)
#set par(
justify: true,
leading: 1.5em,
first-line-indent: indent,
spacing: 1.5em,
)
#show heading: set block(spacing: 1.5em)
#show heading: it => {
if it.level == 1 {
pagebreak(weak: true)
text(
14pt,
align(
center,
upper(it),
),
)
} else {
text(12pt, it)
}
""
v(-1cm)
}
#let vspace = 1fr
#let fill = box(width: 1fr, repeat(sym.space))
#let long-underline = underline(box(width: 1fr, repeat(sym.space)))
#set page(numbering: none)
#heading(level: 1, outlined: false, numbering: none, "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ē,
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 un/vai recenzentam uzrādītajai darba versijai.
#context {
set par(
first-line-indent: 1cm,
hanging-indent: 1cm,
)
v(vspace / 2)
[Autors: *Kristiāns Francis Cagulis, kc22015 ~~06.01.2025.*]
v(vspace)
[Rekomendēju darbu aizstāvēšanai\
Darba vadītājs: *Mg. dat. Jānis Iljins ~~06.01.2025.*]
v(vspace)
[Recenzents: *Artūrs Driķis*]
v(vspace)
[Darbs iesniegs *06.01.2025.*\
Kvalifikācijas darbu pārbaudījumu komisijas sekretārs (elektronisks paraksts)
]
v(vspace)
}

2087
main.typ

File diff suppressed because it is too large Load Diff

283
presentation.typ Normal file
View File

@ -0,0 +1,283 @@
#import "@preview/touying:0.5.5": *
#import themes.university: *
#import "@preview/cetz:0.3.1"
#import "@preview/fletcher:0.5.3" as fletcher: node, edge
#import "@preview/ctheorems:1.1.3": *
#import "@preview/numbly:0.1.0": numbly
#import "src/diagrams.typ": *
#set text(
font: (
"Times New Roman",
"New Computer Modern",
),
size: 12pt,
hyphenate: auto,
lang: "lv",
region: "lv",
)
#show raw: set text(
font: (
"JetBrainsMono NF",
"JetBrains Mono",
"Fira Code",
),
features: (calt: 0),
)
// cetz and fletcher bindings for touying
#let cetz-canvas = touying-reducer.with(
reduce: cetz.canvas,
cover: cetz.draw.hide.with(bounds: true),
)
#let fletcher-diagram = touying-reducer.with(
reduce: fletcher.diagram,
cover: fletcher.hide,
)
#set figure(supplement: none)
// Theorems configuration by ctheorems
#show: thmrules.with(qed-symbol: $square$)
#let theorem = thmbox("theorem", "Theorem", fill: rgb("#eeffee"))
#let corollary = thmplain(
"corollary",
"Corollary",
base: "theorem",
titlefmt: strong,
)
#let definition = thmbox(
"definition",
"Definition",
inset: (x: 1.2em, top: 1em),
)
#let example = thmplain("example", "Example").with(numbering: none)
#let proof = thmproof("proof", "Proof")
#show: university-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Spēles izstrāde, izmantojot Bevy spēļu dzinēju],
subtitle: [Kvalifikācijas darbs],
author: [Kristiāns Francis Cagulis kc22015],
date: [2025],
institution: [Latvijas Universitāte],
),
config-colors(
primary: rgb("#575279"),
secondary: rgb("#797593"),
tertiary: rgb("#286983"),
neutral-lightest: rgb("#faf4ed"),
neutral-darkest: rgb("#575279"),
),
)
#title-slide()
#slide[
= Pārskats
- Entitāšu komponenšu sistēma (ECS)
- Maze Ascension
- Hexlab bibliotēka
- Sešstūru implementācija
- Saskarne
- Rezultāti un secinājumi
]
= Entitāšu komponenšu sistēma (ECS)
== Kas ir ECS?
- Koncentrējas uz kompozīciju, nevis mantošanu.
- Datu orientēta arhitektūra.
- Nodalīti dati (komponentes) un uzvedība (sistēmas).
== Datu izkārtojums
// Here is an illustration to help you visualize the logical structure. The
// checkmarks show what component types are present on each entity. Empty cells
// mean that the component is not present. In this example, we have a player, a
// camera, and several enemies.
#context {
show raw: set text(size: 16pt)
table(
columns: 7,
[*Entity (ID)*],
[*Transform*],
[*Player*],
[*Enemy*],
[*Camera*],
[*Health*],
[*...*],
`...`, "", "", "", "", "", "",
"107",
[#emoji.checkmark.heavy `<translation>`\ `<rotation>`\ `<scale>`],
emoji.checkmark.heavy,
"",
"",
[#emoji.checkmark.heavy `<50.0>`],
"",
"108",
[#emoji.checkmark.heavy `<translation>`\ `<rotation>`\ `<scale>`],
"",
emoji.checkmark.heavy,
"",
[#emoji.checkmark.heavy `<25.0>`],
"",
"109",
[#emoji.checkmark.heavy `<translation>`\ `<rotation>`\ `<scale>`],
"",
"",
[#emoji.checkmark.heavy `<camera data>`],
"",
"",
`...`,
)
}
== Piemērs
#context {
show raw: set text(size: 16pt)
```rust
#[derive(Component)]
struct Player;
#[derive(Component)]
struct Health {
current: u32,
max: u32
}
fn heal_player(
mut query: Query<&mut Health, With<Player>>,
time: Res<Time>,
) {
for mut health in query.iter_mut() {
let new_health = health.current + 2. * time.delta_secs();
health.current = new_health.min(health.max);
}
}
```
}
= Maze Ascension
== 1. Līmeņa DPD
#figure(image("assets/images/diagrams/dpd1.png"))
== Stāva modulis
#figure(image("assets/images/diagrams/floor.png", width: 50%))
= Hexlab bibliotēka
#pagebreak()
#figure(
caption: link("https://crates.io/crates/hexlab"),
image("assets/images/crates/hexlab.png", height: 92%),
)
== Labirinta ģenerēšanas funkcijas projektējums
#grid(
columns: (1fr, 1fr),
figure(image("assets/images/diagrams/maze-gen.png")),
figure(image("assets/images/diagrams/recursive.png")),
)
== Ģenerēšanas algoritms
// recursive backtracking
#figure(
caption: link("https://en.wikipedia.org/wiki/Maze_generation_algorithm"),
image("assets/videos/hexmaze/hexmaze.gif", height: 92%),
)
= Sešstūru implementācija
== Attēlošana
#grid(
columns: 2,
figure(image("assets/images/game/tile-spreadout.png", height: 100%)),
figure(image("assets/images/game/tile.png", height: 100%)),
)
#figure(image("assets/images/game/grid.png", height: 100%))
= Saskarne
#pagebreak()
#grid(
columns: 2,
figure(image("assets/images/game/main-menu.png")),
)
#figure(image("assets/videos/game/maze-game.gif"))
#grid(
columns: (1fr, 1fr),
figure(image("assets/images/game/debug.png")),
figure(image("assets/images/game/dev-tools.png")),
)
#figure(image("assets/videos/game/big-maze.gif"))
= Secinājumi
== Rezultāti
- 3D spēle izmantojot Bevy un Rust:
- Procedurāla sešstūraina labirints ģenerēšana, izmantojot DFS.
- Izveidota atkārtoti lietojama atvērtā pirmkoda bibliotēka "hexlab".
- Efektīva līmeņa pārvaldība:
- Vienmērīga pāreja starp labirinta līmeņiem.
== Turpmākie darbi
- Īstenot vairāk labirintu ģenerēšanas paņēmienus/algoritmus.
- Ieviest jaunas mehānikas un papildspējas.
- Uzlabot vizuālo kvalitāti un spēlētāja izskatu.
- Izstrādāt daudzspēlētāju režīmu.
= Paldies par uzmanību!
Jautājumi?
#show: appendix
= Galavārds
== ECS vs OOP
#grid(
columns: (1fr, 1fr),
gutter: 1em,
[
*ECS*
- Plakana hierarhija
- Datu orientēta
- Kešatmiņai piemērots
- Paralēla apstrāde
],
[
*OOP*
- Dziļa mantojamība (inheritance)
- Objektorientēta
- Kešatmiņa izkliedēts
- Secīga apstrāde
],
)
== Iedvesma
#figure(
caption: link("https://www.redblobgames.com/grids/hexagons/"),
grid(
columns: 2,
figure(image("assets/images/redblogmages/axial-coords.png", height: 92%)),
figure(image("assets/videos/coords/coords.gif", height: 92%)),
),
)

7
requirements.typ Normal file
View File

@ -0,0 +1,7 @@
#import "@preview/dashy-todo:0.0.1"
#import "@preview/fletcher:0.5.3"
#import "@preview/headcount:0.1.0"
#import "@preview/i-figured:0.2.4"
#import "@preview/tablex:0.0.9"
#import "@preview/touying:0.5.5"
#import "@preview/wordometer:0.1.3"

75
src/abstract.typ Normal file
View File

@ -0,0 +1,75 @@
#import "@preview/dashy-todo:0.0.1": todo
#import "@preview/wordometer:0.1.3": word-count, total-words
#pagebreak()
#heading(
level: 1,
outlined: false,
numbering: none,
"Anotācija",
)
Kvalifikācijas darbā ir izstrādāta spēle "Maze Ascension", kas piedāvā
spēlētājiem izaicinājumu iziet cauri procedurāli ģenerētiem sešstūrainam
labirintiem. Spēle ir veidota, izmantojot Rust programmēšanas valodu un Bevy
spēļu dzinēju.
Darba gaitā tika izstrādāta "hexlab" bibliotēka labirintu ģenerēšanai, kas tika
atdalīta no galvenās spēles loģikas. Labirintu ģenerēšanai tiek izmantots
rekursīvās atpakaļizsekošanas algoritms, kas nodrošina, ka katrai šūnai var
piekļūt no jebkuras citas šūnas.
Spēle ir izstrādāta kā vienspēlētāja režīmā ar progresējošu grūtības
pakāpi, kur katrs nākamais līmenis piedāvā lielāku labirintu. Spēle ir pieejama
gan kā lejupielādējama versija Windows, Linux un macOS platformām, gan kā
tīmekļa versija, izmantojot WebAssembly tehnoloģiju.
#par(
first-line-indent: 0cm,
[*Atslēgvārdi:*],
)
Labirints,
datorspēle,
sistēmas prasības,
programmatūras prasību specifikācija,
Bevy,
ECS,
papildspējas.
#text(
hyphenate: auto,
lang: "en",
[
#pagebreak()
#heading(
level: 1,
outlined: false,
numbering: none,
"Abstract",
)
The qualification work "Game development using Bevy game engine" includes the game "Maze Ascension", which offers
players the challenge to pass through procedurally generated hexagons
mazes. The game is built using the Rust programming language and Bevy
game engine.
The work included the development of a "hexlab" library for maze generation,
which was separated from the main game logic. The maze generation is a
recursive backtracking algorithm which ensures that each cell can be
accessed from any other cell.
The game is designed as a single-player mode with progressive difficulty
with each successive level offering a larger maze. The game is available
as a downloadable version for Windows, Linux and macOS platforms, and as
as a web-based version using WebAssembly technology.
#par(
first-line-indent: 0cm,
[*Keywords:*],
)
Maze,
computer game,
system requirements,
software requirements specification,
Bevy,
ECS,
power-ups.
],
)

36
src/attachments.typ Normal file
View File

@ -0,0 +1,36 @@
#import "@preview/i-figured:0.2.4"
#heading("Pielikumi", numbering: none)
#set figure(numbering: "1")
#set figure(kind: "attachment", supplement: "pielikums")
#figure(
caption: [Clippy rīka rezultāts "hexlab" bibliotēkai],
image("../assets/images/clippy/hexlab.png"),
) <clippy-hexlab>
#figure(
caption: [Clippy rīka rezultāts "Maze Ascension" spēlei],
image("../assets/images/clippy/maze-ascension.png"),
) <clippy-maze-ascension>
#figure(
caption: [Tarpaulin rīka rezultāts "hexlab" bibliotēkai],
image("../assets/images/tests/tarpaulin/hexlab.png"),
) <tarpaulin-hexlab>
#figure(
caption: [Tokei rīka rezultāts "Maze Ascension" spēlei],
image("../assets/images/tokei/maze-ascension.png"),
) <tokei-maze-ascension>
#figure(
caption: [Tokei rīka rezultāts "hexlab" bibliotēkai],
image("../assets/images/tokei/hexlab.png"),
) <tokei-hexlab>
#figure(
caption: [Pilns "hexlab" bibliotēkas testu rezultāts],
image("../assets/images/tests/hexlab-full.png", height: 90%),
) <tests-hexlab-full>

20
src/code.typ Normal file
View File

@ -0,0 +1,20 @@
#import "utils.typ": *
#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",
)
#codeblock(
[Labirinta ģenerācijas implementācijas piemērs],
"../assets/code/maze-ascension/maze_generation.rs",
)

207
src/diagrams.typ Normal file
View File

@ -0,0 +1,207 @@
#import "@preview/fletcher:0.5.3" as fletcher: diagram, node, edge
#import fletcher.shapes: diamond, ellipse
#import "@preview/cetz:0.3.1"
#import cetz: draw
#let default-node-stroke = 1pt
#let default-edge-stroke = 1pt
// Common filled circle node (terminal node)
#let terminal-node(pos, extrude: none) = {
if extrude != none {
node(
pos,
[],
radius: 6pt,
fill: black,
stroke: default-node-stroke,
extrude: extrude,
)
} else {
node(
pos,
[],
radius: 6pt,
fill: black,
)
}
}
// Common rounded rectangle node
#let action-node(..args) = {
node(
corner-radius: 4pt,
stroke: default-node-stroke,
shape: rect,
..args,
)
}
// Common diamond node (decision node)
#let decision-node(pos, text) = {
node(
pos,
text,
shape: diamond,
stroke: default-node-stroke,
)
}
// Standard arrow edge
#let std-edge(..args) = {
edge(
label-pos: 0.1,
stroke: default-edge-stroke,
label-size: 10pt,
..args,
"-|>",
)
}
// Fork/parallel function
#let parallel-fork(
pos,
paths,
path_spacing: 1,
join_pos: none,
) = {
let elements = ()
// Calculate positions
let path_count = paths.len()
let total_width = (path_count + 1) * path_spacing
let (start_x, start_y) = pos
// Fork bar (horizontal line)
elements.push(
edge(
(start_x - total_width / 2, start_y),
(start_x + total_width / 2, start_y),
stroke: default-edge-stroke * 3,
),
)
// Create paths
for path in paths {
let first_obj_path = path.first().value.pos.raw
let x_offset = first_obj_path.first()
let path_start = (x_offset, start_y)
// Vertical connector from fork bar
elements.push(
std-edge(
path_start,
first_obj_path,
),
)
// Add the path elements
elements += path
}
// Join paths if specified
if join_pos != none {
let (join_x, join_y) = join_pos
// Join bar (horizontal line)
elements.push(
edge(
(join_x - total_width / 2, join_y),
(join_x + total_width / 2, join_y),
stroke: default-edge-stroke * 3,
),
)
// Connect each path to join bar
for path in paths {
let last_obj_path = path.last().value.pos.raw
let x_offset = last_obj_path.first()
let path_end = (x_offset, join_y)
elements.push(
std-edge(
last_obj_path,
path_end,
),
)
}
}
elements
}
#let data-store(pos, text) = {
node(
pos,
text,
inset: 20pt,
stroke: default-node-stroke,
)
}
#let process(..args) = {
node(
inset: 10pt,
shape: ellipse,
stroke: default-node-stroke,
..args,
)
}
#let dpd-edge(..args) = {
edge(
label-pos: 0.5,
stroke: default-edge-stroke,
label-anchor: "center",
label-fill: white,
corner-radius: 4pt,
label-size: 10pt,
..args,
"-|>",
)
}
// Database shape
#let database(node, extrude) = {
let (w, h) = node.size
// Calculate dimensions for the cylinder parts
let ellipse-height = h * 0.15
let cap-ratio = 0.2 // Cap height will be 30% of width
let cap-height = w * cap-ratio
// Main body sides (without bottom line)
draw.line(
(-w, -h + cap-height), // Start at top-left
(-w, h - cap-height), // Left side
)
draw.line(
(w, h - cap-height), // To bottom-right
(w, -h + cap-height), // Right side
)
// Top ellipse
draw.circle(
(0, h - cap-height),
radius: (w, cap-height),
)
// Bottom elliptical cap (front arc only)
draw.arc(
(-w, -h + cap-height),
radius: (w, cap-height),
start: 180deg,
delta: 180deg,
)
}
#let dpd-database(..args) = {
node(
shape: database,
height: 4em,
stroke: default-node-stroke,
fill: white,
..args,
)
}

View File

@ -1,5 +1,6 @@
#import "@preview/i-figured:0.2.4"
#import "@preview/tablex:0.0.9": tablex
#import "@preview/headcount:0.1.0": *
#let indent = 1cm
@ -11,7 +12,7 @@
#let project(
university: "",
faculty: "",
type: "",
thesis_type: "",
title: [],
authors: (),
advisor: "",
@ -31,13 +32,36 @@
paper: "a4",
)
set text(
//font: "New Computer Modern",
font: "CMU", size: 12pt, hyphenate: auto, lang: "lv", region: "lv",
font: (
"Times New Roman",
"New Computer Modern",
),
size: 12pt,
hyphenate: auto,
lang: "lv",
region: "lv",
)
show raw: set text(
font: (
"JetBrainsMono NF",
"JetBrains Mono",
"Fira Code",
),
features: (calt: 0),
)
show raw: set text(font: "New Computer Modern Mono")
show math.equation: set text(weight: 400)
// replace `.` with `,`
show math.equation: it => {
show regex("\d+\.\d+"): num => {
show ".": math.class("normal", ",")
num
}
it
}
// Formatting for regular text
set par(
justify: true,
@ -46,6 +70,7 @@
spacing: 1.5em,
)
show heading: set block(spacing: 1.5em)
set terms(separator: [ -- ])
// Headings
@ -101,7 +126,7 @@
upper(
text(
size: 16pt,
type,
thesis_type,
),
),
)
@ -141,24 +166,33 @@
align(
center,
upper(
text(date),
),
upper(text(date)),
)
/* Title page config end */
// WARNING: remove before sending
outline(title: "TODOs", target: figure.where(kind: "todo"))
// Start page numbering
set page(
numbering: "1",
number-align: center,
)
// WARNING: remove before sending
// 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.")
set figure(numbering: dependent-numbering("1.1"))
set figure(placement: none)
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)
show figure.where(kind: image): set par(leading: 0.75em)
show figure.caption: set text(size: 11pt)
show figure.caption: it => {
@ -180,10 +214,91 @@
),
)
}
if (
it.kind in (
"i-figured-raw",
"i-figured-\"attachment\"",
)
) {
return align(
end,
it.counter.display() + ". pielikums. " + text(it.body),
)
}
it
}
set ref(supplement: it => { }) // disable default reference suppliments
// disable default reference suppliments
set ref(supplement: it => { })
// Custom show rule for references
show ref: it => {
let el = it.element
if el == none {
return it
}
if el.func() == heading {
return link(
el.location(),
numbering(
el.numbering,
..counter(heading).at(el.location()),
) + " " + el.body,
)
}
if el.func() == figure {
let kind = el.kind
// Map for different kinds of supplements
let supplement_map = (
i-figured-table: "tab.",
i-figured-image: "att.",
attachment: "pielikumu",
)
// Get the supplement value properly
let supplement = if type(it.supplement) != function {
it.supplement
} else {
if kind in supplement_map {
supplement_map.at(kind)
} else {
""
}
}
let number = if kind == "attachment" {
(
numbering(
el.numbering,
..counter(figure.where(kind: kind)).at(el.location()),
) + "."
) // Only add dot for attachments
} else {
numbering(
el.numbering,
..counter(figure.where(kind: kind)).at(el.location()),
) // No extra dot for tables and images
}
// Create counter based on the kind
return link(
el.location(),
number + if supplement != "" {
" " + supplement
} else {
""
},
)
}
// Default case for non-figure elements
it
}
/* --- Figure/Table config end --- */
set list(marker: (
@ -214,12 +329,6 @@
)
/* ToC config end */
// Start page numbering
set page(
numbering: "1",
number-align: center,
)
// show link: set text(fill: blue.darken(20%))

View File

@ -21,7 +21,7 @@
..items,
) = {
set par(first-line-indent: 0pt)
figure(
return figure(
gap: 1.5em,
kind: table,
caption: if caption != "" {
@ -42,9 +42,7 @@
),
)
}
#for i in range(
items.pos().len(),
) {
#for i in range(items.pos().len()) {
if titles.len() > 0 {
custom-block(
text(
@ -53,13 +51,10 @@
),
)
}
custom-block(
items.pos().at(i),
)
custom-block(items.pos().at(i))
}
],
)
linebreak()
}
#let parameter-table(
@ -110,7 +105,7 @@
if caption == "" {
caption = items.pos().first()
}
longtable(
return longtable(
titles: (
"Funkcijas nosaukums",
"Funkcijas identifikators",
@ -147,47 +142,113 @@
)
}
#let entity-table(
caption: "",
id: (),
..items,
) = {
if id == () {
id = (
"id",
"serial8",
"primary key, not null",
"Unikālais identifikators",
)
}
#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: table,
tablex(
columns: (4cm, 3cm, auto, auto),
repeat-header: true,
/* Header */
[*Lauks*], [*Datu tips*], [*Lauka atribūti*], [*Apraksts*],
..entity-table-row(..id), // id row
..for i in range(items.pos().len(), step:4){
entity-table-row(..items.pos().slice(i, i+4))
},
kind: "attachment",
supplement: "pielikums",
raw(
read(filename),
block: true,
lang: lang,
),
)
}
#let hyperlink-source(
author,
title,
link_str,
date,
#let components-table(
caption: str,
..body,
) = {
if link_str == "" {
[#author #title Aplūkots #date.display("[day].[month].[year]")]
} else {
[#author #title Pieejams: #link(link_str) aplūkots #date.display("[day].[month].[year]")]
}
figure(
caption: caption,
kind: table,
tablex(
columns: 3,
[*Komponente*],
[*Apraksts*],
[*Pielietojums*],
..body,
),
)
}
#let events-table(
caption: str,
..body,
) = {
figure(
caption: caption,
kind: table,
tablex(
columns: 3,
[*Notikums*],
[*Apraksts*],
[*Pielietojums*],
..body,
),
)
}
#let resources-table(
caption: str,
..body,
) = {
figure(
caption: caption,
kind: table,
tablex(
columns: 3,
[*Resurss*],
[*Apraksts*],
[*Pielietojums*],
..body,
),
)
}
#let test-table(
caption: "",
..items,
) = {
if caption == "" {
caption = items.pos().first()
}
return longtable(
titles: (
"Testa gadījuma nosaukums",
"Testa identifikators",
"Apraksts",
"Soļi",
"Sagaidāmais rezultāts",
"Faktiskais rezultāts",
),
caption: caption,
..items,
)
}
#let entity-table-row(
..items,
) = {
(
items.pos().at(0),
upper(
raw(
items.pos().at(1),
block: false,
),
),
upper(
raw(
items.pos().at(2),
block: false,
),
),
items.pos().at(3),
)
}