Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d925fd4ce | |||
| f170000db1 | |||
| 9262f98e19 | |||
| 7f7314de81 | |||
| 4ec2ebecf0 | |||
| e1e9346adf | |||
| 09b3e38b52 | |||
| 01f37de1df | |||
| a3ac544af6 | |||
| e6a51832ab | |||
| c9d987f5b6 | |||
| bda058ff59 | |||
| 7217cd110d | |||
| e71ddd761a | |||
| a2dece79d6 | |||
| 4efbf66eb3 | |||
| 5b7d2d0e35 | |||
| 4a095f9631 | |||
| 331854ccac | |||
| fb90f28a0d | |||
| 69fae7946d | |||
| 47cdcca4cd | |||
| e16cab9182 | |||
| 2d5d6da161 | |||
| 84b95fac6a | |||
| c4bbd7c88c | |||
| 2721ef6034 | |||
| 297dba61a8 | |||
| a2c0e4499d | |||
| df975053a0 | |||
| 58fc131f38 | |||
| a78266ce61 | |||
| d50ffe4eff | |||
| 4209d94e24 | |||
| 935343f7d2 | |||
| 84cedbed37 | |||
| e297e1ab9a | |||
| 8277cf452a | |||
| 96f6289552 | |||
| c68065abc7 | |||
| d04531d1c1 | |||
| 25387397c4 | |||
| cd602f30c1 | |||
| db6ce3325a | |||
| eb71f9261b | |||
| 645a14aee5 | |||
| 2ff2c82385 | |||
| 1d0d45b6ee | |||
| 18d9970131 | |||
| 684b56bd9a | |||
| 91eca5ce41 | |||
| ae1dee0996 | |||
| 081d6a5b3b | |||
| 5142324ea3 | |||
| d11b89ba2d |
31
.github/workflows/deploy.yml
vendored
Normal 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
|
||||||
21
.github/workflows/typst.yml
vendored
@ -11,19 +11,24 @@ on:
|
|||||||
type: string
|
type: string
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
packages: read
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Typst
|
- name: Install Typst
|
||||||
uses: lvignoli/typst-action@main
|
uses: typst-community/setup-typst@v3
|
||||||
with:
|
with:
|
||||||
source_file: |
|
typst-version: 0.12
|
||||||
main.typ
|
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
|
- name: Upload PDF file
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: PDF
|
name: PDF
|
||||||
path: "*.pdf"
|
path: "*.pdf"
|
||||||
@ -35,4 +40,6 @@ jobs:
|
|||||||
if: github.ref_type == 'tag'
|
if: github.ref_type == 'tag'
|
||||||
with:
|
with:
|
||||||
name: "${{ github.ref_name }} — ${{ env.DATE }}"
|
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
@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
/files
|
/files
|
||||||
|
*.pdf
|
||||||
|
|||||||
54
abstract.typ
@ -1,54 +0,0 @@
|
|||||||
#pagebreak()
|
|
||||||
#heading(
|
|
||||||
level: 1,
|
|
||||||
outlined: false,
|
|
||||||
numbering: none,
|
|
||||||
"Anotācija",
|
|
||||||
)
|
|
||||||
|
|
||||||
#lorem(100)
|
|
||||||
|
|
||||||
#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._
|
|
||||||
#par(
|
|
||||||
first-line-indent: 0cm,
|
|
||||||
[*Keywords:*],
|
|
||||||
)
|
|
||||||
Maze,
|
|
||||||
comtuper game,
|
|
||||||
system requirements,
|
|
||||||
software requirements specification,
|
|
||||||
Bevy,
|
|
||||||
ECS,
|
|
||||||
power-ups.
|
|
||||||
],
|
|
||||||
)
|
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
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
@ -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;
|
||||||
|
}
|
||||||
177
assets/code/maze-ascension/maze_generation.rs
Normal 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
@ -0,0 +1 @@
|
|||||||
|
max_width = 80
|
||||||
BIN
assets/fonts/TimesNewRoman/Times.TTF
Normal file
BIN
assets/fonts/TimesNewRoman/Times.ttf
Normal file
BIN
assets/fonts/TimesNewRoman/Timesbd.TTF
Normal file
BIN
assets/fonts/TimesNewRoman/Timesbi.TTF
Normal file
BIN
assets/fonts/TimesNewRoman/Timesi.TTF
Normal file
BIN
assets/images/clippy/hexlab.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
assets/images/clippy/maze-ascension.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/crates/hexlab.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
assets/images/diagrams/dpd1.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
assets/images/diagrams/floor.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
assets/images/diagrams/maze-gen.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
assets/images/diagrams/recursive.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
@ -1 +0,0 @@
|
|||||||
../placeholder.jpg
|
|
||||||
@ -1 +0,0 @@
|
|||||||
../placeholder.jpg
|
|
||||||
112
assets/images/fork.svg
Normal 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 |
BIN
assets/images/game/debug.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/images/game/dev-tools.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/images/game/grid.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
assets/images/game/main-menu.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
assets/images/game/tile-spreadout.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
assets/images/game/tile.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/images/redblogmages/axial-coords.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
assets/images/sceens/dev-tools.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
814
assets/images/sceens/game.svg
Normal file
|
After Width: | Height: | Size: 650 KiB |
37
assets/images/sceens/main.svg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/images/tests/hexlab-full.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
assets/images/tests/hexlab-minimized.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/images/tests/tarpaulin/hexlab.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/images/tokei/hexlab.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/images/tokei/maze-ascension.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
assets/videos/coords/coords-fast.gif
Normal file
|
After Width: | Height: | Size: 33 MiB |
BIN
assets/videos/coords/coords-fast.webm
Normal file
BIN
assets/videos/coords/coords.gif
Normal file
|
After Width: | Height: | Size: 36 MiB |
BIN
assets/videos/coords/coords.webm
Normal file
BIN
assets/videos/coords/palette.png
Normal file
|
After Width: | Height: | Size: 952 B |
BIN
assets/videos/game/big-maze.gif
Normal file
|
After Width: | Height: | Size: 42 MiB |
BIN
assets/videos/game/big-maze.mkv
Normal file
BIN
assets/videos/game/maze-game-fast.gif
Normal file
|
After Width: | Height: | Size: 31 MiB |
BIN
assets/videos/game/maze-game-fast.webm
Normal file
BIN
assets/videos/game/maze-game.gif
Normal file
|
After Width: | Height: | Size: 84 MiB |
BIN
assets/videos/game/maze-game.webm
Normal file
BIN
assets/videos/game/palette.png
Normal file
|
After Width: | Height: | Size: 986 B |
BIN
assets/videos/hexmaze/hexmaze-fast.gif
Normal file
|
After Width: | Height: | Size: 3.8 MiB |
BIN
assets/videos/hexmaze/hexmaze-fast.webm
Normal file
BIN
assets/videos/hexmaze/hexmaze.gif
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
assets/videos/hexmaze/hexmaze.webm
Normal file
BIN
assets/videos/hexmaze/palette.png
Normal file
|
After Width: | Height: | Size: 983 B |
138
bibliography.yml
@ -7,22 +7,22 @@ typst:
|
|||||||
- Haug
|
- Haug
|
||||||
- Martin
|
- Martin
|
||||||
- Typst Projekta Izstrādātāji
|
- Typst Projekta Izstrādātāji
|
||||||
url: https://typst.app/
|
url: "https://typst.app/"
|
||||||
hex_grid:
|
hex-grid:
|
||||||
type: Web
|
type: Web
|
||||||
title: Hexagonal Grids
|
title: Hexagonal Grids
|
||||||
author: Red Blob Games
|
author: Red Blob Games
|
||||||
url: {value: 'https://www.redblobgames.com/grids/hexagons/', date: 2024-09-10}
|
url: {value: "https://www.redblobgames.com/grids/hexagons/", date: 2024-12-20}
|
||||||
bevy_examples:
|
bevy-examples:
|
||||||
type: Web
|
type: Web
|
||||||
title: Bevy Examples
|
title: Bevy Examples
|
||||||
author:
|
author: Bevy Projekta Izstrādātāji
|
||||||
url: {value: 'https://bevyengine.org/examples/', date: 2024-09-10}
|
url: {value: "https://bevyengine.org/examples/", date: 2024-09-14}
|
||||||
bevy_cheatbook:
|
bevy-cheatbook:
|
||||||
type: Web
|
type: Web
|
||||||
title: Unofficial Bevy Cheat Book
|
title: Unofficial Bevy Cheat Book
|
||||||
author:
|
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:
|
lvs_68:
|
||||||
type: Book
|
type: Book
|
||||||
title: Programmatūras prasību specifikācijas ceļvedis
|
title: Programmatūras prasību specifikācijas ceļvedis
|
||||||
@ -39,8 +39,124 @@ lvs_72:
|
|||||||
date: 1996-03-27
|
date: 1996-03-27
|
||||||
organization: Latvijas Nacionālais standartizācijas un metroloģijas centrs
|
organization: Latvijas Nacionālais standartizācijas un metroloģijas centrs
|
||||||
page-total: 13
|
page-total: 13
|
||||||
pipeline:
|
maze-generation:
|
||||||
type: Web
|
type: Web
|
||||||
title: "CI/CD: The what, why, and how"
|
title: Maze Generation
|
||||||
author:
|
author:
|
||||||
url: https://github.com/resources/articles/devops/ci-cd
|
url: https://rosettacode.org/wiki/Maze_generation
|
||||||
|
sem-ver:
|
||||||
|
type: Web
|
||||||
|
title: Semantiskā versiju veidošana
|
||||||
|
author: Tom Preston-Werner
|
||||||
|
url: {value: "https://semver.org/", date: 2024-09-17}
|
||||||
|
omg-uml:
|
||||||
|
type: Book
|
||||||
|
title: OMG Unified Modeling Language (OMG UML)
|
||||||
|
author: Object Management Group
|
||||||
|
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
@ -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)
|
||||||
|
}
|
||||||
283
presentation.typ
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,13 +1,18 @@
|
|||||||
#import "@preview/i-figured:0.2.4"
|
#import "@preview/i-figured:0.2.4"
|
||||||
#import "@preview/big-todo:0.2.0": *
|
|
||||||
#import "@preview/tablex:0.0.9": tablex
|
#import "@preview/tablex:0.0.9": tablex
|
||||||
|
#import "@preview/headcount:0.1.0": *
|
||||||
|
|
||||||
#let indent = 1cm
|
#let indent = 1cm
|
||||||
|
|
||||||
|
#let indent-par(
|
||||||
|
body,
|
||||||
|
) = par(h(indent) + body)
|
||||||
|
|
||||||
|
|
||||||
#let project(
|
#let project(
|
||||||
university: "",
|
university: "",
|
||||||
faculty: "",
|
faculty: "",
|
||||||
type: "",
|
thesis_type: "",
|
||||||
title: [],
|
title: [],
|
||||||
authors: (),
|
authors: (),
|
||||||
advisor: "",
|
advisor: "",
|
||||||
@ -27,13 +32,36 @@
|
|||||||
paper: "a4",
|
paper: "a4",
|
||||||
)
|
)
|
||||||
set text(
|
set text(
|
||||||
//font: "New Computer Modern",
|
font: (
|
||||||
font: "CMU", size: 12pt, hyphenate: auto, lang: "lv", region: "lv",
|
"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)
|
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
|
// Formatting for regular text
|
||||||
set par(
|
set par(
|
||||||
justify: true,
|
justify: true,
|
||||||
@ -42,6 +70,7 @@
|
|||||||
spacing: 1.5em,
|
spacing: 1.5em,
|
||||||
)
|
)
|
||||||
show heading: set block(spacing: 1.5em)
|
show heading: set block(spacing: 1.5em)
|
||||||
|
|
||||||
set terms(separator: [ -- ])
|
set terms(separator: [ -- ])
|
||||||
|
|
||||||
// Headings
|
// Headings
|
||||||
@ -57,7 +86,7 @@
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
it
|
text(12pt, it)
|
||||||
}
|
}
|
||||||
""
|
""
|
||||||
v(-1cm)
|
v(-1cm)
|
||||||
@ -97,7 +126,7 @@
|
|||||||
upper(
|
upper(
|
||||||
text(
|
text(
|
||||||
size: 16pt,
|
size: 16pt,
|
||||||
type,
|
thesis_type,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -137,24 +166,33 @@
|
|||||||
|
|
||||||
align(
|
align(
|
||||||
center,
|
center,
|
||||||
upper(
|
upper(text(date)),
|
||||||
text(date),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
/* Title page config end */
|
/* Title page config end */
|
||||||
|
|
||||||
// WARNING: remove before sending
|
// Start page numbering
|
||||||
todo_outline
|
set page(
|
||||||
|
numbering: "1",
|
||||||
|
number-align: center,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WARNING: remove before sending
|
||||||
|
// outline(title: "TODOs", target: figure.where(kind: "todo"))
|
||||||
/* --- Figure/Table config start --- */
|
/* --- Figure/Table config start --- */
|
||||||
show heading: i-figured.reset-counters
|
show heading: i-figured.reset-counters
|
||||||
show figure: i-figured.show-figure.with(numbering: "1.1.")
|
show figure: i-figured.show-figure.with(numbering: "1.1.")
|
||||||
set figure(placement: auto)
|
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 block(breakable: true)
|
||||||
show figure.where(kind: "i-figured-table"): set figure.caption(position: top)
|
show figure.where(kind: "i-figured-table"): set figure.caption(position: top)
|
||||||
|
show figure.where(kind: "attachment"): set figure.caption(position: top)
|
||||||
|
show figure.where(kind: raw): set figure.caption(position: top)
|
||||||
|
|
||||||
|
|
||||||
show figure: set par(justify: false) // disable justify for figures (tables)
|
show figure: set par(justify: false) // disable justify for figures (tables)
|
||||||
|
show figure.where(kind: table): set par(leading: 1em)
|
||||||
|
show figure.where(kind: image): set par(leading: 0.75em)
|
||||||
show figure.caption: set text(size: 11pt)
|
show figure.caption: set text(size: 11pt)
|
||||||
|
|
||||||
show figure.caption: it => {
|
show figure.caption: it => {
|
||||||
@ -176,10 +214,91 @@
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
it.kind in (
|
||||||
|
"i-figured-raw",
|
||||||
|
"i-figured-\"attachment\"",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return align(
|
||||||
|
end,
|
||||||
|
it.counter.display() + ". pielikums. " + text(it.body),
|
||||||
|
)
|
||||||
|
}
|
||||||
it
|
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 --- */
|
/* --- Figure/Table config end --- */
|
||||||
|
|
||||||
set list(marker: (
|
set list(marker: (
|
||||||
@ -210,12 +329,6 @@
|
|||||||
)
|
)
|
||||||
/* ToC config end */
|
/* ToC config end */
|
||||||
|
|
||||||
// Start page numbering
|
|
||||||
set page(
|
|
||||||
numbering: "1",
|
|
||||||
number-align: center,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
// show link: set text(fill: blue.darken(20%))
|
// show link: set text(fill: blue.darken(20%))
|
||||||
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
#import "@preview/tablex:0.0.9": tablex
|
#import "@preview/tablex:0.0.9": tablex
|
||||||
#import "@preview/big-todo:0.2.0": todo as TODO
|
|
||||||
|
|
||||||
#let custom-block(
|
#let custom-block(
|
||||||
item,
|
item,
|
||||||
@ -22,7 +21,7 @@
|
|||||||
..items,
|
..items,
|
||||||
) = {
|
) = {
|
||||||
set par(first-line-indent: 0pt)
|
set par(first-line-indent: 0pt)
|
||||||
figure(
|
return figure(
|
||||||
gap: 1.5em,
|
gap: 1.5em,
|
||||||
kind: table,
|
kind: table,
|
||||||
caption: if caption != "" {
|
caption: if caption != "" {
|
||||||
@ -43,9 +42,7 @@
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
#for i in range(
|
#for i in range(items.pos().len()) {
|
||||||
items.pos().len(),
|
|
||||||
) {
|
|
||||||
if titles.len() > 0 {
|
if titles.len() > 0 {
|
||||||
custom-block(
|
custom-block(
|
||||||
text(
|
text(
|
||||||
@ -54,13 +51,10 @@
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
custom-block(
|
custom-block(items.pos().at(i))
|
||||||
items.pos().at(i),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
linebreak()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#let parameter-table(
|
#let parameter-table(
|
||||||
@ -111,11 +105,11 @@
|
|||||||
if caption == "" {
|
if caption == "" {
|
||||||
caption = items.pos().first()
|
caption = items.pos().first()
|
||||||
}
|
}
|
||||||
longtable(
|
return longtable(
|
||||||
titles: (
|
titles: (
|
||||||
"Funkcijas nosaukums",
|
"Funkcijas nosaukums",
|
||||||
"Funkcijas identifikators",
|
"Funkcijas identifikators",
|
||||||
"Ievads",
|
"Apraksts",
|
||||||
"Ievade",
|
"Ievade",
|
||||||
"Apstrāde",
|
"Apstrāde",
|
||||||
"Izvade",
|
"Izvade",
|
||||||
@ -148,58 +142,113 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#let entity-table(
|
#let codeblock(caption, filename, lang: "rust") = {
|
||||||
caption: "",
|
show figure: set block(breakable: true)
|
||||||
id: (),
|
show raw: set par.line(numbering: "1")
|
||||||
..items,
|
set figure(numbering: "1")
|
||||||
) = {
|
|
||||||
if id == () {
|
|
||||||
id = (
|
|
||||||
"id",
|
|
||||||
"serial8",
|
|
||||||
"primary key, not null",
|
|
||||||
"Unikālais identifikators",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
figure(
|
figure(
|
||||||
caption: caption,
|
caption: caption,
|
||||||
kind: table,
|
kind: "attachment",
|
||||||
tablex(
|
supplement: "pielikums",
|
||||||
columns: (4cm, 3cm, auto, auto),
|
raw(
|
||||||
repeat-header: true,
|
read(filename),
|
||||||
/* Header */
|
block: true,
|
||||||
[*Lauks*], [*Datu tips*], [*Lauka atribūti*], [*Apraksts*],
|
lang: lang,
|
||||||
|
|
||||||
..entity-table-row(..id), // id row
|
|
||||||
|
|
||||||
..for i in range(items.pos().len(), step:4){
|
|
||||||
entity-table-row(..items.pos().slice(i, i+4))
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#let todo(
|
#let components-table(
|
||||||
|
caption: str,
|
||||||
..body,
|
..body,
|
||||||
) = {
|
) = {
|
||||||
TODO(
|
figure(
|
||||||
..body,
|
caption: caption,
|
||||||
inline: true,
|
kind: table,
|
||||||
big_text: 14pt,
|
tablex(
|
||||||
small_text: 12pt,
|
columns: 3,
|
||||||
|
[*Komponente*],
|
||||||
|
[*Apraksts*],
|
||||||
|
[*Pielietojums*],
|
||||||
|
..body,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#let hyperlink-source(
|
#let events-table(
|
||||||
author,
|
caption: str,
|
||||||
title,
|
..body,
|
||||||
link_str,
|
|
||||||
date,
|
|
||||||
) = {
|
) = {
|
||||||
if link_str == "" {
|
figure(
|
||||||
[#author #title Aplūkots #date.display("[day].[month].[year]")]
|
caption: caption,
|
||||||
} else {
|
kind: table,
|
||||||
[#author #title Pieejams: #link(link_str) aplūkots #date.display("[day].[month].[year]")]
|
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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||