diff --git a/content/articles/dijkstras-shortest-path.md b/content/articles/dijkstras-shortest-path.md
index b66694e..9884933 100644
--- a/content/articles/dijkstras-shortest-path.md
+++ b/content/articles/dijkstras-shortest-path.md
@@ -10,6 +10,8 @@ tags:
Implementation of the shortest path algorithm, invented by Dijkstra.
+
+
```python
#!/usr/bin/python
diff --git a/content/articles/first.md b/content/articles/first.md
deleted file mode 100644
index 0a5c0a2..0000000
--- a/content/articles/first.md
+++ /dev/null
@@ -1,88 +0,0 @@
----
-title: "My First Blog Post"
-description: "This is a test article."
-navigation: true
-date: "2025-03-01"
-img: "/img/personal-website/login.png"
-tags:
- - C++20
- - GLSL
- - Lua
- - Software
----
-
-Foobarbazbuz.
-
-## Component Rendering
-
-::ExampleComponent
-#namedslot
-This is a Named Slot
-#default
-This is the Default Slot
-::
-
-### Testing Some Markdown Features
-
-A link: [website-vue](https://github.com/riyyi/website-vue)
-
-A codeblock:
-```js [file.js]{2} meta-info=val
-export default () => {
- console.log('Code block')
-}
-```
-
-```cpp
-int i = 0;
-```
-
-```php
-protected static $router;
-$path = parse_url($_SERVER['REQUEST_URI'])['path'];
-```
-
-Inline `hightlight` looks like so.
-
-Inline highlight with language `const code: string = 'highlighted code inline'`{lang="ts"} like this.
-
-- An
-- Unordered
-- List
-
-## Heading With No Subheading
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a tempor
-dolor. Nullam mattis sapien vel finibus dignissim. Etiam et diam ultrices,
-aliquam enim nec, commodo sem. Cras ut faucibus risus. Suspendisse vel faucibus
-ipsum. Duis vel orci nec arcu porttitor fermentum eu quis est. Phasellus elit
-odio, elementum ac placerat at, feugiat sit amet sapien.
-
-Vestibulum dapibus pharetra metus. Integer volutpat lacus nec enim euismod, id
-dignissim felis rhoncus. Cras commodo tempus turpis, eu vehicula mi lacinia
-eget. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
-inceptos himenaeos. Integer rhoncus dolor ut dolor gravida vestibulum. Ut
-dignissim orci a ornare ullamcorper. Nam facilisis mauris sit amet nunc
-fermentum pretium. Nullam in nisi at risus luctus viverra. Mauris non congue
-dolor, vel finibus lectus. Nam quis leo pretium, sodales augue et, dictum sem.
-
-Curabitur velit ante, imperdiet in eros eu, iaculis gravida risus. Nullam ut
-feugiat eros, viverra vehicula sem. Aliquam finibus mi magna, eu fringilla
-tellus ullamcorper vel. Fusce eget auctor mi. Mauris venenatis pellentesque
-arcu. Nam ac diam sem. Nulla suscipit sed risus non vehicula. Cras molestie
-lectus et tincidunt tempor. Ut tempus lorem id augue semper convallis. Aliquam
-vel dui quis dolor cursus faucibus eget a mi. Curabitur tempus justo diam, a
-facilisis ligula tincidunt viverra. Donec sodales quis dolor at dignissim.
-Nullam placerat vitae urna quis bibendum.
-
-Integer a magna a velit bibendum mollis. Donec lobortis molestie diam at rutrum.
-Nunc viverra gravida metus at facilisis. Nulla sit amet erat sodales, commodo
-elit sed, ultrices magna. Aliquam vel purus fringilla, gravida enim quis,
-aliquam augue. Duis interdum et erat nec feugiat. Praesent vitae lacinia leo,
-non suscipit purus.
-
-Aenean massa magna, imperdiet id ex quis, mollis auctor lectus. Cras velit
-nulla, volutpat eu neque id, semper venenatis urna. Integer in blandit ex, non
-scelerisque nisi. Ut sagittis tincidunt enim at volutpat. Sed hendrerit metus ac
-interdum laoreet. In rutrum turpis in nulla vulputate suscipit. Nunc dictum nisl
-id magna laoreet dapibus.
diff --git a/content/articles/inferno.md b/content/articles/inferno.md
new file mode 100644
index 0000000..06a12c0
--- /dev/null
+++ b/content/articles/inferno.md
@@ -0,0 +1,432 @@
+---
+title: "Inferno"
+description: "An open-source game engine."
+navigation: true
+date: "2019-12-10"
+img: "/img/inferno/preview.png"
+tags:
+ - C++20
+ - GLSL
+ - Lua
+ - CMake
+ - Software
+---
+
+An open-source game engine.
+Repository at
+[GitHub](https://github.com/riyyi/inferno){target="_blank"},
+[GitLab](https://gitlab.com/riyyi/inferno){target="_blank"} or
+[Gitea](https://git.riyyi.com/riyyi/inferno){target="_blank"}.
+
+
+Video games have always been a hobby of mine, but what if I could combine my
+profession with this hobby? Then you get this project, a game engine!
+
+This open-source game engine is written in C++20, GLSL and Lua with the
+libraries EnTT, glad, GLFW, GLM, sol3 and stb, using the build tool CMake.
+
+## Abstractions
+
+Making a game engine is not an easy task, if the project has any chance of
+success it needs a solid foundation. Therefor, the main focus of the project
+thus far has been to work on solid, easy-to-use abstractions. The additional
+benefit of this approach is that I learned a lot about software design in the
+process.
+
+### Events
+
+The library used for window and context creation and receiving input is GLFW,
+this is abstracted away in a "Window" wrapper class. This "Window" is created in
+the "Application" class, which also binds the callback function for event
+handling.
+
+```cpp
+// Make it more readable
+#define NF_BIND_EVENT(f) std::bind(&f, this, std::placeholders::_1)
+
+m_window = std::make_unique();
+m_window->setEventCallback(NF_BIND_EVENT(Application::onEvent));
+```
+
+The event callback is stored in a std::function.
+
+```cpp
+inline void setEventCallback(const std::function& callback) { m_eventCallback = callback; }
+```
+
+Inside the "Window" wrapper, we first associate the wrapper to the internal "GLFWwindow" object.
+```cpp
+glfwSetWindowUserPointer(m_window, this);
+```
+
+Then, it can set the appropriate callback functions. In this example, we set the
+position of the mouse. It gets the "Window" wrapper from the association, then
+creates an event object, which is passed on to the callback function.
+
+```cpp
+// Mouse position callback
+glfwSetCursorPosCallback(m_window, [](GLFWwindow* window, double xPos, double yPos) {
+ Window& w = *(Window*)glfwGetWindowUserPointer(window);
+
+ MousePositionEvent event(xPos, yPos);
+ w.m_eventCallback(event);
+});
+```
+
+All we have to do to handle these incoming events is create an event dispatcher
+and call dispatch. The dispatcher simply checks if the current event is of the
+same type as the provided event and calls the function if this is the case.
+
+```cpp
+void Application::onEvent(Event& e)
+{
+ EventDispatcher dispatcher(e);
+ dispatcher.dispatch(NF_BIND_EVENT(Application::onMousePosition));
+}
+```
+
+### Logging
+
+I wanted logging to work with both `printf` and `std::cout` style logging. To
+make logging fast and easy to use, it also has to be compatible with
+user-defined types. The class hierarchy looks like the following:
+
+```
+.
+└── LogStream
+ └── BufferedLogStream
+ ├── DebugLogStream
+ └── StringLogStream
+```
+
+"LogStream" is an abstract class with a pure virtual function named "write", to
+be used as the interface. We need both variants of the write function in order
+to also print extended ASCII characters.
+
+```cpp
+class LogStream {
+public:
+ virtual void write(const char* characters, int length) const = 0;
+ virtual void write(const unsigned char* characters, int length) const = 0;
+};
+```
+
+To extend the functionality of the "LogStream" class, we override the bitwise
+left shift `<<` operator. Support for user-defined types is added in the same way.
+
+```cpp
+const LogStream& operator<<(const LogStream& stream, const std::string& value)
+{
+ stream.write(value.c_str(), value.length());
+ return stream;
+}
+
+const LogStream& operator<<(const LogStream& stream, bool value)
+{
+ return stream << (value ? "true": "false");
+}
+```
+
+In order to keep I/O performance high, logging is done in a buffered manner.
+When a "BufferedLogStream" is created it allocates an array of 128 bytes in
+length on the stack, which you can then write to. If the buffer size is
+exceeded, the buffer grows automatically to the required size in chunks of 128,
+this time on the heap.
+
+```cpp
+// Append to buffer
+memcpy(buffer() + m_count, characters, length);
+
+// Buffer is increased in chunks of 128 bytes
+size_t newCapacity = (m_count + bytes + BUFFER_SIZE - 1) & ~(BUFFER_SIZE - 1);
+```
+
+The "DebugLogStream" class is used to display messages. It has two variables to
+configure this behavior, "newline" and "type", to indicate if a newline should
+be printed and the color of the message. When the object goes out of scope, it
+will print everything that was added to the buffer in one go.
+
+```cpp
+fwrite(buffer(), 1, count(), stdout);
+```
+
+Now we get to the actual usage of the system. Using helper functions, printing
+colored and formatted messages is easy. Each helper function also has a
+non-newline variant as newlines are optional. They simply create a
+"DebugLogStream" and return it.
+
+```cpp
+DebugLogStream dbg(bool newline);
+DebugLogStream info(bool newline);
+DebugLogStream warn(bool newline);
+DebugLogStream danger(bool newline);
+DebugLogStream success(bool newline);
+
+dbg() << "Hello World! " << 2.5 << " " << true;
+info() << "This will be printed in blue.";
+```
+
+The printf style logging is more complicated than the previous use case. This is
+because the function needs to be able to process any number of arguments and of
+any type, this is accomplished using a template parameter pack. We use recursion
+to loop through all the parameters in this pack, with an overload for when there
+is no parameter expansion. Because the "DebugLogStream" is used for printing,
+all the type overloads are available to us, so we don't have to specify the type
+like usual with `printf`.
+
+```cpp
+void dbgln(Log type, bool newline);
+void dbgln(Log type, bool newline, const char* format);
+
+template
+void dbgln(Log type, bool newline, const char* format, T value, P&&... parameters)
+{
+ std::string_view view { format };
+
+ for(uint32_t i = 0; format[i] != '\0'; i++) {
+
+ if (format[i] == '{' && format[i + 1] == '}') {
+ DebugLogStream(type, false) << view.substr(0, i) << value;
+ dbgln(type, newline, format + i + 2, parameters...);
+ return;
+ }
+ }
+}
+```
+
+This style of logging also has a bunch of helper functions to make using them
+quick and easy.
+
+```cpp
+template void dbgln(const char* format, P&&... parameters) { dbgln(Log::None, true, format, std::forward
(parameters)...); }
+template void infoln(const char* format, P&&... parameters) { dbgln(Log::Info, true, format, std::forward
(parameters)...); }
+template void warnln(const char* format, P&&... parameters) { dbgln(Log::Warn, true, format, std::forward
(parameters)...); }
+template void dangerln(const char* format, P&&... parameters) { dbgln(Log::Danger, true, format, std::forward
(parameters)...); }
+template void successln(const char* format, P&&... parameters) { dbgln(Log::Success, true, format, std::forward
(parameters)...); }
+
+dbgln("Hello {}, print anything! {} {}", "World", 2.5, true);
+dangerln("This will print in {}.", "red");
+```
+
+Finally, "StringLogStream" is used to convert any supported type into a
+std::string. This is achieved by simply converting the buffer in
+"BufferedLogStream" to a string and setting the provided string to it when the
+object goes out of scope.
+
+```cpp
+StringLogStream str(std::string* fill);
+
+std::string result;
+str(&result) << "Add " << "anything " << 2.5 << " " << false;
+```
+
+### Shaders
+
+"Shader" functionality is split into two classes, "Shader" and "ShaderManager".
+The manager does exactly what the name would suggest, it manages the resources.
+Using convenient functions you can `add`, `load`, `check existence`, `remove`
+shaders. The shaders get stored in a hash table (hash map), with the key being
+its name and the value a `std::shared_ptr` to the shader object. Adding anything
+to the manager that has already been loaded will simply return the existing
+instance, to prevent duplication. The other pairs "Texture/TextureManager",
+"Font/FontManager" and "Gltf/GltfManager" are structured similarly.
+
+```cpp
+void add(const std::string& name, const std::shared_ptr& shader);
+std::shared_ptr load(const std::string& name);
+std::shared_ptr load(const std::string& vertexSource,
+ const std::string& fragmentSource);
+bool exists(const std::string& name);
+
+void remove(const std::string& name);
+void remove(const std::shared_ptr& shader);
+
+std::unordered_map> m_shaderList;
+```
+
+To construct a "Shader", only a name needs to be provided. It will then load,
+compile and link both the vertex and fragment shader files. Any errors like
+files not existing or GLSL syntax errors will be printed to the console.
+
+```cpp
+// Get file contents
+std::string vertexSrc = File::read(name + ".vert");
+std::string fragmentSrc = File::read(name + ".frag");
+
+// Compile shaders
+uint32_t vertexID = compileShader(GL_VERTEX_SHADER, vertexSrc.c_str());
+uint32_t fragmentID = compileShader(GL_FRAGMENT_SHADER, fragmentSrc.c_str());
+
+// Link shaders
+if (vertexID > 0 && fragmentID > 0) {
+ m_id = linkShader(vertexID, fragmentID);
+}
+```
+
+An uniform is a global "Shader" variable, to set uniform data, there are
+functions for the most common used data types. ASSERT is a wrapper for `dbgln`,
+which can print any registered data types, explained in the
+[Logging](#logging)
+section.
+
+```cpp
+int32_t findUniform(const std::string& name) const;
+
+void setInt(const std::string& name, int value);
+void setInt(const std::string& name, int* values, uint32_t count);
+void setFloat(const std::string& name, float value) const;
+void setFloat(const std::string& name, float v1, float v2, float v3, float v4) const;
+void setFloat(const std::string& name, glm::vec2 value) const;
+void setFloat(const std::string& name, glm::vec3 value) const;
+void setFloat(const std::string& name, glm::vec4 value) const;
+void setFloat(const std::string& name, glm::mat3 matrix) const;
+void setFloat(const std::string& name, glm::mat4 matrix) const;
+
+void bind() const;
+void unbind() const;
+
+int32_t Shader::findUniform(const std::string& name) const
+{
+ int32_t location = glGetUniformLocation(m_id, name.c_str());
+ ASSERT(location != -1, "Shader could not find uniform '{}'", name);
+ return location;
+}
+
+void Shader::setInt(const std::string& name, int value)
+{
+ // Set unifrom int
+ glUniform1i(findUniform(name), value);
+}
+```
+
+In the renderer we only need to do the following. The `the` function in
+"ShaderManager" is a static function that gets the instance of the singleton.
+
+```cpp
+m_shader = ShaderManager::the().load("assets/glsl/batch-quad");
+
+m_shader->bind();
+m_shader->setFloat("u_projectionView", cameraProjectionView);
+m_shader->unbind();
+```
+
+### Buffers
+
+Rendering in OpenGL is done using a set of two buffers, the vertex buffer and
+the index buffer. The vertex buffer is used to store geometry data, in the
+format of points. From these points, you can construct triangles, which can
+actually be rendered. The constructed triangles are stored as indexes to points
+in the index buffer, the simplest form of an index buffer of a single triangle
+looks like the following: `[0, 1, 2]`. When you have these two buffers set up
+correctly, you can draw a triangle.
+
+```cpp
+void RenderCommand::drawIndexed(const VertexArray& vertexArray, uint32_t indexCount)
+{
+ uint32_t count = indexCount ? indexCount : vertexArray.getIndexBuffer()->getCount();
+ glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr);
+}
+```
+
+To create a vertex buffer a lot of manual setup is needed. This includes offset
+calculation, which is very prone to errors, so these steps are abstracted away
+in the "VertexBuffer" class. A "VertexBuffer" object stores a "BufferLayout",
+which stores a `std::vector` of "BufferElements". "BufferElements" are what
+OpenGL calls vertex attributes, which allows us to specify any input we want,
+because of this we also have to specify how the data should be interpreted. The
+"BufferElement" objects hold the variables `type`, the types `size`, `offset` in
+the buffer and if the data is normalized, these can be accessed via getter
+functions. Now for the fun part of vertex buffers, the "BufferLayout", via a
+`std::initializer_list` they can be constructed very easily.
+
+```cpp
+BufferLayout::BufferLayout(const std::initializer_list& elements)
+ : m_elements(elements)
+{
+ calculateOffsetsAndStride();
+}
+
+void BufferLayout::calculateOffsetsAndStride()
+{
+ m_stride = 0;
+ for (auto& element : m_elements) {
+ element.setOffset(m_stride);
+ m_stride += element.getSize();
+ }
+}
+```
+
+Because of OpenGL's C-like API, rendering requires manual binding of the vertex
+attribute configurations and vertex buffers, which is a lot of boilerplate. In
+order to simplify this, a new concept was added called a vertex array object
+(also known as VAO), which is actually required in OpenGL's core profile. VAOs
+store the vertex attribute configuration and the associated vertex buffers, they
+are abstracted away in the "VertexArray" class which stores the "VertexBuffers"
+and an "IndexBuffer". When adding a "VertexBuffer" to a "VertexArray", it will
+set up the vertex attribute configuration.
+
+```cpp
+void VertexArray::addVertexBuffer(std::shared_ptr vertexBuffer)
+{
+ const auto& layout = vertexBuffer->getLayout();
+ ASSERT(layout.getElements().size(), "VertexBuffer has no layout");
+
+ bind();
+ vertexBuffer->bind();
+
+ uint32_t index = 0;
+ for (const auto& element : layout) {
+ glEnableVertexAttribArray(index);
+ glVertexAttribPointer(
+ index,
+ element.getTypeCount(),
+ element.getTypeGL(),
+ element.getNormalized() ? GL_TRUE : GL_FALSE,
+ layout.getStride(),
+ reinterpret_cast(element.getOffset()));
+
+ index++;
+ }
+ m_vertexBuffers.push_back(std::move(vertexBuffer));
+
+ unbind();
+ vertexBuffer->unbind();
+}
+```
+
+The final usage looks like the following, notice how the layout is created
+easily using the `std::initializer_list` in the `setLayout` function.
+
+```cpp
+// Create vertex array
+m_vertexArray = std::make_shared();
+
+// Create vertex buffer
+auto vertexBuffer = std::make_shared(sizeof(QuadVertex) * vertexCount);
+vertexBuffer->setLayout({
+ { BufferElementType::Vec3, "a_position" },
+ { BufferElementType::Vec4, "a_color" },
+ { BufferElementType::Vec2, "a_textureCoordinates" },
+ { BufferElementType::Float, "a_textureIndex" },
+});
+m_vertexArray->addVertexBuffer(vertexBuffer);
+
+uint32_t indices[] { 0, 1, 2, 2, 3, 0 };
+
+// Create index buffer
+auto indexBuffer = std::make_shared(indices, sizeof(uint32_t) * indexCount);
+m_vertexArray->setIndexBuffer(indexBuffer);
+```
+
+## Design Structure
+
+Pictured below are all the classes currently in the codebase and the
+relationships between them.
+
+
+
+## Preview
+
+There isn't much visual to show as of now.
+
+
diff --git a/content/articles/invert-binary-tree.md b/content/articles/invert-binary-tree.md
index ec4c555..0b246e7 100644
--- a/content/articles/invert-binary-tree.md
+++ b/content/articles/invert-binary-tree.md
@@ -10,6 +10,8 @@ tags:
Implementation of inverting a binary tree.
+
+
binarytree.h
```cpp
#ifndef BINARYTREE_H
diff --git a/content/articles/manafiles.md b/content/articles/manafiles.md
new file mode 100644
index 0000000..9748310
--- /dev/null
+++ b/content/articles/manafiles.md
@@ -0,0 +1,63 @@
+---
+title: "Manafiles"
+description: "Config file and package tracking utility."
+navigation: false
+date: "2021-09-04"
+img: "/img/manafiles-unit-test.png"
+tags:
+ - C++20
+ - CMake
+ - Software
+---
+
+Config file and package tracking utility.
+Repository at
+[GitHub](https://github.com/riyyi/manafiles){target="_blank"},
+[GitLab](https://gitlab.com/riyyi/manafiles){target="_blank"} or
+[Gitea](https://git.riyyi.com/riyyi/manafiles){target="_blank"}.
+
+
+Written in C++20, using the build tool CMake.
+
+The goal of this project is simple reproducibility of a Linux system, without
+using symlinks. This is achieved by managing configuration files and keeping
+track of what packages were installed.
+
+In order to use the same bundle of files for multiple systems, the program
+allows to specify variables inside of the configuration files. These
+configuration lines will then get commented or uncommented when pushing the
+configuration to the system, depending on the value of the variables. The
+variables that are supported are the `distribution` the `hostname`, the
+`username`, and the display `session`, which is either X.Org or Wayland.
+
+Below an example of a variable block, where I set the amount of jobs the
+compiler will use, depending on the hostname, because my desktop has more cores
+than my laptop.
+
+```
+# >>> hostname=arch-desktop
+MAKEFLAGS="-j8"
+# <<<
+# >>> hostname=arch-laptop
+# MAKEFLAGS="-j4"
+# <<<
+```
+
+List of features:
+
+- Manage dotfiles and system config files.
+- Selectively comment and uncomment depending on machine configuration.
+- Store a list of all installed packages.
+- Install packages from a stored list.
+- Pattern matching in the config file and cli arguments.
+- Test suite with unit tests, using my own macros.
+
+Pictured below is the output of running the test suite:
+
+
+
+
+
+
+
+
diff --git a/content/articles/opengl-test.md b/content/articles/opengl-test.md
new file mode 100644
index 0000000..5c4d6f7
--- /dev/null
+++ b/content/articles/opengl-test.md
@@ -0,0 +1,36 @@
+---
+title: "OpenGL Test"
+description: "OpenGL test."
+navigation: false
+date: "2021-02-20"
+img: "/img/opengl-test-preview.png"
+tags:
+ - C++14
+ - GLSL
+ - CMake
+ - Software
+---
+
+Config file and package tracking utility.
+Repository at
+[GitHub](https://github.com/riyyi/opengl-test){target="_blank"},
+[GitLab](https://gitlab.com/riyyi/opengl-test){target="_blank"} or
+[Gitea](https://git.riyyi.com/riyyi/opengl-test){target="_blank"}.
+
+
+Written in C++ with glad, glfw, glm and stb, using the build tool CMake.
+Created for the Computer Graphics course at Hogeschool Rotterdam.
+
+Design structure.
+
+
+
+Preview.
+
+
+
+Demo showcasing translation, rotation, scaling and textures working. You can
+also fly around, which is not shown in the video.
+
+::VideoLazy{:src="https://riyyi.com/media/opengl-test-demo.webm"}
+::
diff --git a/content/articles/personal-website.md b/content/articles/personal-website.md
index 1ad3abb..41bce02 100644
--- a/content/articles/personal-website.md
+++ b/content/articles/personal-website.md
@@ -2,7 +2,7 @@
title: "Personal Website"
description: "An open-source content management system, used for this website."
navigation: false
-date: "2025-03-03"
+date: "2021-03-03"
img: "/img/personal-website/admin-menu.png"
tags:
- PHP 7
@@ -13,9 +13,9 @@ tags:
Open-source content management system.
Repository at
-[GitHub](https://github.com/riyyi/website),
-[GitLab](https://gitlab.com/riyyi/website) or
-[Gitea](https://git.riyyi.com/riyyi/website).
+[GitHub](https://github.com/riyyi/website){target="_blank"},
+[GitLab](https://gitlab.com/riyyi/website){target="_blank"} or
+[Gitea](https://git.riyyi.com/riyyi/website){target="_blank"}.
This is the CMS that is used for this website! It's written in PHP 7, MySQL and
diff --git a/content/articles/second.md b/content/articles/second.md
deleted file mode 100644
index 1cd1c32..0000000
--- a/content/articles/second.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-title: "My Second Blog Post"
-description: "This is another article."
-navigation: false
-date: "2025-03-02"
-img: "/img/personal-website/reset-password.png"
-tags:
- - Bash
- - Hardware
----
-
-This is another article.
diff --git a/content/articles/sieve-of-eratosthenes.md b/content/articles/sieve-of-eratosthenes.md
index aef98f7..8521fdc 100644
--- a/content/articles/sieve-of-eratosthenes.md
+++ b/content/articles/sieve-of-eratosthenes.md
@@ -11,6 +11,8 @@ tags:
Implementation of the ancient sieve of Eratosthenes math algorithm, used for
finding prime numbers up to a given limit.
+
+
```c
#include // bool
#include // printf
diff --git a/content/articles/simple-2d-rpg.md b/content/articles/simple-2d-rpg.md
new file mode 100644
index 0000000..508cff1
--- /dev/null
+++ b/content/articles/simple-2d-rpg.md
@@ -0,0 +1,28 @@
+---
+title: "Simple 2D RPG"
+description: "Simple 2D RPG."
+navigation: false
+date: "2015-03-09"
+img: "/img/simple-2d-rpg.png"
+tags:
+ - C++11
+ - CMake
+ - Software
+---
+
+Simple 2D RPG.
+Repository at
+[GitHub](https://github.com/riyyi/rpg){target="_blank"},
+[GitLab](https://gitlab.com/riyyi/rpg){target="_blank"} or
+[Gitea](https://git.riyyi.com/riyyi/rpg){target="_blank"}.
+
+
+Written in C++ with SFML and RapidJSON, using the build tool CMake.
+
+This is a very old project, the only things that are implemented are walking and
+tile collision. Mainly used for exploring the SFML library and Tiled tool for
+level design.
+
+Preview.
+
+
diff --git a/content/articles/space-walk.md b/content/articles/space-walk.md
new file mode 100644
index 0000000..91dc98f
--- /dev/null
+++ b/content/articles/space-walk.md
@@ -0,0 +1,65 @@
+---
+title: "Space Walk"
+description: "Space Walk board game."
+navigation: false
+date: "2021-02-18"
+img: "/img/space-walk/phase-1.png"
+tags:
+ - C++11
+ - Software
+---
+
+Board game.
+Repository at
+[GitHub](https://github.com/riyyi/space-walk){target="_blank"},
+[GitLab](https://gitlab.com/riyyi/space-walk){target="_blank"} or
+[Gitea](https://git.riyyi.com/riyyi/space-walk){target="_blank"}.
+
+
+This is an implementation of the [Space
+Walk](https://mancala.fandom.com/wiki/Space_Walk){target="_blank"} board game,
+written in C++ with ncurses for the UI, built using GNU Make. Created for the
+C++ course at Hogeschool Rotterdam.
+
+UML design.
+
+
+
+
+Setting player names.
+
+
+
+
+
+Message box informing the players of the current phase.
+
+
+
+
+
+Players take turns putting their space ships onto the planets.
+
+
+
+
+
+Message box informing the players of the current phase.
+
+
+
+
+
+Players take turns evacuating the planets, where ships that pass the edges of
+the board will get sucked into black holes (top and bottom square).
+
+
+
+
diff --git a/content/articles/utility-library.md b/content/articles/utility-library.md
new file mode 100644
index 0000000..4574b42
--- /dev/null
+++ b/content/articles/utility-library.md
@@ -0,0 +1,200 @@
+---
+title: "Utility Library"
+description: "Utility library."
+navigation: true
+date: "2022-08-17"
+img: "/img/ruc-example-unit-test.png"
+tags:
+ - C++20
+ - CMake
+ - Software
+---
+
+Utility library.
+Repository at
+[GitHub](https://github.com/riyyi/ruc){target="_blank"},
+[GitLab](https://gitlab.com/riyyi/ruc){target="_blank"} or
+[Gitea](https://git.riyyi.com/riyyi/ruc){target="_blank"}.
+
+
+C++20 utility library without any dependencies, using build tool CMake.
+
+This is an attempt at deduplicating all the commonly used functionality across
+my projects and create one cohesive style.
+
+## Argument parsing
+
+placeholder
+
+## Formatting library
+
+placeholder
+
+## JSON parsing
+
+This is a full implementation of the JSON
+[RFC 7159](https://datatracker.ietf.org/doc/html/rfc7159)
+specification. Created mostly
+for fun, but also for the convenient API.
+
+First, lets specify some JSON that we want to parse.
+
+```js
+{
+ "window": {
+ "title": "Inferno",
+ "width": 1280
+ "height": 720,
+ "fullscreen": "windowed",
+ "vsync": false,
+ }
+}
+```
+
+Then, define the structs the JSON will get serialized into.
+```cpp
+struct WindowProperties {
+ std::string title { "Inferno" };
+ uint32_t width { 1280 };
+ uint32_t height { 720 };
+ std::string fullscreen { "borderless" };
+ bool vsync { true };
+};
+
+struct SettingsProperties {
+ WindowProperties window;
+};
+```
+
+To deserialize the JSON into the struct defined above, we first need to read the
+contents of the JSON file. Then we call the parse function on it, which will
+return an instance of the base class of the JSON library. Calling the get
+function on this instance will try to convert it to the specified type, using
+user-declared conversion functions, more on this later.
+
+```cpp
+ruc::Json object = ruc::Json::parse(ruc::File("assets/settings.json").data());
+
+if (object.type() != ruc::Json::Type::Object) {
+ ruc::warn("Settings invalid formatting");
+ return false;
+}
+
+SettingsProperties properties = object.get();
+```
+
+Serializing back into JSON is simple: just create an instance of the JSON base
+class, whose constructor takes in any type. After, call the dump function on it.
+
+```cpp
+ruc::Json object = properties;
+
+auto file = ruc::File("assets/settings.json");
+file.clear();
+file.append(object.dump(1, '\t'));
+file.append("\n");
+file.flush();
+```
+
+So how does this work, how are the JSON objects mapped to the C++ instances? The
+library is using a [clever
+trick](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html){target="_blank"}
+with Argument-Dependent Lookup (ADL). "Lookup" refers to name lookup, which is
+the process a C++ compiler uses to resolve identifiers to their declarations. We
+let the compiler search for a function of a specific shape, anywhere in the
+project, that allows the library to find functions declared by the user of that
+library! What this effectively means is that all a user has to do is declare a
+`to` and `from` conversion function anywhere in his project and these will get
+used automatically by the library.
+
+Below the implementation of the window settings.
+
+```cpp
+void fromJson(const ruc::Json& object, WindowProperties& window)
+{
+ VERIFY(object.type() == ruc::Json::Type::Object);
+
+ if (object.exists("title"))
+ object.at("title").getTo(window.title);
+ if (object.exists("width"))
+ object.at("width").getTo(window.width);
+ if (object.exists("height"))
+ object.at("height").getTo(window.height);
+ if (object.exists("fullscreen"))
+ object.at("fullscreen").getTo(window.fullscreen);
+ if (object.exists("vsync"))
+ object.at("vsync").getTo(window.vsync);
+}
+
+void toJson(ruc::Json& object, const WindowProperties& window)
+{
+ object = ruc::Json {
+ { "title", window.title },
+ { "width", window.width },
+ { "height", window.height },
+ { "fullscreen", window.fullscreen },
+ { "vsync", window.vsync },
+ };
+}
+
+void fromJson(const ruc::Json& object, SettingsProperties& settings)
+{
+ VERIFY(object.type() == ruc::Json::Type::Object);
+
+ if (object.exists("window"))
+ object.at("window").getTo(settings.window);
+}
+
+void toJson(ruc::Json& object, const SettingsProperties& settings)
+{
+ object = ruc::Json {
+ { "window", settings.window }
+ };
+}
+```
+
+## Unit test macros
+
+These are some macros to quickly setup a unit test. The usage is made very
+simple, as there is no need to setup a main function entrypoint.
+
+To accomplish this, the macro has a trick to declare the unit test inside of a
+struct and that also has a static variable of that struct type. Because its
+static it will get allocated on program startup and in the constructor of the
+struct it will register the unit test function in the `TestSuite` class.
+
+What this effectively means, is that after the CMake configuration is setup, all
+a user has to do is create a `.cpp` file and put a test inside of it with the
+`TEST_CASE` macro.
+
+```cpp
+#include "macro.h"
+#include "testcase.h"
+#include "testsuite.h"
+
+TEST_CASE(ExampleUnitTest)
+{
+ // Test a boolean value
+ EXPECT(true);
+}
+
+TEST_CASE(ExampleUnitTestTrue)
+{
+ // Test 2 values, true
+ int leftside = 3;
+ int rightside = 3;
+ EXPECT_EQ(leftside, rightside);
+}
+
+TEST_CASE(ExampleUnitTestFalse)
+{
+ // Test 2 values, false
+ int leftside = 3;
+ int rightside = 5;
+ EXPECT_EQ(leftside, rightside);
+}
+```
+
+Output of the testcases above:
+
+
diff --git a/public/img/inferno/design.png b/public/img/inferno/design.png
new file mode 100644
index 0000000..a92b4f3
Binary files /dev/null and b/public/img/inferno/design.png differ
diff --git a/public/img/inferno/preview.png b/public/img/inferno/preview.png
new file mode 100644
index 0000000..6a7241c
Binary files /dev/null and b/public/img/inferno/preview.png differ
diff --git a/public/img/manafiles-unit-test.png b/public/img/manafiles-unit-test.png
new file mode 100644
index 0000000..7de695d
Binary files /dev/null and b/public/img/manafiles-unit-test.png differ
diff --git a/public/img/opengl-test-design.png b/public/img/opengl-test-design.png
new file mode 100644
index 0000000..030b467
Binary files /dev/null and b/public/img/opengl-test-design.png differ
diff --git a/public/img/opengl-test-preview.png b/public/img/opengl-test-preview.png
new file mode 100644
index 0000000..895db80
Binary files /dev/null and b/public/img/opengl-test-preview.png differ
diff --git a/public/img/ruc-example-unit-test.png b/public/img/ruc-example-unit-test.png
new file mode 100644
index 0000000..3fe0df0
Binary files /dev/null and b/public/img/ruc-example-unit-test.png differ
diff --git a/public/img/simple-2d-rpg.png b/public/img/simple-2d-rpg.png
new file mode 100644
index 0000000..67fbb0e
Binary files /dev/null and b/public/img/simple-2d-rpg.png differ
diff --git a/public/img/space-walk/evacuate.png b/public/img/space-walk/evacuate.png
new file mode 100644
index 0000000..b063fe3
Binary files /dev/null and b/public/img/space-walk/evacuate.png differ
diff --git a/public/img/space-walk/phase-1.png b/public/img/space-walk/phase-1.png
new file mode 100644
index 0000000..5685893
Binary files /dev/null and b/public/img/space-walk/phase-1.png differ
diff --git a/public/img/space-walk/phase-2.png b/public/img/space-walk/phase-2.png
new file mode 100644
index 0000000..3a2f402
Binary files /dev/null and b/public/img/space-walk/phase-2.png differ
diff --git a/public/img/space-walk/place-ship.png b/public/img/space-walk/place-ship.png
new file mode 100644
index 0000000..f3ab422
Binary files /dev/null and b/public/img/space-walk/place-ship.png differ
diff --git a/public/img/space-walk/player-name.png b/public/img/space-walk/player-name.png
new file mode 100644
index 0000000..16f55d2
Binary files /dev/null and b/public/img/space-walk/player-name.png differ
diff --git a/public/img/space-walk/title-screen.png b/public/img/space-walk/title-screen.png
new file mode 100644
index 0000000..e6c7907
Binary files /dev/null and b/public/img/space-walk/title-screen.png differ
diff --git a/public/img/space-walk/uml.png b/public/img/space-walk/uml.png
new file mode 100644
index 0000000..526b8e2
Binary files /dev/null and b/public/img/space-walk/uml.png differ
diff --git a/src/components/video-lazy.vue b/src/components/video-lazy.vue
new file mode 100644
index 0000000..11fd0ef
--- /dev/null
+++ b/src/components/video-lazy.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
diff --git a/src/utils/index.ts b/src/utils/index.ts
index e20a999..2f97f97 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -36,3 +36,14 @@ export const prettyDate = function (date: string | Date): string {
return formatted;
}
+
+/**
+ * Check if DOM element is inside the current viewport
+ */
+export const isInViewport = function(el: HTMLElement): boolean {
+ const rect = el.getBoundingClientRect();
+ return (
+ rect.bottom > 0 &&
+ rect.top < (window.innerHeight || document.documentElement.clientHeight)
+ );
+}