<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Zig on despatches</title><link>https://icle.es/tags/zig/</link><description>Recent content in Zig on despatches</description><generator>Hugo</generator><language>en</language><lastBuildDate>Thu, 19 Mar 2026 20:01:29 +0000</lastBuildDate><atom:link href="https://icle.es/tags/zig/index.xml" rel="self" type="application/rss+xml"/><item><title>Calling Javascript from Zig through WebAssembly</title><link>https://icle.es/2025/09/17/calling-javascript-from-zig-through-webassembly/</link><pubDate>Wed, 17 Sep 2025 15:52:20 +0100</pubDate><guid>https://icle.es/2025/09/17/calling-javascript-from-zig-through-webassembly/</guid><description>&lt;p>The next step for &lt;a href="https://icle.es/excursions/shine.md">shine&lt;/a> is to build a bridge
between &lt;a href="https://icle.es/tags/zig">zig&lt;/a> and &lt;a href="https://icle.es/tags/javascript">javascript&lt;/a>.&lt;/p>
&lt;p>I am currently planning to using &lt;a href="https://supabase.com/">supabase&lt;/a> for storage.
Unsurprisingly, it does not have a zig sdk. It does, however, have a javascript
sdk.&lt;/p>
&lt;p>If I can write basic CRUD operations in javascript and call that from zig
through webassembly, that could make that integration a lot easier.&lt;/p>
&lt;h2 id="goals">Goals&lt;/h2>
&lt;p>There are a few ideal restrictions for me - mainly because writing javascript is
not fun for me.&lt;/p></description><content:encoded><![CDATA[<p>The next step for <a href="https://icle.es/excursions/shine.md">shine</a> is to build a bridge
between <a href="https://icle.es/tags/zig">zig</a> and <a href="https://icle.es/tags/javascript">javascript</a>.</p>
<p>I am currently planning to using <a href="https://supabase.com/">supabase</a> for storage.
Unsurprisingly, it does not have a zig sdk. It does, however, have a javascript
sdk.</p>
<p>If I can write basic CRUD operations in javascript and call that from zig
through webassembly, that could make that integration a lot easier.</p>
<h2 id="goals">Goals</h2>
<p>There are a few ideal restrictions for me - mainly because writing javascript is
not fun for me.</p>
<ul>
<li>Use the Supabase js/ts library through zig</li>
<li>Use TypeScript as much as possible. (I don&rsquo;t love TypeScript, but at least
it&rsquo;s not javascript)</li>
<li>Keep as much of the supabase related code in the web part so that
<a href="https://icle.es/tags/deno">deno</a> and <a href="https://icle.es/tags/lume">lume</a> can handle any heavy lifting.</li>
</ul>
<h2 id="options">Options</h2>
<p>I can&rsquo;t use FFI(Foreign Function Interface):</p>
```zig
extern "env" fn jsLog(ptr: [*]const u8, len: usize) void;
```
```javascript
const wasm = await WebAssembly.instantiateStreaming(fetch("prog.wasm"), {
  env: {
    jsLog: (ptr, len) => {
      /* read from memory and console.log */
    },
  },
});
```
<p>because imgui pulls in emscripten, which means we don&rsquo;t have the ability to call
<code>instantiateStreaming</code>.</p>
<p>With emscripten, declaring an external function is easy enough.</p>
```zig
extern fn jsLog(ptr: [*]const u8, len: usize) void;
```
<p>There are a couple of options to wire them up to the javascript:</p>
<h3 id="libraryjs--mergeinto"><code>library.js</code> / <code>mergeInto</code></h3>
<p>This option requires javascript files on the zig side. If you want to start with
typescript, you&rsquo;ll need to integrate a transpiler into the build chain as well.</p>
<p>First, you want a javascript file - let&rsquo;s call it <code>libshine.js</code>, and pop it into
a <code>js</code> dir.</p>
```javascript
// js/libshine.js
// Emscripten will provide these globals at link/runtime
declare var mergeInto: (lib: any, funcs: Record<string, Function>) => void;
declare var LibraryManager: { library: any };
declare function UTF8ToString(ptr: number, len?: number): string;

mergeInto(LibraryManager.library, {
  jsLog: (ptr: number) => {
    const msg = UTF8ToString(ptr);
    console.log("🟢 Zig says:", msg);
  },
});
```
<p>We then need to pass this <code>js</code> file into the build step</p>
<p>as part of my sokol build step, I pass it in as <code>.extra_args</code>.</p>
```zig
// create a build step which invokes the Emscripten linker
const link_step = try sokol.emLinkStep(b, .{
    .lib_main = shine,
    .target = opts.mod_main.resolved_target.?,
    .optimize = opts.mod_main.optimize.?,
    .emsdk = dep_emsdk,
    .use_webgl2 = true,
    .use_emmalloc = true,
    .use_filesystem = false,
    .shell_file_path = opts.dep_sokol.path("src/sokol/web/shell.html"),
    // set the js file here
    .extra_args = &.{
        "--js-library", "js/libshine.js",
    },
});
```
<p>We can then call it from zig, with something like:</p>
```zig
pub fn main() void {
    jsLog("hello from zig");
}

extern fn jsLog(ptr: [*]const u8) void;
```
<p>From my firefox console:</p>
```
Lume live reloading is ready. Listening for changes...     localhost:3000:102:15
🟢 Zig says: hello from zig                                shine.js:3168:11
```
<h3 id="em_js--em_asm"><code>EM_JS</code> / <code>EM_ASM</code></h3>
<p>The other option is to use
<a href="https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-call-javascript-from-native"><code>EM_JS</code></a>
which involves writing a wee bit of <code>C</code>, which can embed the <code>javascript</code>.</p>
<p>In theory, it&rsquo;s as simple as:</p>
```c
#include <emscripten.h>

EM_JS_DEPS(bla, "$UTF8ToString");

EM_JS(void, jsLog, (const char* s), {
  console.log(UTF8ToString(s));
});
```
<p>and adding it into the build file:</p>
```zig
// build the main file into a library, this is because the WASM 'exe'
// needs to be linked in a separate build step with the Emscripten linker
const shine = b.addLibrary(.{
    .name = "shine",
    .root_module = opts.mod_main,
});

// get the Emscripten SDK dependency from the sokol dependency
const dep_emsdk = opts.dep_sokol.builder.dependency("emsdk", .{});

// need to inject the Emscripten system header include path into
// the cimgui C library otherwise the C/C++ code won't find
// C stdlib headers
const emsdk_incl_path = dep_emsdk.path("upstream/emscripten/cache/sysroot/include");

shine.root_module.addCSourceFile(.{
    .file = b.path("src/libjs.c"),
    .flags = &.{}, // optional extra emcc flags
});
shine.addSystemIncludePath(emsdk_incl_path);
```
<p>The calling code in <code>main.zig</code> remains the same:</p>
```zig
pub fn main() void {
    jsLog("hello from zig");
}

extern fn jsLog(ptr: [*]const u8) void;
```
<p>However, this didn&rsquo;t work, and failed with:</p>
```
error: undefined symbol: jsLog (referenced by root reference (e.g. compiled C/C++ code))
warning: To disable errors for undefined symbols use `-sERROR_ON_UNDEFINED_SYMBOLS=0`
warning: _jsLog may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library
Error: Aborting compilation due to previous errors
```
<p>Thanks to
<a href="https://ziggit.dev/t/help-with-getting-a-simple-call-to-js-through-emscripten-working/12090/3">some help</a>
from <a href="https://ziggit.dev/u/floooh">flooh</a> (who btw put together the
<a href="https://github.com/floooh/sokol">sokol</a> and
<a href="https://github.com/floooh/sokol-zig">sokol-zig</a> packages as well the
<a href="https://github.com/floooh/sokol-zig-imgui-sample">sokol-imgui-sample</a> template
which I used to kick start this project.), I was able to get it working.</p>
<p>Turns out the c file needs to have a function in it that is used in the zig
file - it doesn&rsquo;t need to do anything.</p>
<p>So, based on the suggestion, <code>libjs.c</code> changes to:</p>
```c
#include <emscripten.h>

EM_JS_DEPS(bla, "$UTF8ToString");

EM_JS(void, jsLog, (const char* s), {
  console.log(UTF8ToString(s));
});

void dummy(void) {};
```
<p>and in <code>main.zig</code>:</p>
```zig
pub fn main() void {
    dummy();
    jsLog("hello from zig");
}

extern fn jsLog(ptr: [*]const u8) void;

extern fn dummy() void;
```
<p>From my firefox console:</p>
```
Lume live reloading is ready. Listening for changes...     localhost:3000:102:15
🟢 Zig says: hello from zig                                shine.js:3168:11
```
<p>You can see a working example in [my forked repo](</p>
<h2 id="em_js-directly-through-zig-unsuccessful"><code>EM_JS</code> directly through <code>zig</code> [unsuccessful]</h2>
<p>Looking at the macro for <code>EM_JS</code> and with my good friend ChatGPT, I attempted
translating it to zig and made some progress, but ultimately failed to get it
working. I&rsquo;ll leave the work here in the hopes it might be helpful.</p>
```c
#define _EM_JS(ret, c_name, js_name, params, code)                             \
  _EM_BEGIN_CDECL                                                              \
  ret c_name params EM_IMPORT(js_name);                                        \
  __attribute__((visibility("hidden")))                                        \
  void* __em_js_ref_##c_name = (void*)&c_name;                                 \
  EMSCRIPTEN_KEEPALIVE                                                         \
  __attribute__((section("em_js"), aligned(1))) char __em_js__##js_name[] =    \
    #params "<::>" code;                                                       \
  _EM_END_CDECL
```
<p>The above macro translates to zig roughly (with help from ChatGPT) as:</p>
```zig
extern fn jsLog(ptr: [*]const u8) void;

/// 2. Keep a reference to avoid the linker removing the function.
///    Same role as __em_js_ref_* in the C macro.
pub export const __em_js_ref_jsLog = &jsLog;

/// 3. Embed the JS implementation in a special section called "em_js".
///    Emscripten will scan this and inject the code into the output JS.
export const __em_js__jsLog align(1) linksection("em_js") =
    "(const char* s)<::>{ console.log(UTF8ToString(s)); }\x00";

pub export fn dummy() void {}
```
<p>I added a <code>pub fn</code> and called it from main:</p>
```zig
pub fn log(ptr: [*]const u8) void {
    jsLog(ptr);
}
```
<p>Which gave me the familiar error about not being able to find <code>jsLog</code>.</p>
<p>comparing the linker sections gave some clues:</p>
```
❯ wasm-objdump --section=linking -x <path/to/libjs.o>

libjs.o:        file format wasm 0x1

Section Details:

Custom:
 - name: "linking"
  - symbol table [count=9]
   - 0: F <dummy> func=1 [ binding=global vis=hidden ]
   - 1: D <__em_js_ref_jsLog> segment=0 offset=0 size=4 [ binding=global vis=hidden ]
   - 2: F <jsLog> func=0 [ undefined explicit_name binding=global vis=default ]
   - 3: D <__em_js__jsLog> segment=1 offset=0 size=53 [ exported no_strip binding=global vis=hidden ]
   - 4: S <.debug_abbrev> section=7 [ binding=local vis=default ]
   - 5: G <env.__stack_pointer> global=0 [ undefined binding=global vis=default ]
   - 6: S <.debug_str> section=9 [ binding=local vis=default ]
   - 7: T <env.__indirect_function_table> table=0 [ undefined exported no_strip binding=global vis=default ]
   - 8: S <.debug_line> section=10 [ binding=local vis=default ]
  - segment info [count=2]
   - 0: .data.__em_js_ref_jsLog p2align=2 [ ]
   - 1: em_js p2align=0 [ RETAIN ]
```
<p>and the zig object:</p>
```
❯ wasm-objdump --section=linking -x js.o

js.o:   file format wasm 0x1

Section Details:

Custom:
 - name: "linking"
  - symbol table [count=6]
   - 0: F <dummy> func=1 [ binding=global vis=default ]
   - 1: D <__em_js_ref_jsLog> segment=0 offset=0 size=4 [ binding=global vis=default ]
   - 2: F <jsLog> func=0 [ undefined explicit_name binding=global vis=default ]
   - 3: D <__em_js__jsLog> segment=1 offset=0 size=4 [ binding=global vis=default ]
   - 4: D <__anon_946> segment=2 offset=0 size=54 [ binding=local vis=default ]
   - 5: T <env.__indirect_function_table> table=0 [ undefined exported no_strip binding=global vis=default ]
  - segment info [count=3]
   - 0: .rodata.__em_js_ref_jsLog p2align=2 [ ]
   - 1: em_js p2align=0 [ ]
   - 2: .rodata.__anon_946 p2align=0 [ ]`
```
<p>From what I could understand (which is little), it looks like <code>__em_js__jsLog</code>
in the zig obj is a pointer while from C, it&rsquo;s the full string.</p>
<p>hardcoding it as a static array helped:</p>
```zig
export const __em_js__jsLog align(1) linksection("em_js") = [_]u8{
    '(', 'c','o','n','s','t',' ','c','h','a','r','*',' ','s',')',
    '<',':',':','>','{',' ',
    'c','o','n','s','o','l','e','.','l','o','g','(',
    'U','T','F','8','T','o','S','t','r','i','n','g','(',
    's',')',')',';',' ','}','\x00',
};
```
<p>The output from this is a little more promising</p>
```
❯ wasm-objdump --section=linking -x js.o

js.o:   file format wasm 0x1

Section Details:

Custom:
 - name: "linking"
  - symbol table [count=5]
   - 0: F <dummy> func=1 [ binding=global vis=default ]
   - 1: D <__em_js_ref_jsLog> segment=0 offset=0 size=4 [ binding=global vis=default ]
   - 2: F <jsLog> func=0 [ undefined explicit_name binding=global vis=default ]
   - 3: D <__em_js__jsLog> segment=1 offset=0 size=53 [ binding=global vis=default ]
   - 4: T <env.__indirect_function_table> table=0 [ undefined exported no_strip binding=global vis=default ]
  - segment info [count=2]
   - 0: .rodata.__em_js_ref_jsLog p2align=2 [ ]
   - 1: em_js p2align=0 [ ]
```
<p>Let&rsquo;s look at the two side by side</p>
```
# From C
- 3: D <__em_js__jsLog> segment=1 offset=0 size=53 [ exported no_strip binding=global vis=hidden ]
# From zig
- 3: D <__em_js__jsLog> segment=1 offset=0 size=53 [ binding=global vis=default ]
```
<p>There are some clear differences in how the two are output and I am already
beyond my knowledge level here - so I&rsquo;ll leave it to someone who knows this
stuff better (or wait until I do)</p>
<p>You can
<a href="https://github.com/drone-ah/sokol-zig-imgui-sample/tree/zig_em_js">check out the code in the branch of my forked repo</a></p>
<h2 id="next-steps">Next steps</h2>
<p>My plan is to use <code>EM_JS</code> through <code>C</code> to implement glue JavaScript functions -
something like:</p>
```c
EM_JS(void, jsLog, (const char* s), {
	Module.jsLog(UTF8ToString(s));
});
```
<p>By doing this, I can have one-line js code in the <code>.c</code> file and all the
implementation can go into the web side (and can easily be TypeScript too).</p>
```javascript
window.Module = {
  jsLog: function (msg) {
    console.log("🟢 Zig says:", msg);
  },
};
```
]]></content:encoded></item><item><title>Auto reload WASM with zig+lume</title><link>https://icle.es/2025/09/16/auto-reload-wasm-with-zig-lume/</link><pubDate>Tue, 16 Sep 2025 15:56:42 +0100</pubDate><guid>https://icle.es/2025/09/16/auto-reload-wasm-with-zig-lume/</guid><description>&lt;p>I’ve been taking some time off to rest and recover from health issues that made
it hard to focus. To ease back in, I’ve started a small project:
&lt;a href="https://icle.es/excursions/shine.md">shine&lt;/a>&lt;/p>
&lt;p>The project would do well to be multiplatform - mobile and web. The obvious
choice was &lt;a href="https://icle.es/tags/flutter">flutter&lt;/a> and I have enjoyed working with it before.&lt;/p>
&lt;p>However, as I&amp;rsquo;m currently in love with &lt;a href="https://icle.es/tags/zig">zig&lt;/a>, I wanted to work with
that instead.&lt;/p>
&lt;h2 id="libraries">Libraries&lt;/h2>
&lt;h3 id="graphics">Graphics&lt;/h3>
&lt;p>I&amp;rsquo;ve been playing with &lt;a href="https://icle.es/tags/raylib">raylib&lt;/a> and that was my initial instinct.
However, raylib
&lt;a href="https://github.com/raysan5/raylib/discussions/2681">does not support iOs&lt;/a> and
&lt;a href="https://github.com/raysan5/raylib/discussions/3626">has issues with wasm&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>I’ve been taking some time off to rest and recover from health issues that made
it hard to focus. To ease back in, I’ve started a small project:
<a href="https://icle.es/excursions/shine.md">shine</a></p>
<p>The project would do well to be multiplatform - mobile and web. The obvious
choice was <a href="https://icle.es/tags/flutter">flutter</a> and I have enjoyed working with it before.</p>
<p>However, as I&rsquo;m currently in love with <a href="https://icle.es/tags/zig">zig</a>, I wanted to work with
that instead.</p>
<h2 id="libraries">Libraries</h2>
<h3 id="graphics">Graphics</h3>
<p>I&rsquo;ve been playing with <a href="https://icle.es/tags/raylib">raylib</a> and that was my initial instinct.
However, raylib
<a href="https://github.com/raysan5/raylib/discussions/2681">does not support iOs</a> and
<a href="https://github.com/raysan5/raylib/discussions/3626">has issues with wasm</a></p>
<p>I considered a few options, including</p>
<ul>
<li><a href="https://www.libsdl.org/">sdl</a>, which looked great but was perhaps a little
too low level for me.</li>
<li><a href="https://github.com/Jack-Ji/jok">jok</a> - does not support mobile and possibly
has a little more than I needed.</li>
</ul>
<p>In the end, I decided to go with <a href="https://github.com/floooh/sokol">sokol</a> and
<a href="https://github.com/floooh/sokol-zig">sokol-zig</a>.</p>
<p>While a little more lower level than raylib, it has:</p>
<ul>
<li>a modern clean api</li>
<li>first class mobile support</li>
<li>first class wasm support</li>
</ul>
<h3 id="ui">UI</h3>
<p>I&rsquo;ve been working with <a href="https://icle.es/tags/dvui">dvui</a> a lot recently. Unfortunately, it
doesn&rsquo;t support sokol. <a href="https://github.com/SpexGuy/Zig-ImGui">imgui</a> is a better
option.</p>
<p>There is even a
<a href="https://github.com/floooh/sokol-zig-imgui-sample">template project that I could start from</a>.</p>
<h2 id="wasm-first">WASM first</h2>
<p>To keep things straightforward, I decided to start with wasm. If I make the site
mobile friendly, I could see how it goes and see if it needs a mobile version.</p>
<p>I will need some shared data storage and have been considering
<a href="https://supabase.com/">supabase</a>, which has <a href="https://icle.es/tags/javascript">javascript</a>
libs. By using <a href="https://icle.es/tags/wasm">wasm</a>, I can effectively shim in js functions to
handle that instead of having to write bare rest calls from zig.</p>
<h3 id="structuring-the-project">Structuring the project</h3>
<p>Is this a zig project with a web component, vice versa or indeed two independent
parts that work together.</p>
<p>I did a fair amount of web searching to see if there was some guidance I could I
find for a good way to structure a relatively straightforward zig+js project.</p>
<p>I could not find one. In the end, I decided to keep it fairly straightforward.</p>
```
- shine/
  - src/ # zig code
  - web/ # all the web stuff
```
<h3 id="frontend">Frontend</h3>
<p>After a bit of research, and realising that I will probably need a little bit of
supporting content around <a href="https://icle.es/excursions/shine.md">shine</a>, I decided to go
with <a href="https://lume.land/">lume</a></p>
<p>I used the <a href="https://github.com/lumeland/theme-simple-blog">simple-blog theme</a> as
a template to start from. I could have just pulled the template in but I wanted
a custom homepage.</p>
<p>When I tried to add an <code>index.md</code>, it complained about two files wanting to
write <code>index.html</code>. From what I could find, the easiest way to override the
homepage was to just pick up the theme and edit it - which was easy enough.</p>
<h3 id="wasm--frontend">WASM =&gt; frontend</h3>
<p>I didn&rsquo;t want to copy over the wasm and the js file every time, so I added a
couple of steps to <code>build.zig</code> right after the <code>link_step</code> (also included below)</p>
```zig
// build.zig
// create a build step which invokes the Emscripten linker
const link_step = try sokol.emLinkStep(b, .{
    .lib_main = shine,
    .target = opts.mod_main.resolved_target.?,
    .optimize = opts.mod_main.optimize.?,
    .emsdk = dep_emsdk,
    .use_webgl2 = true,
    .use_emmalloc = true,
    .use_filesystem = false,
    .shell_file_path = opts.dep_sokol.path("src/sokol/web/shell.html"),
});
// attach to default target
b.getInstallStep().dependOn(&link_step.step);

// Copy shine.js from default emscripten output
const js_install = b.addInstallFileWithDir(
    b.path("zig-out/web/shine.js"),
    .{ .custom = "../web/src/static/shine" },
    "shine.js",
);
js_install.step.dependOn(&link_step.step);
b.getInstallStep().dependOn(&js_install.step);

// Copy shine.wasm from default emscripten output
const wasm_install = b.addInstallFileWithDir(
    b.path("zig-out/web/shine.wasm"),
    .{ .custom = "../web/src/static/shine" },
    "shine.wasm",
);
wasm_install.step.dependOn(&link_step.step);
```
<p>These steps will copy across the wasm and the js file across to <code>static/shine</code>.
I wanted to put the js in <code>src/js</code> and the wasm in the static dir. However, the
js file expects the wasm in the same dir. <del>I tried overriding <code>locateFile</code> but
it didn&rsquo;t
work.</del>(<a href="https://icle.es/lume/wasm.md">you can use a different location by overriding <code>locateFile</code></a>)</p>
```html
<!-- index.vto -->
<script>
  window.Module = {
    locateFile: (path, prefix) => {
      if (path.endsWith(".wasm")) {
        return "/static/shine/shine.wasm";
      }
      return prefix + path;
    },
  };
</script>
<script src="/js/shine.js"></script>
```
<p>I was able to get lume to process the javascript file by <code>add</code>ing it.</p>
```javascript
// _config.ts
site.add("static/shine/shine.js");
```
<h2 id="conclusion">Conclusion</h2>
<p>With all of these set up, I was able to run:</p>
```bash
zig build -Dtarget=wasm32-emscripten --watch
```
```

```
<p>in one window. This command will rebuild wasm and provide it to lume whenever
the zig code changes.</p>
```bash
deno task serve
```
<p>Running this in another window will mean that lume will rebuild on any changes,
including a new wasm file and redeploy. The redeploy will trigger an auto-reload
of the page as well if I have it in a browser.</p>
<p>I now effectively have automated reload with changes if I make changes in either
zig or the frontend.</p>
<p>I don&rsquo;t have <em>hot</em> reload - but this is pretty good for now.</p>
]]></content:encoded></item><item><title>Building Pong with Zig and Raylib #6: Font Size, Collision Bugs, and Refactors</title><link>https://icle.es/2025/07/19/building-pong-with-zig-and-raylib-%236-font-size-collision-bugs-and-refactors/</link><pubDate>Sat, 19 Jul 2025 08:00:00 +0100</pubDate><guid>https://icle.es/2025/07/19/building-pong-with-zig-and-raylib-%236-font-size-collision-bugs-and-refactors/</guid><description>&lt;p>In this one, we’ll tidy up our Pong implementation by addressing three key
issues:&lt;/p>
&lt;ol>
&lt;li>The score font is too small&lt;/li>
&lt;li>The ball can get stuck inside a paddle&lt;/li>
&lt;li>Trigger score on edge (not the middle) of the ball going past.&lt;/li>
&lt;/ol>
&lt;p>We’ll also refactor score tracking to better match game logic structure.&lt;/p>
&lt;h2 id="-make-the-score-font-more-readable">🖋️ Make the Score Font More Readable&lt;/h2>
&lt;p>The score text was a little too small. If you&amp;rsquo;re using &lt;code>dvui&lt;/code>, here&amp;rsquo;s how to
adjust it:&lt;/p></description><content:encoded><![CDATA[<p>In this one, we’ll tidy up our Pong implementation by addressing three key
issues:</p>
<ol>
<li>The score font is too small</li>
<li>The ball can get stuck inside a paddle</li>
<li>Trigger score on edge (not the middle) of the ball going past.</li>
</ol>
<p>We’ll also refactor score tracking to better match game logic structure.</p>
<h2 id="-make-the-score-font-more-readable">🖋️ Make the Score Font More Readable</h2>
<p>The score text was a little too small. If you&rsquo;re using <code>dvui</code>, here&rsquo;s how to
adjust it:</p>
```zig
const font_size: f32 = 64;
var label_options: dvui.Options = .{
    .color_text = .white,
    .font_style = .title,
};
label_options.font = label_options.fontGet().resize(font_size);
dvui.label(@src(), "{d}", .{score}, label_options);
```
<p>Thanks to
<a href="https://ziggit.dev/t/building-pong-in-zig-with-raylib-part-1-paddles-and-a-ball/10768/12">code sample from milogreg</a></p>
<p>You may also need to adjust the width and height of the container if the larger
font gets clipped. In our case, font size of 64 felt about right.</p>
<h2 id="-fix-the-ball-getting-stuck-in-the-paddle">🧱 Fix the Ball Getting Stuck in the Paddle</h2>
<p>When the ball moves too far in one frame, it can land <em>inside</em> the paddle and
bounce back and forth infinitely.</p>
<p>This trap happens because we just multiply the x-velocity with <code>-1</code> to reverse
direction.</p>
<p>One way to fix this is to only trigger the bounce if the ball is moving <em>toward</em>
the paddle. For example:</p>
```zig
const crossing_x = switch (self.which) {
    .right => ball.vel.x > 0 and
        ball.pos.x + ball.r >= self.pos.x,
    .left => ball.vel.x < 0 and
        ball.pos.x - ball.r <= self.pos.x + size.x,
};
```
<p>Thanks again to
<a href="https://ziggit.dev/t/building-pong-in-zig-with-raylib-part-1-paddles-and-a-ball/10768/12">code sample from milogreg</a></p>
<p>This ensures we don’t apply the bounce logic when the ball is already inside the
paddle.</p>
<p>Another (potential) way to fix this would be to change the collision logic to
fix the x direction based on whether it&rsquo;s the left or right paddle.</p>
<h2 id="-trigger-a-score-when-the-balls-edge-crosses-the-screen">🧮 Trigger a Score When the Ball’s Edge Crosses the Screen</h2>
<p>Previously, we checked whether the ball’s <strong>center</strong> (<code>ball.x</code>) crossed the
screen edge. Particularly with the ball being bigger than the paddle, this
caused issues when the top/bottom of the paddle hit the ball.</p>
<p><a href="https://icle.es/games/pong/src/Game.zig">Game.zig</a></p>
```zig
if (self.ball.pos.x + self.ball.r > self.screen_width) {
    self.left_score += 1;
    self.ball.reset();
}

if (self.ball.pos.x < self.ball.r) {
    self.right_score += 1;
    self.ball.reset();
}
```
<p>This triggers the score as soon as the edge of the ball crosses the screen
bounds.</p>
<h2 id="-refactor-move-scores-out-of-the-paddle-struct">🔄 Refactor: Move Scores Out of the Paddle Struct</h2>
<p>Storing the score inside the <code>Paddle</code> struct is convenient but semantically odd</p>
<ul>
<li>paddles shouldn’t own scores. Instead, let&rsquo;s move them into your <code>Game</code>
struct:</li>
</ul>
<p>Then, pass the score explicitly to the rendering function.</p>
<p>You can find the updated code in
<a href="https://icle.es/games/pong/src/Game.zig">Game.zig</a>.</p>
<h2 id="-bonus-fix-make-score-display-use-paddle-play-area">✅ Bonus Fix: Make Score Display Use Paddle Play Area</h2>
<p>We now compute each paddle’s <em>play area</em> (a <code>dvui.Rect</code>) and use it to position
the score label, keeping layout logic more re-usable.</p>
<p>We add a <code>play_area</code> field or method to our <code>Paddle</code> struct that returns its
side of the screen. This makes the rendering logic clearer and more flexible.</p>
<h2 id="-whats-next">⏭️ What’s Next?</h2>
<p>Next episode: adding a <strong>pause menu</strong> to Pong, based on what I just built for my
other game, <em>triangle</em>. We’ll add basic Resume/Quit options and freeze game
state mid-play.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-6.md">Watch Video</a></li>
<li><a href="https://icle.es/games/pong/">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/5-ui.md">Smarter Collisions &amp; Cleaner Code</a></li>
<li>Next: Pause Menu</li>
</ul>
]]></content:encoded></item><item><title>Building Pong with Zig and Raylib #5: Show Score with dvui</title><link>https://icle.es/2025/07/15/building-pong-with-zig-and-raylib-%235-show-score-with-dvui/</link><pubDate>Tue, 15 Jul 2025 08:00:00 +0100</pubDate><guid>https://icle.es/2025/07/15/building-pong-with-zig-and-raylib-%235-show-score-with-dvui/</guid><description>&lt;p>In this episode, I finally add a score display to Pong using DVUI, a native Zig
UI framework. The scoring logic was already in place - now it&amp;rsquo;s time to show it
on screen.&lt;/p>
&lt;h2 id="extract-out-game-struct">Extract out &lt;code>Game&lt;/code> struct&lt;/h2>
&lt;p>One of the structural changes I wanted to make to tidy up the code was to pull
out the game logic into its own struct. This change helps to declutter the
&lt;code>main.zig&lt;/code> file, leading the way to add in the dvui scaffolding.&lt;/p></description><content:encoded><![CDATA[<p>In this episode, I finally add a score display to Pong using DVUI, a native Zig
UI framework. The scoring logic was already in place - now it&rsquo;s time to show it
on screen.</p>
<h2 id="extract-out-game-struct">Extract out <code>Game</code> struct</h2>
<p>One of the structural changes I wanted to make to tidy up the code was to pull
out the game logic into its own struct. This change helps to declutter the
<code>main.zig</code> file, leading the way to add in the dvui scaffolding.</p>
<p>This refactor pulls the update and render logic into a <code>Game</code> struct, which
helps declutter <code>main.zig</code> and sets up a better foundation for UI work.</p>
<p><a href="https://icle.es/games/pong/src/Game.zig">Game.zig</a></p>
```zig
pub fn update(self: *Game, dt: f32) void {
    self.ball.checkEdgeCollisions(self.screen_height);
    self.ball.update(dt);
    self.ball.checkPaddleCollision(&self.left_paddle);
    self.ball.checkPaddleCollision(&self.right_paddle);
    if (self.ball.pos.x > self.screen_width) {
        self.left_paddle.score += 1;
        std.debug.print("scores: l: {d}, r: {d}\n", .{ self.left_paddle.score, self.right_paddle.score });
        self.ball.reset();
    }

    if (self.ball.pos.x < 0) {
        self.right_paddle.score += 1;
        std.debug.print("scores: l: {d}, r: {d}\n", .{ self.left_paddle.score, self.right_paddle.score });
        self.ball.reset();
    }

    if (rl.isKeyDown(.w)) {
        self.left_paddle.moveUp(dt);
    }

    if (rl.isKeyDown(.s)) {
        self.left_paddle.moveDown(dt);
    }

    if (rl.isKeyDown(.e)) {
        self.right_paddle.moveUp(dt);
    }

    if (rl.isKeyDown(.d)) {
        self.right_paddle.moveDown(dt);
    }
}

pub fn render(self: *const Game) void {
    self.left_paddle.render();
    self.right_paddle.render();
    self.ball.render();

    showScore(self.screen_width * 0.25, self.left_paddle.score);
    showScore(self.screen_width * 0.75, self.right_paddle.score);
}
```
<h2 id="add-dvui-dependency">Add dvui dependency</h2>
<p>Let&rsquo;s fetch the dependency, adding it to <code>build.zig.zon</code>:</p>
<p><code>zig fetch --save git+https://github.com/david-vanderson/dvui.git</code></p>
<p>Then, in <code>build.zig</code>, we also need to declare it:</p>
```zig
const dvui_dep = b.dependency("dvui", .{
    .target = target,
    .optimize = optimize,
});

const dvui = dvui_dep.module("dvui_raylib");
```
<p>and then add it as a dependency:</p>
```zig
exe.root_module.addImport("dvui", dvui);
```
<h2 id="add-dvui-to-game-loop">Add <code>dvui</code> to game loop</h2>
<p>We also need to initialise dvui in the main loop.</p>
<p><a href="https://icle.es/games/pong/src/main.zig">main.zig</a></p>
<h3 id="import">Import</h3>
```zig
const dvui = @import("dvui");

const RaylibBackend = dvui.backend;
comptime {
    std.debug.assert(@hasDecl(RaylibBackend, "RaylibBackend"));
}
const ray = RaylibBackend.c;
```
<h3 id="initialise">Initialise</h3>
```zig
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const allocator = gpa.allocator();
defer _ = gpa.deinit();

//--------------------------------------------------------------------------
// init Raylib backend
// init() means the app owns the window (and must call CloseWindow itself)
var backend = RaylibBackend.init(allocator);
defer backend.deinit();
backend.log_events = true;

// init dvui Window (maps onto a single OS window)
// OS window is managed by raylib, not dvui
var win = try dvui.Window.init(@src(), allocator, backend.backend(), .{});
defer win.deinit();
```
<h3 id="pre-render">Pre-render</h3>
```zig
// marks the beginning of a frame for dvui, can call dvui functions after this
try win.begin(std.time.nanoTimestamp());

// send all Raylib events to dvui for processing
_ = try backend.addAllEvents(&win);
```
<h3 id="post-render">Post-render</h3>
```zig
_ = try win.end(.{});

// cursor management
if (win.cursorRequestedFloating()) |cursor| {
    // cursor is over floating window, dvui sets it
    backend.setCursor(cursor);
} else {
    // cursor should be handled by application
    backend.setCursor(.arrow);
}
```
<h2 id="show-score">Show Score</h2>
<p>We can now show the score using <code>dvui.label</code> with positioning hardcoded to about
25% and 75% across the screen. There may be a better way to position it but it
works well enough for now.</p>
<p>I had to generate an id for the label so that they were unique. I generated it
the x-position using <code>@intFromFloat()</code>.</p>
<p><a href="https://icle.es/games/pong/src/Game.zig">Game.zig</a></p>
```zig
pub fn render(self: *const Game) void {
    self.left_paddle.render();
    self.right_paddle.render();
    self.ball.render();

    showScore(self.screen_width * 0.25, self.left_paddle.score);
    showScore(self.screen_width * 0.75, self.right_paddle.score);
}

fn showScore(xpos: f32, score: u8) void {
    const id: usize = @intFromFloat(xpos);
    var right = dvui.box(@src(), .horizontal, .{ .rect = .{ .x = xpos, .y = 50, .w = 50, .h = 50 }, .id_extra = id });
    defer right.deinit();

    dvui.label(@src(), "{d}", .{score}, .{ .color_text = .white, .font_style = .title });
}
```
<h2 id="closing">Closing</h2>
<p>As always, things took a bit longer than expected, but by the end:</p>
<ul>
<li>The game&rsquo;s structure is cleaner</li>
<li>DVUI is wired up properly</li>
<li>Scores now show up on screen</li>
</ul>
<p>Shoutout to <a href="https://ziggit.dev/u/milogreg/summary">milo_greg</a> on
<a href="https://ziggit.dev/">ziggit.dev</a> for loads of really valuable feedback and
tips. That kind of thoughtful review really helps.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-5.md">Watch Video</a></li>
<li><a href="https://icle.es/games/pong/">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/4-refactor.md">Smarter Collisions &amp; Cleaner Code</a></li>
<li>Next: <a href="https://icle.es/6-refactor.md">Font Size, Collision Bugs, and Refactors</a></li>
</ul>
]]></content:encoded></item><item><title>Building Pong with Zig and Raylib - Part 4: Smarter Collisions, Cleaner Code</title><link>https://icle.es/2025/07/10/building-pong-with-zig-and-raylib-part-4-smarter-collisions-cleaner-code/</link><pubDate>Thu, 10 Jul 2025 15:33:34 +0100</pubDate><guid>https://icle.es/2025/07/10/building-pong-with-zig-and-raylib-part-4-smarter-collisions-cleaner-code/</guid><description>&lt;p>Change of Plans&lt;/p>
&lt;p>I was going to dive into menus and UI, but after sharing the early version of
Pong on &lt;a href="https://ziggit.dev/">ziggit.dev&lt;/a>, I got a bunch of helpful feedback.
The feedback was the kind that makes you stop and think, &lt;em>ah, right&amp;hellip; I should
probably fix that before carrying on.&lt;/em>&lt;/p>
&lt;p>So that&amp;rsquo;s what this episode became: a collection of fixes, tweaks, and small
refactors that clean up the code and align things more closely with how things
&lt;em>should&lt;/em> be done in Zig (or at least, better than I had them before).&lt;/p></description><content:encoded><![CDATA[<p>Change of Plans</p>
<p>I was going to dive into menus and UI, but after sharing the early version of
Pong on <a href="https://ziggit.dev/">ziggit.dev</a>, I got a bunch of helpful feedback.
The feedback was the kind that makes you stop and think, <em>ah, right&hellip; I should
probably fix that before carrying on.</em></p>
<p>So that&rsquo;s what this episode became: a collection of fixes, tweaks, and small
refactors that clean up the code and align things more closely with how things
<em>should</em> be done in Zig (or at least, better than I had them before).</p>
<h2 id="-naming-matters">🧼 Naming Matters</h2>
<p>I’d originally named my files <code>paddle.zig</code> and <code>ball.zig</code> - lowercase,
snake-case. I had tried to find the guidelines around this, but turns out I only
got half the story. If a file implicitly defines a struct via top-level fields,
it should be named in PascalCase. So, <code>Paddle.zig</code>, not <code>paddle.zig</code>.</p>
<p>It’s a small thing, but one that helps be a bit more idiomatic - and avoids
confusion when others are reading it.</p>
<p>(now to make the same change across many more files in
<a href="https://icle.es/endeavours/triangle.md">triangle</a>)</p>
<h2 id="-default-field-initializers-and-when-not-to-use-them">🛠️ Default Field Initializers (and When <em>Not</em> to Use Them)</h2>
<p>Another thing I learned: structs that aren’t used as config objects shouldn’t
use default field values. Instead, they should have an <code>init</code> constant that
represents their starting state.</p>
<p>I’d missed this distinction, and both of my types were using default values
incorrectly. So I cleaned that up and added the values into the <code>init</code> method.
It’s a subtle change, but it keeps config objects and plain data objects
conceptually separate - and makes it clearer which parts of a struct are
supposed to be overridden.</p>
<h2 id="-rls-please">✨ RLS, Please</h2>
<p>One of the comments suggested I lean more into Zig’s Result Location Syntax -
where you define the type on the left-hand side and let Zig figure out the rest.</p>
<p>I’d been using a mix of styles. Nothing broke, but consistency helps. So I swept
through the code and updated those as well.</p>
```zig
//main.zig
var left_paddle: Paddle = .init(Paddle.size.x * 0.5, .left, screen_height);
var right_paddle: Paddle = .init(screen_width - Paddle.size.x * 1.5, .right, screen_height);
var ball: Ball = .init(.{ .x = screen_width * 0.5, .y = screen_height * 0.5 });
```
<h2 id="-fixing-paddle-collisions-on-the-y-axis">🎯 Fixing Paddle Collisions on the Y Axis</h2>
<p>Now for something more visible: my collision detection logic only checked the
center point of the ball along the y-axis. That meant if the ball clipped the
paddle at the edge, it was sneaking through.</p>
<p>The fix was simple: add/subtract the ball’s radius in the y-axis check. Much
better.</p>
```zig
const colliding = ball.pos.y + ball.r >= self.pos.y and ball.pos.y - ball.r <= self.pos.y + size.y;
```
<p>You can actually see the difference in-game - that satisfying little <em>thock</em> now
triggers when it should, even on corner hits.</p>
<p>(now I just need add some sounds - I&rsquo;d forgotten about that)</p>
<h2 id="-iscolliding-should-only-collide">🧽 <code>isColliding</code> Should Only Collide</h2>
<p>Previously, <code>isColliding</code> also handled coloring the paddle red when it detected
a hit - a debug leftover that had no place in the final function.</p>
<p>I stripped that out and left <code>isColliding</code> to do just one thing: return whether
there was a collision. If I want debug visuals again later, I’ll wrap this in
another function.</p>
```zig
// Paddle.zig
pub fn isColliding(self: *const Paddle, ball: *const Ball) bool {
    // which edge do we need to check
    const crossing_x: bool = switch (self.which) {
        .right => ball.pos.x + ball.r >= self.pos.x,
        .left => ball.pos.x - ball.r <= self.pos.x + size.x,
    };

    if (!crossing_x) {
        return false;
    }

    const colliding = ball.pos.y + ball.r >= self.pos.y and ball.pos.y - ball.r <= self.pos.y + size.y;

    return colliding;
}
```
<h2 id="-movement-logic-encapsulation">🔀 Movement Logic Encapsulation</h2>
<p>Paddle movement was scattered, and the logic for moving up/down lived inside
<code>main.zig</code>. I pulled that out into proper <code>moveUp</code> and <code>moveDown</code> methods on
<code>Paddle</code>.</p>
<p>It reads cleaner now:</p>
```zig
//Paddle.zig
pub fn moveUp(self: *Paddle, dt: f32) void {
    self.move(-100, dt);
}

pub fn moveDown(self: *Paddle, dt: f32) void {
    self.move(100, dt);
}
```
```zig
// main.zig
if (rl.isKeyDown(.w)) {
    left_paddle.moveUp(dt);
}

if (rl.isKeyDown(.s)) {
    left_paddle.moveDown(dt);
}

if (rl.isKeyDown(.e)) {
    right_paddle.moveUp(dt);
}

if (rl.isKeyDown(.d)) {
    right_paddle.moveDown(dt);
}
```
<p>…and it keeps the input logic in <code>main</code>, but the movement logic in the paddle -
where it belongs.</p>
<h2 id="-resolution-independence">📐 Resolution Independence</h2>
<p>Some values were hardcoded (like setting <code>y = 200</code> for paddle start position),
while others used <code>getScreenHeight()</code> and <code>getScreenWidth()</code>. I refactored to
make everything use actual screen dimensions, converting <code>i32</code> screen height
values to <code>f32</code> where needed.</p>
<p>It was a bit fiddly, but worth it. Now Pong should behave properly regardless of
window size.</p>
<h2 id="closing">Closing</h2>
<p>So, yeah - not the flashiest episode, but a satisfying one. Lots of small things
that feel better now that they’re fixed. And it&rsquo;s a reminder that sharing early
(even rough work) is usually a good idea. You never know what you&rsquo;ll learn.</p>
<p>Next time, we <em>will</em> get into UI. I’m planning to bring in
<a href="https://github.com/david-vanderson/dvui">DVUI</a> and show a basic score display,
a pause menu, and maybe some options for reset and quit.</p>
<p>Until then, thanks for reading (and watching) - see you in the next one.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-4.md">Watch Video</a></li>
<li><a href="https://icle.es/games/pong/">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/3-scoring.md">Ball Movement &amp; Paddle Collisions</a></li>
<li>Next: <a href="https://icle.es/5-ui.md">Integrate DVUI &amp; Show Score</a></li>
</ul>
]]></content:encoded></item><item><title>Building Pong in Zig with Raylib – Part 3: Edge Collisions, Scoring &amp; Player Input</title><link>https://icle.es/2025/07/07/building-pong-in-zig-with-raylib-part-3-edge-collisions-scoring-player-input/</link><pubDate>Mon, 07 Jul 2025 12:01:00 +0100</pubDate><guid>https://icle.es/2025/07/07/building-pong-in-zig-with-raylib-part-3-edge-collisions-scoring-player-input/</guid><description>&lt;p>In this part, we round out the core gameplay of our Pong clone in Zig using
raylib. By the end, we&amp;rsquo;ve got a working game - with a bouncing ball, paddle
controls, and basic scoring.&lt;/p>
&lt;h2 id="-edge-collisions">🧱 Edge Collisions&lt;/h2>
&lt;p>We already had paddle collision working, but we needed the ball to bounce off
the top and bottom edges of the screen. That meant checking if the ball&amp;rsquo;s
y-position was outside the visible range and inverting its vertical velocity
accordingly:&lt;/p></description><content:encoded><![CDATA[<p>In this part, we round out the core gameplay of our Pong clone in Zig using
raylib. By the end, we&rsquo;ve got a working game - with a bouncing ball, paddle
controls, and basic scoring.</p>
<h2 id="-edge-collisions">🧱 Edge Collisions</h2>
<p>We already had paddle collision working, but we needed the ball to bounce off
the top and bottom edges of the screen. That meant checking if the ball&rsquo;s
y-position was outside the visible range and inverting its vertical velocity
accordingly:</p>
```zig
pub fn checkEdgeCollisions(self: *Ball, screen_height: f32) void {
    if (self.pos.y < self.r or self.pos.y > screen_height - self.r) {
        self.vel.y *= -1;
    }
}
```
<p>There was a small detour where I realised <code>screen_height</code> wasn’t giving the
expected value - using <code>GetScreenHeight()</code> directly from raylib helped resolve
that.</p>
<h2 id="-scoring-system">🏁 Scoring System</h2>
<p>Once edge collisions were in place, it was time to detect goals. If the ball
passed the left or right edge of the screen, the opposing player got a point. We
also need a <code>reset()</code> method to the <code>Ball</code> struct to return it to the centre
after each goal.</p>
```zig
pub fn reset(self: *Ball) void {
    self.pos = self.home;
    self.vel = .{ .x = 250, .y = -50 };
}
```
<p>For now, let&rsquo;s print the scores via <code>std.debug.print</code>, but this sets the stage
for UI integration.</p>
```zig
if (ball.pos.x > screenWidth) {
    left_paddle.score += 1;
    std.debug.print("scores: l: {d}, r: {d}", .{ left_paddle.score, right_paddle.score });
    ball.reset();
}

if (ball.pos.x < 0) {
    right_paddle.score += 1;
    std.debug.print("scores: l: {d}, r: {d}", .{ left_paddle.score, right_paddle.score });
    ball.reset();
}
```
<h2 id="-player-input">⌨️ Player Input</h2>
<p>Finally, we wired up keyboard input to allow paddle movement. We used
<code>IsKeyDown</code> from raylib, and made sure movement was frame-rate independent by
scaling it with <code>dt</code>:</p>
```zig
pub fn move(self: *Paddle, y: f32, dt: f32) void {
    self.pos.y += y * dt;
}
```
<p>Left paddle uses <code>W/S</code>, right paddle uses <code>E/D</code>. Simple and responsive.</p>
```zig
if (rl.isKeyDown(.w)) {
    left_paddle.move(-100, dt);
}

if (rl.isKeyDown(.s)) {
    left_paddle.move(100, dt);
}

if (rl.isKeyDown(.e)) {
    right_paddle.move(-100, dt);
}

if (rl.isKeyDown(.d)) {
    right_paddle.move(100, dt);
}
```
<h2 id="-whats-working">✅ What&rsquo;s Working</h2>
<p>By the end of this episode, we’ve got:</p>
<ul>
<li>Ball bounces off top and bottom edges</li>
<li>Scoring when the ball passes a paddle</li>
<li>Ball reset after each goal</li>
<li>Both paddles are fully controllable</li>
</ul>
<p>All that’s missing now is a visible score, a win condition, and maybe a simple
menu.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-3.md">Watch Video</a></li>
<li><a href="https://icle.es/games/pong/">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/2-collisions.md">Ball Movement &amp; Paddle Collisions</a></li>
<li>Next: <a href="https://icle.es/4-refactor.md">Code Improvements from Review</a></li>
</ul>
]]></content:encoded></item><item><title>Building Pong in Zig with Raylib – Part 2: Ball Movement &amp; Paddle Collisions</title><link>https://icle.es/2025/07/04/building-pong-in-zig-with-raylib-part-2-ball-movement-paddle-collisions/</link><pubDate>Fri, 04 Jul 2025 12:01:00 +0100</pubDate><guid>https://icle.es/2025/07/04/building-pong-in-zig-with-raylib-part-2-ball-movement-paddle-collisions/</guid><description>&lt;p>In &lt;a href="https://icle.es/1-setup.md">Part 1&lt;/a> we set up the basics: a window, paddles, and a ball.
In this episode, we go one step further and get the ball moving 😉, add paddle
collisions, and make everything frame-rate independent.&lt;/p>
&lt;h2 id="ball-movement">Ball Movement&lt;/h2>
&lt;p>The first step was to give the ball some velocity and update its position every
frame.&lt;/p>
```zig
self.pos = rl.math.vector2Add(self.pos, self.vel);
```
&lt;p>We also fixed a small oversight: the ball and paddles were being recreated every
frame inside the game loop. Moving their initialization outside meant we could
actually observe state changes between frames.&lt;/p></description><content:encoded><![CDATA[<p>In <a href="https://icle.es/1-setup.md">Part 1</a> we set up the basics: a window, paddles, and a ball.
In this episode, we go one step further and get the ball moving 😉, add paddle
collisions, and make everything frame-rate independent.</p>
<h2 id="ball-movement">Ball Movement</h2>
<p>The first step was to give the ball some velocity and update its position every
frame.</p>
```zig
self.pos = rl.math.vector2Add(self.pos, self.vel);
```
<p>We also fixed a small oversight: the ball and paddles were being recreated every
frame inside the game loop. Moving their initialization outside meant we could
actually observe state changes between frames.</p>
<h2 id="frame-rate-independence">Frame-Rate Independence</h2>
<p>Raylib provides <code>GetFrameTime()</code> which returns the time in seconds since the
last frame. Multiplying the velocity by this <code>dt</code> value ensures that the ball
movement stays consistent across different frame rates:</p>
```zig
const vel_this_frame = rl.math.vector2Scale(self.vel, dt);
self.pos = rl.math.vector2Add(self.pos, vel_this_frame);
```
<p>With that, the ball now moves at a steady speed, no matter the frame rate.</p>
<h2 id="paddle-collision-x-axis">Paddle Collision (X-Axis)</h2>
<p>Next up: detecting collisions between the ball and paddles. I considered writing
a standalone collision checker, but ended up keeping the logic within the
<code>Paddle</code> struct itself.</p>
<p>To simplify which edge to check (left or right), I added a <code>which</code> field to
paddles - an enum with values <code>left</code> and <code>right</code>. That made the conditional
logic much cleaner.</p>
<p>To debug collisions, I added color switching: when a paddle detects a collision
on the x-axis, it flashes red.</p>
```zig
pub fn isColliding(self: *Paddle, ball: *const Ball) bool {
    // which edge do we need to check
    const crossing_x: bool = switch (self.which) {
        .right => ball.pos.x + ball.r >= self.pos.x,
        .left => ball.pos.x - ball.r <= self.pos.x + size.x,
    };

    self.colour = if (crossing_x) .red else .white;
    return crossing_x;
}
```
<h2 id="paddle-collision-y-axis">Paddle Collision (Y-Axis)</h2>
<p>After confirming horizontal collision detection, I added vertical bounds
checking. This just involved verifying the ball&rsquo;s y-position is within the
paddle’s vertical range.</p>
```zig
pub fn isColliding(self: *Paddle, ball: *const Ball) bool {
    // which edge do we need to check
    const crossing_x: bool = switch (self.which) {
        .right => ball.pos.x + ball.r >= self.pos.x,
        .left => ball.pos.x - ball.r <= self.pos.x + size.x,
    };

    if (!crossing_x) {
        self.colour = .white;
        return false;
    }

    const colliding = ball.pos.y >= self.pos.y and ball.pos.y <= self.pos.y + size.y;

    self.colour = if (colliding) .red else .white;
    return colliding;
}
```
<h2 id="bounce-logic">Bounce Logic</h2>
<p>With detection in place, we added bounce logic to the ball. If a collision with
a paddle is detected, we flip the x-component of the velocity vector:</p>
```zig
pub fn checkPaddleCollision(self: *Ball, paddle: *Paddle) void {
    if (paddle.isColliding(self)) {
        self.vel = rl.math.vector2Scale(self.vel, -1);
    }
}
```
<h2 id="whats-next">What’s Next</h2>
<p>That wraps up part 2. In the next episode, we’ll handle edge collisions (top and
bottom), scoring, and input management.</p>
<p>Thanks for following along!</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://youtu.be/IoOLH1O_a7M">Watch Video</a></li>
<li><a href="https://github.com/drone-ah/wordsonsand/tree/shri-codes/pong/part-2/games/pong">Source Code (at this point)</a></li>
<li>Prev: <a href="https://icle.es/1-setup.md">Place Paddles &amp; Ball</a></li>
<li>Next: <a href="https://icle.es/3-scoring.md">Edge Collisions, Scoring &amp; Inputs</a></li>
</ul>
]]></content:encoded></item><item><title>Building Pong in Zig with Raylib – Part 1: Setup, Paddles, and Ball</title><link>https://icle.es/2025/06/28/building-pong-in-zig-with-raylib-part-1-setup-paddles-and-ball/</link><pubDate>Sat, 28 Jun 2025 20:38:29 +0100</pubDate><guid>https://icle.es/2025/06/28/building-pong-in-zig-with-raylib-part-1-setup-paddles-and-ball/</guid><description>&lt;p>Before continuing development on Triangle - my larger, arcade ARPG, factory
game, I wanted to take a step back and build something small and familiar. Pong
felt like the perfect choice: quick to prototype, easy to understand, and a good
warm-up before diving deeper into raylib again.&lt;/p>
&lt;p>This post goes alongside &lt;a href="https://youtu.be/ICq2D_na6zc">my video on YouTube&lt;/a>,
and walks through the early steps of the project: setting up the game, getting
something on screen, and implementing the paddles and the ball.&lt;/p></description><content:encoded><![CDATA[<p>Before continuing development on Triangle - my larger, arcade ARPG, factory
game, I wanted to take a step back and build something small and familiar. Pong
felt like the perfect choice: quick to prototype, easy to understand, and a good
warm-up before diving deeper into raylib again.</p>
<p>This post goes alongside <a href="https://youtu.be/ICq2D_na6zc">my video on YouTube</a>,
and walks through the early steps of the project: setting up the game, getting
something on screen, and implementing the paddles and the ball.</p>
<h2 id="why-pong">Why Pong?</h2>
<p>Sometimes, before getting deeper into a project, it helps to do something simple
just to get your hands moving again. I hadn’t written Zig in a couple of weeks,
and wanted a fast feedback loop to:</p>
<ul>
<li>Get back into using <code>raylib-zig</code></li>
<li>Have a bit of fun</li>
<li>Remind myself why I made certain decisions in the first place</li>
</ul>
<h2 id="project-setup">Project Setup</h2>
<p>I&rsquo;m using <a href="https://github.com/Not-Nik/raylib-zig"><code>raylib-zig</code></a> for this. You
can scaffold a new project with:</p>
```bash
./project_setup.sh <project-name>
```
<p>You could also use <code>zig init</code> and then add the dependency in manually if you
prefer.</p>
<h2 id="drawing-the-playground">Drawing the Playground</h2>
<p>The Pong &ldquo;arena&rdquo; is simple:</p>
<ul>
<li>Two paddles</li>
<li>A ball (currently static)</li>
<li>A center dividing line</li>
<li>Placeholder for scores</li>
</ul>
<h2 id="implementing-paddles">Implementing Paddles</h2>
<p>The paddles have:</p>
<ul>
<li>A position (top-left corner)</li>
<li>A fixed size</li>
<li>A simple render function</li>
</ul>
```zig
const std = @import("std");
const rl = @import("raylib");

const Paddle = @This();

pos: rl.Vector2,

pub fn init(x: f32) Paddle {
    return .{ .pos = .{
        .x = x,
        .y = 200,
    } };
}

pub const size = rl.Vector2{ .x = 25, .y = 100 };

pub fn render(self: Paddle) void {
    rl.drawRectangleV(self.pos, size, .white);
}
```
<p>It might have been nice to be able to have the paddle calculate more of its own
values, but the more I think about it, the more it makes sense to keep the logic
in paddle simpler.</p>
<h2 id="ball-placeholder">Ball Placeholder</h2>
<p>To wrap things up for this session, I added a static ball in the center of the
screen. It has a radius, position, and velocity fields ready to go. Rendering is
straightforward:</p>
```zig
const std = @import("std");
const rl = @import("raylib");

const Ball = @This();

pos: rl.Vector2,
r: f32 = 16,
vel: rl.Vector2 = .{ .x = 0, .y = 0 },

pub fn render(self: Ball) void {
    rl.drawCircleV(self.pos, self.r, .white);
}
```
<h2 id="whats-next">What&rsquo;s Next</h2>
<p>This was mostly about warming up, but the next episode will tackle:</p>
<ul>
<li>Adding velocity to the ball</li>
<li>Basic collision detection with paddles</li>
</ul>
<p>I&rsquo;ll try and think about simplifying paddle logic, especially the awkward
symmetry between left and right.</p>
<p>You can find the full source code
<a href="https://github.com/drone-ah/wordsonsand/tree/main/games/pong">on GitHub</a>.</p>
<p>See you in part 2!</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/youtube/shri-codes/pong/pong-1.md">YouTube Video</a></li>
<li><a href="https://github.com/drone-ah/wordsonsand/tree/shri-codes/pong/part-1/games/pong">Full Source Code (at this point)</a></li>
<li>Next: <a href="https://icle.es/2-collisions.md">Ball Movement &amp; Paddle Collisions</a></li>
</ul>
]]></content:encoded></item><item><title>Under the Hood of triangle</title><link>https://icle.es/2025/06/27/under-the-hood-of-triangle/</link><pubDate>Fri, 27 Jun 2025 08:01:28 +0100</pubDate><guid>https://icle.es/2025/06/27/under-the-hood-of-triangle/</guid><description>&lt;p>In &lt;a href="https://youtu.be/8nA-a5Z1IDc">this Let&amp;rsquo;s Code video&lt;/a> on
&lt;a href="http://www.youtube.com/@ShriCodesHere">&lt;strong>shri codes&lt;/strong>&lt;/a>, I walk through some of
the early systems in place for &lt;a href="https://icle.es/tags/triangle/">&lt;em>Triangle&lt;/em>&lt;/a> - my space
ARPG built in &lt;strong>Zig&lt;/strong> with &lt;strong>raylib&lt;/strong>.&lt;/p>
&lt;p>For the broader story behind the project - what &lt;em>Triangle&lt;/em> is, where it&amp;rsquo;s going,
and why I’m making it - check out the
&lt;a href="https://icle.es/endeavours/triangle.md">triangle endeavour&lt;/a> or the
&lt;a href="https://icle.es/tags/triangle/">devlog archive&lt;/a>. This post is about the &lt;em>how&lt;/em>.&lt;/p>
&lt;h3 id="-core-systems-so-far">🧩 Core Systems So Far&lt;/h3>
&lt;p>At this stage, Triangle supports:&lt;/p>
&lt;ul>
&lt;li>Basic player movement and shooting (Asteroids-style).&lt;/li>
&lt;li>Procedurally generated asteroid fields per &lt;em>sector&lt;/em>.&lt;/li>
&lt;li>Material drops (iron ore) from destroyed asteroids.&lt;/li>
&lt;li>Automatic recipe unlocking when new items are picked up.&lt;/li>
&lt;li>Smelting system that converts ore to ingots.&lt;/li>
&lt;li>Unlocking and queuing up &lt;em>constructors&lt;/em> using the crafting menu.&lt;/li>
&lt;li>A basic UI for crafting and inventory.&lt;/li>
&lt;li>Configurable keyboard mappings using TOML.&lt;/li>
&lt;/ul>
&lt;p>That’s the visible layer. Below the surface, the codebase has carved out some
space for more ambitious systems.&lt;/p></description><content:encoded><![CDATA[<p>In <a href="https://youtu.be/8nA-a5Z1IDc">this Let&rsquo;s Code video</a> on
<a href="http://www.youtube.com/@ShriCodesHere"><strong>shri codes</strong></a>, I walk through some of
the early systems in place for <a href="https://icle.es/tags/triangle/"><em>Triangle</em></a> - my space
ARPG built in <strong>Zig</strong> with <strong>raylib</strong>.</p>
<p>For the broader story behind the project - what <em>Triangle</em> is, where it&rsquo;s going,
and why I’m making it - check out the
<a href="https://icle.es/endeavours/triangle.md">triangle endeavour</a> or the
<a href="https://icle.es/tags/triangle/">devlog archive</a>. This post is about the <em>how</em>.</p>
<h3 id="-core-systems-so-far">🧩 Core Systems So Far</h3>
<p>At this stage, Triangle supports:</p>
<ul>
<li>Basic player movement and shooting (Asteroids-style).</li>
<li>Procedurally generated asteroid fields per <em>sector</em>.</li>
<li>Material drops (iron ore) from destroyed asteroids.</li>
<li>Automatic recipe unlocking when new items are picked up.</li>
<li>Smelting system that converts ore to ingots.</li>
<li>Unlocking and queuing up <em>constructors</em> using the crafting menu.</li>
<li>A basic UI for crafting and inventory.</li>
<li>Configurable keyboard mappings using TOML.</li>
</ul>
<p>That’s the visible layer. Below the surface, the codebase has carved out some
space for more ambitious systems.</p>
<h3 id="-code-structure-overview">🗂️ Code Structure Overview</h3>
<p>Here’s a quick tour of the key directories and their purpose:</p>
<h4 id="blit"><code>blit/</code></h4>
<p>Handles all the 2D rendering abstractions - bounding boxes, shapes (including
triangle fans 😉 and circles), and canvas drawing. Wraps <code>raylib</code> for better
namespacing and type control.</p>
<h4 id="phys"><code>phys/</code></h4>
<p>Physics system. <code>body.zig</code> implements basic movement, collisions, and inertia.
Still minimal, but usable.</p>
<h4 id="combat"><code>combat/</code></h4>
<p>Still early - only <code>bullet</code> is implemented, and is used to destroy asteroids.
Placeholder for upcoming systems like health, damage, and targeting.</p>
<h4 id="items"><code>items/</code></h4>
<p>This module forms the backbone of the crafting system. Items are tied to
recipes, and recipes are triggered via pickups or crafting actions. There&rsquo;s a
lot more to unpack here - likely its own devlog soon.</p>
<h4 id="notifier"><code>notifier/</code></h4>
<p>Manages in-game UI feedback. When you pick something up or unlock a new recipe,
this is what shows it. Notifiers are scoped - one for inventory, one for
crafting - and show up on either side of the screen.</p>
<h4 id="ship"><code>ship/</code></h4>
<p>This module contains the ship related logic, including movement, and the logic
for constructing smelters and other buildables. Eventually this will handle
assembly chains.</p>
<h4 id="asteroid"><code>asteroid</code></h4>
<p>These files should be refactored into its own module, and should contain the
asteroid itself, as well as sector, a chunk of space - asteroids, entities,
bullets, etc. This is the closest thing to a “level” in Triangle. It’s
procedurally generated, and you can eventually travel from one sector to the
next.</p>
<h4 id="diag"><code>diag/</code></h4>
<p>Code to support diagnostics &amp; logging</p>
<h4 id="game"><code>game/</code></h4>
<p>This module should get a bit of refactoring as well, moving some of the files
from the root into its own module</p>
<ul>
<li><code>config</code>: Takes care of user control bindings and input overrides via a TOML
config file.</li>
<li><code>notifier</code>: supports collecting notifications across multiple channels.</li>
<li><code>save</code>: Just scaffolding for now - no save/load logic yet, but it’s a
placeholder to group serializable components together.</li>
<li><code>context</code>: container struct to support dependency injection.</li>
</ul>
<h4 id="ui"><code>ui/</code></h4>
<p>Holds in-game panels like crafting and inventory. Needs cleanup and
consolidation.</p>
<h3 id="-whats-missing">🧪 What’s Missing</h3>
<p>A lot of the systems are stubbed out, but not yet wired in. For instance:</p>
<ul>
<li><code>power</code> is empty (future energy system)</li>
<li>The <em>canvas</em> currently wraps <code>raylib</code> without adding much - that will likely
change as UI complexity grows.</li>
</ul>
<h3 id="-threads-ill-pull-next">🧶 Threads I’ll Pull Next</h3>
<p>These are the next things I’ll either build or record:</p>
<ul>
<li>Improving the coding structure. I&rsquo;d like to improve organisation and am
considering extracting a re-usable library out of it, mainly so I can tinker
with other smaller game projects.</li>
<li>A <strong>rudimentary menu system</strong></li>
<li>A <strong>build+deploy system</strong> to generate early test builds</li>
<li>Possibly a <strong>contact/feedback</strong> form directly in-game or on the site</li>
</ul>
<p>I’ll be releasing this in what I’m calling a <a href="https://icle.es/sprout.md"><strong>&ldquo;Sprout&rdquo; build</strong></a> -
a playable seed, a form of extremely early access. Feedback and collaboration is
really important to me and I want to get this out there as soon as there is the
tiniest spark of &ldquo;life.&rdquo;</p>
<p>If you’d like to follow the journey, you can
<a href="https://youtu.be/8nA-a5Z1IDc">watch the video devlog</a></p>
]]></content:encoded></item><item><title>Managing Configuration</title><link>https://icle.es/2025/06/08/managing-configuration/</link><pubDate>Sun, 08 Jun 2025 21:47:05 +0000</pubDate><guid>https://icle.es/2025/06/08/managing-configuration/</guid><description>&lt;p>Before making the game available for playtesting, I wanted the player to be able
to configure the game to some degree.&lt;/p>
&lt;p>As a starting point, my keyboard layout is &lt;code>colemak&lt;/code>, and I doubt that the
controls I use would suit the majority of players.&lt;/p>
&lt;p>I am putting off a UI based config management option down the road (did I
mention that I do not enjoy GUI work?). As such I&amp;rsquo;ve been pondering alternative
configuration options.&lt;/p></description><content:encoded><![CDATA[<p>Before making the game available for playtesting, I wanted the player to be able
to configure the game to some degree.</p>
<p>As a starting point, my keyboard layout is <code>colemak</code>, and I doubt that the
controls I use would suit the majority of players.</p>
<p>I am putting off a UI based config management option down the road (did I
mention that I do not enjoy GUI work?). As such I&rsquo;ve been pondering alternative
configuration options.</p>
<h2 id="platform-independence">Platform independence</h2>
<p>Before I even get to that, the first problem I need to solve is a way to
determine the location for the config files independent of the platform.</p>
<p>Fortunately, <a href="https://github.com/ziglibs/known-folders">known-folders</a> came to
the rescue and provided an easy to use framework that can be used to determine
the various relevant locations for multiple platforms.</p>
```zig
const known_folders = @import("known-folders");
const maybe_config = try known_folders.getPath(allocator, .roaming_configuration);
if (maybe_config) |config| {
    defer allocator.free(config);
    std.debug.print("roaming config path: {s}\n", .{config});
}
```
<h2 id="locations">Locations</h2>
<p>There are three real locations of relevance for triangle</p>
<ul>
<li>The binary / package</li>
<li>Config, technically, split into two
<ul>
<li>user (or in Windows parlance, the remote dir, and can be shared across
computers)</li>
<li>system (in windows parlance, local, and is specific to that system)</li>
</ul>
</li>
<li>Save Data</li>
</ul>
<h2 id="user--game-config-files">User &amp; Game Config Files</h2>
<p>With that sorted out, the next bit is to identify the relevant config files. I
expect that triangle will continue to use these, and will eventually just get a
UI config option as well.</p>
<h3 id="user-config">User Config</h3>
<p>There are two main bits of user configuration</p>
<ul>
<li>Preference like controls</li>
<li>System details like resolution</li>
</ul>
<p>I am currently unsure when it&rsquo;ll support system config.</p>
<h3 id="game-config">Game Config</h3>
<p>There are two bits of config that the game will store. One set of config is to
remember game choices the user has made.</p>
<h4 id="remember-player-actions">Remember Player Actions</h4>
<p>For example, it will be useful to show the user details of changes to the game
since they last played. To do this, we need to track the last set of changes
that the user saw.</p>
<p>The game will show a notice on startup about its extremely early access status,
and provide an option for the user to hide that in the future. We need to save
that somewhere too.</p>
<h4 id="telemetry">Telemetry</h4>
<p>The second bit of config is metrics. While a lot of games will simply send
telemetry information directly to the developer, player privacy is really
important to me. I recognise that I will get far less data because of this, and
that there will be a bit of survivorship bias with the data - but I feel that
privacy is more important.</p>
<p>The way I want telemetry to work is that it will all be saved in a human
readable telemetry file in the config file location.</p>
<p>The data is stored only locally, and is never automatically sent. The player is
welcome to use this data for themselves if they wish and also share at their
discretion. The information will be stored in a human readable format that
should be as easy to understand as possible - no data dumps.</p>
<p>The location will also store logs (if enabled).</p>
<h2 id="setting-config">Setting Config</h2>
<p>In terms of allowing the player to manage config, there are a couple of
challenges:</p>
<ul>
<li>Providing enough documentation that it is easy to do</li>
<li>Allowing for updates, particularly to the addition of new keys</li>
</ul>
<p>To tackle this, I am going to provide an annotated template file with all the
config options. The user can create a separate file based on this with <strong>only</strong>
the config they wish to override.</p>
<p>It will be tricky to change how particular parameters are configured. E.g. If a
single value key needs to switch to an array. I&rsquo;d be loathe to sprinkle the code
with checks for legacy keys/formats. We&rsquo;ll play it by ear.</p>
<p>I considered updating the config file automatically, but this would discard any
comments the user had added. While I could offer an option to merge changes in,
it’s not straightforward enough to implement just yet.</p>
<h2 id="format">Format</h2>
<p>I&rsquo;ve been considering <code>toml</code> and <code>yaml</code> for this, with
<a href="https://github.com/sam701/zig-toml/">zig-toml</a> and
<a href="https://github.com/kubkon/zig-yaml">zig-yaml</a> respectively.</p>
<p><code>zig-yaml</code> seems to be more active (more stars, forks, issues and pr&rsquo;s and
currently also the more recent commit).</p>
<p>I am also more familiar with and prefer yaml.</p>
<p>However, it does not currently support default values. I would like the user to
have to specify only the config they&rsquo;d like to override. <code>zig-yaml</code> currently
expects all the keys to be defined if you want to parse it into a struct.</p>
<p><a href="https://github.com/kubkon/zig-yaml/issues/85">#85</a> should bring it in, but I
<a href="https://github.com/kubkon/zig-yaml/issues/92">could not get it to work</a></p>
<p>So, I tried out <code>zig-toml</code> and the test worked the first time.</p>
<p><a href="https://github.com/drone-ah/wordsonsand/tree/main/code/zig/src/toml_with_defaults.zig">src/toml_with_defaults.zig</a></p>
```zig
const std = @import("std");

const Controls = struct {
    forward: []const u8 = "w",
    craft: []const u8 = "q",
    inventory: []const u8 = "e",
};

const User = struct {
    controls: Controls = .{},
};

test "load partial toml config" {
    const toml = @import("toml");
    const allocator = std.testing.allocator;
    var parser = toml.Parser(User).init(allocator);
    defer parser.deinit();

    const source =
        \\[controls]
        \\craft = "s"
    ;
    var result = try parser.parseString(source);
    defer result.deinit();

    const config = result.value;
    const default = User{};
    try std.testing.expectEqualStrings(default.controls.forward, config.controls.forward);
    try std.testing.expectEqualStrings("s", config.controls.craft);
}
```
<h1 id="using-the-config">Using the config</h1>
<p>The final part is to <em>use</em> the config.</p>
<h2 id="loading-config">Loading config</h2>
<p>All config is loaded at startup and attached to a <code>Config</code> struct, which is in
turn part of a <code>Context</code> struct that is passed around.</p>
<p><a href="https://github.com/drone-ah/wordsonsand/tree/main/code/zig/src/load_save_config.zig">src/load_save_config.zig</a></p>
```zig
user: User,

game_path: []const u8,
game: Game,

pub fn init(allocator: std.mem.Allocator) ConfigError!Self {
    const maybe_config = known_folders.getPath(allocator, .roaming_configuration) catch {
        return ConfigError.UnableToDetermineConfigLocation;
    };
    if (maybe_config) |config| {
        defer allocator.free(config);

        // user config path
        const full_path = std.fmt.allocPrint(allocator, "{s}/triangle/user.toml", .{config}) catch {
            std.debug.panic("oom", .{});
        };
        defer allocator.free(full_path);

        // game config path
        const game_path = std.fmt.allocPrint(allocator, "{s}/triangle/game.toml", .{config}) catch {
            std.debug.panic("oom", .{});
        };

        return .{
            .user = loadConfig(allocator, User, full_path),

            .game_path = game_path,
            .game = loadConfig(allocator, Game, game_path),
        };
    }

    return ConfigError.UnableToDetermineConfigLocation;
}

fn loadConfig(allocator: std.mem.Allocator, ConfigType: type, path: []const u8) ConfigType {
    var parser = toml.Parser(ConfigType).init(allocator);
    defer parser.deinit();

    var result = parser.parseFile(path) catch {
        log.warn("unable to read config file: {s}", .{path});
        return .{};
    };
    defer result.deinit();

    return result.value;
}
```
<h2 id="user-config-controls">User config (controls)</h2>
<p>This one involves a little translation as we need to know the <code>rl.KeyboardKey</code>
for each mapping to be able to detect it.</p>
<p>I use a <code>std.StringArrayHashMapUnmanaged(rl.KeyboardKey)</code> to map the string to
each key</p>
<p><a href="https://github.com/drone-ah/wordsonsand/tree/main/code/zig/src/load_save_config.zig">src/load_save_config.zig</a></p>
```zig
const Input = struct {
    keymap: KeyMaps,

    pub fn init(allocator: std.mem.Allocator) Self {
        var keymap = KeyMaps{};
        for (default_keybindings) |entry| {
            keymap.put(allocator, entry.name, entry.key) catch {
                std.debug.panic("oom", .{});
            };
        }

        return .{
            .keymap = keymap,
        };
    }

    const KeyMap = struct {
        name: []const u8,
        key: rl.KeyboardKey,
    };

    const default_keybindings = [_]KeyMap{
        .{ .name = "a", .key = .a },
        .{ .name = "b", .key = .b },
        .{ .name = "c", .key = .c },
    };
}
```
<h2 id="game-config-1">Game Config</h2>
<p>As a starting point, we&rsquo;ll probably only have one config entry here - the last
time the news was marked as read by the player.</p>
<p>We&rsquo;ll store this as an <code>i64</code> and loading it is exactly the same as above.</p>
<p>The main difference with the <code>Game</code> config class is that on writing any value,
it will also save it to disk.</p>
```zig
pub fn markNewsAsRead(self: *Self) void {
    self.game.news_read = std.time.timestamp();
    saveConfig(self.allocator, self.game, self.game_path);
}

fn saveConfig(allocator: std.mem.Allocator, Config: anytype, full_path: []const u8) void {
    const path = std.fs.path.dirname(full_path) orelse ".";
    std.fs.cwd().makePath(path) catch |err| { // creates parent dirs if needed
        log.warn("unable to save: {any}", .{err});
        return;
    };

    var file = std.fs.cwd().createFile(full_path, .{
        .read = false,
        .truncate = true,
    }) catch |err| {
        log.warn("unable to save: {any}", .{err});
        return;
    };
    defer file.close();

    var writer = file.writer().any();
    toml.serialize(allocator, Config, &writer) catch |err| {
        log.warn("unable to write to config file: {any}", .{err});
    };
}
```
<p><del>I ran into a bug where
<a href="https://github.com/sam701/zig-toml/issues/32">the api for serialization was broken</a>.
There is (currently)
<a href="https://github.com/sam701/zig-toml/pull/33">a pending pr #33 to resolve it</a></del></p>
<p>One of the challenges of using emerging language and ecosystem is that you&rsquo;re
more likely to run into bugs. One of the great joys of working with such
ecosystem is the greater opportunity to contribute and get involved!</p>
<h1 id="links">Links</h1>
<ul>
<li><a href="https://youtu.be/OVswrFoFNjM">YouTube Devlog</a></li>
<li>Prev: <a href="https://icle.es/2025-05-20-crafting-machines.md">Crafting Machines</a></li>
</ul>
]]></content:encoded></item><item><title>Basic Gameplay</title><link>https://icle.es/2025/05/08/basic-gameplay/</link><pubDate>Thu, 08 May 2025 10:07:08 +0000</pubDate><guid>https://icle.es/2025/05/08/basic-gameplay/</guid><description>&lt;p>The first goal was to get it working as far as the coding challenge itself.&lt;/p>
&lt;h2 id="the-ship">The Ship&lt;/h2>
&lt;p>Spawning the ship itself was relatively straightforward. I only needed three
points and &lt;code>drawTriangle&lt;/code> from &lt;code>raylib&lt;/code>.&lt;/p>
&lt;p>In the coding challenge, the ship was rotated with the keyboard, but I wanted
the ship to point to the mouse so that aiming was straightforward. Rotating to
the mouse was trickier — it involved &lt;code>atan2&lt;/code> and ChatGPT got me started.&lt;/p>
&lt;p>The coding challenge video did not worry about time lapsed between each frame,
but I wanted triangle to be framerate independent. That involved a bit of
jiggery pokery to get working, including determining how much the ship can move
within a time frame.&lt;/p>
&lt;p>Moving the ship was a bit easier, but based on the many videos of the Coding
Train that implemented force, velocity and dampening, it was pretty
straightforward. Dampening took a bit of trial and error. ChatGPT sent me down
the garden path initially with an over-complicated formula, but I was able to
simplify it later.&lt;/p>
&lt;p>Integrating the physics for linear momentum with the one for rotational momentum
was also quite nice — it meant that the ship could overshoot the aim when
rotating and will correct back.&lt;/p></description><content:encoded><![CDATA[<p>The first goal was to get it working as far as the coding challenge itself.</p>
<h2 id="the-ship">The Ship</h2>
<p>Spawning the ship itself was relatively straightforward. I only needed three
points and <code>drawTriangle</code> from <code>raylib</code>.</p>
<p>In the coding challenge, the ship was rotated with the keyboard, but I wanted
the ship to point to the mouse so that aiming was straightforward. Rotating to
the mouse was trickier — it involved <code>atan2</code> and ChatGPT got me started.</p>
<p>The coding challenge video did not worry about time lapsed between each frame,
but I wanted triangle to be framerate independent. That involved a bit of
jiggery pokery to get working, including determining how much the ship can move
within a time frame.</p>
<p>Moving the ship was a bit easier, but based on the many videos of the Coding
Train that implemented force, velocity and dampening, it was pretty
straightforward. Dampening took a bit of trial and error. ChatGPT sent me down
the garden path initially with an over-complicated formula, but I was able to
simplify it later.</p>
<p>Integrating the physics for linear momentum with the one for rotational momentum
was also quite nice — it meant that the ship could overshoot the aim when
rotating and will correct back.</p>
<h2 id="the-asteroids">The Asteroids</h2>
<p>The coding challenge worked with a fixed number of on screen asteroids and they
wrapped around. I needed to expand this to:</p>
<ul>
<li>A potentially infinite vertical scroller.</li>
<li>Collisions between the asteroids (currently the same as from the challenge,
only checks full circle collision)</li>
<li>Figure out how to handle asteroids moving out of the screen</li>
</ul>
<p>The above will be covered in a bit more depth in the
<a href="https://icle.es/2025-05-10-asteroid-field.md">next devlog</a>.</p>
<h2 id="the-camera">The Camera</h2>
<p>I also needed a follow camera. Unlike the original asteroids, the ship can move
up/down and the camera should follow it.</p>
<p>The current code looks something like this:</p>
```zig
/// ship_y: ship's current y position
/// ship_vy: ship's y velocity
/// dt: time elapsed since last update
/// margin: the zone out of which camera is moved
/// speed: maximum camera movement speed
fn updateCameraY(self: *Self, ship_y: f32, ship_vy: f32, dt: f32, margin: f32, speed: f32) void {
    const cam_y = self.camera.target.y;
    const inner_margin = margin * 0.8;

    // Predict future ship position
    // TODO: at high speeds, when the clamp goes off, the camera does a little shuffle
    // It would be nice to fix that
    const screen_height: f32 = @floatFromInt(self.screen.height);
    const predicted_y = ship_y + std.math.clamp(ship_vy * 30.0, -screen_height, screen_height);

    var target_y = self.camera.target.y;
    var target_clr: rl.Color = .red;

    const tmargin = cam_y - inner_margin;
    const bmargin = cam_y + inner_margin;
    if (predicted_y < tmargin) {
        target_y = predicted_y + margin;
        target_clr = .blue;
    } else if (predicted_y > bmargin) {
        target_y = predicted_y - margin;
        target_clr = .green;
    }

    if (target_y > screen_height / 2) target_y = screen_height / 2;
    const t = std.math.clamp(dt * speed, 0.0, 1.0);
    self.camera.target.y = lerp(cam_y, target_y, t);
}
```
<p>It predicts the position of the ship, based on its y velocity. If that&rsquo;s outside
the middle zone, it&rsquo;ll move the camera at up to the maximum speed. There is some
clamping to prevent things flying off at high speeds. It also prevents the
camera from overtaking the ship.</p>
<p>This code is mostly hacked together with some help from ChatGPT. It&rsquo;ll be
cleaned up later, once I have a better indication of possible ship speeds, and
how/if we&rsquo;ll zoom in and out as well.</p>
<p>Another important facet to consider with the camera was what to do with the
edges. Out of the four edges, only one is infinite.</p>
<p>The current solution is that the camera stops tracking when it gets to the
&ldquo;bottom.&rdquo; It also does not move to the left or right. The ship, on the other
hand is free to move out of the range of the camera. Currently, nothing changes
except that the camera does not follow.</p>
<p>Since the thrust works in the direction of the mouse, it&rsquo;s pretty easy to bring
the ship back on to the screen. This feels like the world is big and still out
there, we just don&rsquo;t track what happens out there.</p>
<p>I considered wrapping around on the left and right, but that felt more like the
ship was trapped in that zone. I want the feeling of
<a href="https://icle.es/2025-04-26-a-lonely-triangle.md#story">being trapped in vengeance</a> to be more
subtle ;)</p>
<h2 id="combat">Combat</h2>
<p>Combat is pretty much a mirror image of what happens in the coding challenge,
though I didn&rsquo;t have some of the helper functions. I learned some math :)</p>
<p>Initially, the update loop only handled asteroid collisions. I added bullets as
a separate field in the update loop. It then checks each bullet with every
asteroid in the active chunks (covered in
<a href="https://icle.es/2025-05-10-asteroid-field.md">devlog #2</a>).</p>
<p>If there is collision, the asteroid is split into two, moved apart a bit, and
given opposite linear momentum. The bullet also takes &ldquo;damage&rdquo; at this point,
and can be removed. The damage system is designed to support penetration, which
is currently not active.</p>
<p>If the spawned asteroids would be too small, nothing is spawned. Later on, this
would be the trigger to spawn a material drop.</p>
<h2 id="zig--raylib">Zig / Raylib</h2>
<p>Learning <a href="https://ziglang.org">zig</a> and <a href="https://www.raylib.com/">raylib</a> was a
big part of this. Fortunately, both were a lot of fun to learn and work with.</p>
<p>One thing I found annoying was that the vector functionality in raylib was
scattered around as individual functions instead of on the vector struct. While
this was understandable, with raylib being written in C, I found it a bit
frustrating.</p>
<p>I ended up writing my own Vector struct in zig and the functions that I used as
methods on that struct. It was an opportunity for me to learn some vector math
as well.</p>
<p>I also encapsulated raylib inside a <code>Canvas</code> struct. I probably still have some
<code>rl.</code> calls other places in the code, but the idea is that a canvas is passed
into any bits of code that needs it. The main help is that it&rsquo;ll convert our
version of <code>Vector2</code> to the one that raylib wants.</p>
<p>I am also thinking about how I want input handling to work. I would like to
encapsulate that into a separate struct. Right now, I mainly access raylib
directly.</p>
<h2 id="manual-testing">Manual testing</h2>
<p>While I am a big proponent and fan of Test Driven Development, I was happy
enough with manual testing for triangle. I found joy in seeing it work each time
I manage to get something working.</p>
<p>I do end up writing tests later, particularly when I got to bits of code that
was harder to test manually.</p>
<h2 id="feel">Feel</h2>
<p>triangle already feels fun and light. There are some clunky elements to be
ironed out, but so far, it feels good :)</p>
<h2 id="other-posts">Other posts</h2>
<ul>
<li><a href="https://youtu.be/F2ITT2-uKso">Companion vlog for this post</a></li>
<li><a href="https://icle.es/2025-04-26-a-lonely-triangle.md">Prev: A lone triangle vs the universe</a></li>
<li><a href="https://icle.es/2025-05-10-asteroid-field.md">Next: Procedural Asteroid Field</a></li>
</ul>]]></content:encoded></item><item><title>init &amp; deinit in Zig</title><link>https://icle.es/2025/04/27/init-deinit-in-zig/</link><pubDate>Sun, 27 Apr 2025 17:01:57 +0100</pubDate><guid>https://icle.es/2025/04/27/init-deinit-in-zig/</guid><description>&lt;p>When I was ten, my grandmother passed away. As was custom where we lived, my
family moved into her house - a full household with uncles and aunts. I stayed
there for about nine months.&lt;/p>
&lt;p>My youngest uncle was a Computing Studies teacher at a local college. He also
taught private classes at home. I couldn’t get enough.&lt;/p>
&lt;p>I don’t know what most ten-year-olds dream about, but my dream was to learn C. I
saved ₹300 - a lot of money in 1994 - to buy a book called &lt;em>Encyclopedia C&lt;/em>. I
read it cover to cover, understood maybe 10% of it, and reread it years later to
pick up more.&lt;/p>
&lt;p>But I never actually programmed in C.&lt;/p></description><content:encoded><![CDATA[<p>When I was ten, my grandmother passed away. As was custom where we lived, my
family moved into her house - a full household with uncles and aunts. I stayed
there for about nine months.</p>
<p>My youngest uncle was a Computing Studies teacher at a local college. He also
taught private classes at home. I couldn’t get enough.</p>
<p>I don’t know what most ten-year-olds dream about, but my dream was to learn C. I
saved ₹300 - a lot of money in 1994 - to buy a book called <em>Encyclopedia C</em>. I
read it cover to cover, understood maybe 10% of it, and reread it years later to
pick up more.</p>
<p>But I never actually programmed in C.</p>
<h2 id="the-long-detour">The Long Detour</h2>
<p>Life took me through a range of other languages instead: Prolog (strangely, what
my school machines had), Visual Basic, ASP, PHP, Java, Python, JavaScript, Go,
Rust. I tinkered with C++ when messing with game engines, but never quite got
around to C.</p>
<p>Some of the early fears carried through - memory management was intimidating:
<code>malloc</code>, <code>free</code>, and in C++, <code>new</code> and <code>delete</code>.</p>
<p>Over time, I began to overcome the fear of systems languages - first through
Java, then Go. But I never revisited C, and I never quite got over the fear of
manual memory management.</p>
<p>In hindsight, these concepts weren’t really designed for a ten-year-old to
grasp. It makes sense that they felt out of reach.</p>
<h2 id="allocator-confusion">Allocator Confusion</h2>
<p>Zig had me curious for a while, and some recent health issues gave me the space
to explore it. I’ve been building a small game in Zig, and it’s been feeding
that original desire to learn C - just with a bit more clarity.</p>
<p>I was still intimidated by memory allocation though, and went as far as I could
without using an allocator.</p>
<p>Eventually, I had to use one. I understood the convention of <code>init</code> and
<code>deinit</code>, and the idea of allocators in general.</p>
<p>But I was confused about how <code>deinit</code> worked in the context of <code>ArenaAllocator</code>.</p>
<blockquote>
<p>A little learning is a dangerous thing</p>
<p>&ndash; Alexander Pope</p></blockquote>
<p>If the arena frees memory for you, wouldn’t calling <code>deinit</code> on individual
objects inside it risk double-freeing?</p>
<p>If I skip calling <code>deinit</code> on a struct that normally needs it, will I miss other
cleanup tasks?</p>
<p>And does this mean the arena allocator isn’t a true drop-in replacement if I
have to skip <code>deinit</code>?</p>
<p>At the time, the answer wasn’t clear - and I couldn’t find documentation that
resolved it.</p>
<h2 id="clarity">Clarity</h2>
<p>Thankfully, the
<a href="https://ziggit.dev/t/deinit-and-arena-allocator/9856">super friendly folk at Ziggit</a>
helped clarify things.</p>
<h3 id="arenaallocator-handles-it"><code>ArenaAllocator</code> handles it</h3>
<p><code>ArenaAllocator</code> <em>is</em> a drop-in replacement for other allocators. If you
<code>deinit</code> objects using memory from the arena, and those objects try to free that
memory, the operation is effectively (though not exactly) a no-op. There’s no
risk of double-free.</p>
<blockquote>
<p>It’s always safe to free / destroy memory from an arena, as long as you treat
it as freed of course. But it won’t actually be released until the arena is
deinit’ed.</p>
<p>&ndash; <a href="https://ziggit.dev/u/mnemnion/summary">mnemnion</a></p></blockquote>
<p>Even if some memory is freed manually before the arena is cleared, it’s handled
safely.</p>
<p>I was overthinking it.</p>
<h3 id="deinit-should-still-be-called"><code>deinit</code> should still be called</h3>
<p><code>deinit</code> exists for cleanup logic - not just memory. It should always be called
where appropriate, regardless of the allocator. That part doesn’t change.</p>
<h3 id="further-learning">Further learning</h3>
<p>Zig also encourages a light touch. One of the impacts of this is that the object
itself should not hold on to the allocator in the <code>init</code> and use it in <code>deinit</code>.
It could, but the better way would be to accept the <code>allocator</code> in both the
<code>init</code> and the <code>deinit</code>.</p>
<p>This convention is further reinforced by
<a href="https://ziglang.org/download/0.14.0/release-notes.html#Embracing-Unmanaged-Style-Containers">zig deprecating the <code>managed</code> variants of collections</a></p>
<h2 id="thanks">Thanks</h2>
<p>In many ways, this was about cleaning up more than just memory.</p>
<p>Special thanks to the helpful folks at <a href="https://ziggit.dev/">ziggit.dev</a>.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://ziggit.dev/t/deinit-and-arena-allocator/9856">Full discussion on ziggit.dev</a></li>
<li><a href="https://github.com/ziglang/zig/blob/d92649da80a526f2e2b2f220c05b81becf4fa627/lib/std/heap/arena_allocator.zig#L253-L267">Implementation of <code>ArenaAllocator</code></a></li>
</ul>]]></content:encoded></item></channel></rss>