Back to the blog

Mach (Zig) Adventures - Part 2

Click here for Part 1

Getting Started

To pick up where I left off, I will start a new repository by following the mach example.

I decided that I would just use the build.zig generated for me by the zig init-exe command.

I run into my first issue:

warning(link): failed to write cache manifest when linking: Unexpected
.\src\main.zig:2:14: error: unable to find 'mach'
const mach = @import("mach");

I checked my build.zig file and updated it with the one in the example.

Then I got:

.\libs\mach\build.zig:163:16: error: container 'std' has no member called 'Build'
        b: *std.Build,
.\libs\mach\build.zig:150:12: error: container 'std' has no member called 'Build'
    b: *std.Build,

Ok so the example may be out of date. I remembered that there were updates to the package manager done recently (watching Discord) so I made an assumption that the recent updates may have caused the example page to become outdated.

This is where I shine. I could ask for help but I learn more if I try to figure it out myself.

First of all, can I even find the place where it's erroring out? Yep.

libs/mach/build.zig, line 150, definitely has a reference to std.Build (with a capital B) but I noticed that in my build.zig (in my main repo), anything under that namespace has a lowercase b (example: So there is something going on here and a part of me wonders if I have an outdated Zig version since std is probably part of core Zig.

I went to the "supported versions page" and, sure enough, 0.11 is listed. However, my version is 0.9. I got the recommended version and had better luck.

Am I out of the woods?

I get this error:

    build.zig:41:19: error: no field or member function named 'standardReleaseOptions' in 'Build'
    const mode = b.standardReleaseOptions();

Ok so maybe I need to re-initialize my build file with the now recommended version to see if it auto-generates a better build.zig file. I deleted my build.zig file as well as the example files in the /src directory, ran init-exe again and then tried to build.


    lib\std\start.zig:602:45: error: root struct of file 'main' has no member named 'main'
    switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {

Ok so this is starting to tell me that I am way off base here. The build.zig file must be very wrong. How would I figure out what's correct? Well, I know that the mach-examples directory, where I helped to create the sprite2d example before (in part 1), definitely works. Maybe *that* build.zig is different.

A quick peek at the file confirmed my suspicions. It's very different. Also, if I'm going to copy and paste some of the build steps from that file into my own, I will have to do a lot of editing in order to make it work with my own setup.

The first thing I do is read the build file in mach-examples carefully to get a sense of what I definitely do not need, what I *probably* do not need and what I *probably* want. Anything at the level of *probably want* to *definitely want* just falls under the same mental bucket.

It's actually easier than I think. I basically have to cleanup the `inline` portion that has the array of examples. I am just going to copy the "pbr-basic" example because it has the most options. I will have to updated the `deps` since I don't *think* I need any dependencies. Everything else is set to "false" by default so maybe that's good enough?

I try that by running mach-examples command: build run-mach-demo-game which tells me that my files do not exist. Why? Well, of course, I have them under /src and it wants to find them under core\mach-demo-game\main.zig: FileNotFound. This means I have to change the area of the code that looks for the src files in build.zig.

Well, it's not pretty but it works! I have a starting point. I did get the following error error: pathspec 'libs/zigimg' did not match any file(s) known to git but I suspect it's just that I'm missing something silly. I'll circle back to this.

At this point, I let Stephen know that his "Getting Started" example might be outdated and needs a refresh.

At this point, the current state of my code is like this.

Stephen pointed out to me that the package manager updates did, indeed, cause some issues with existing build processes. This all confirms what I suspected. Stephen pointed me to one PR and one issue in the repo that would help me fix up my build so it looks better than the mess I managed to create.

If you're wondering why I didn't just skip to asking the question in the first place, I can answer that. In a professional environment, it's pretty much expected that you raise your hand when you hit a blocker. This way, you leverage other peoples knowledge to work through problems faster. However, this isn't a professional (corporate) environment. I want to learn. So every opportunity to fall flat on my face is an opportunity to learn. This learning will help increase my own knowledge and speed as I keep going.

Using Stephens help, I was able to clean up the build.zig file. I could then run zig build run and everything worked. The cleaned up file is here.

At this point, I have to decide what to tackle next and how to tackle it.

What game do I want to make?

I will not go too deep into the history of this game but, when I was a young kid, I loved playing a game called "Uncharted Waters" on the NES. The sequel is my favorite game of all time, even to this day, called "Uncharted Waters: New Horizons" for the SNES. It's not hard to find some information on this game via Google. It blended just the right amount of story, exploration and satisfaction in economics that I wanted. I want to re-create this game but with several important twists and updates.

Essentially you build up a fleet of ships, travel the oceans, find and invest in ports, trade, fight off pirates and more.

Now I need to write some actual code for my version of this game

Ultimately, I am going to need some structs with functions to encapsulate my game functions. I mostly just want to test my chops and see if I can have a separate file that houses a simple struct with a simple function that logs something to the console, import that into main.zig and then run it.

My understanding is that imported zig files are, in and of themselves, a struct, of sorts. That means you do not need to define a struct inside a secondary file. I can just literally write a simple public testOutput function, import the file in main.zig, use it as if it was a struct like const test = @import('test.zig'); test.testOutput(); and it should work. Let's see if my understanding is correct.

That worked!

Now I can start adding a little bit of structure, confident I will have to re-organize it later, to my game functions. What game functions should I write?

A really basic starting point is having a player move around the screen. I will have to track their coordinates and allow for functions like moving up, down, left and right. I could write a simple "player.zig" file that stores the world coordinates of the player along with those simple functions.

How will I test that? Well, instead of drawing the triangle on the screen (or, really, anything), a simpler way would be to give functions certain inputs and then watch the outputs. My initial thought was to listen for user inputs in the console (so I could type out "move player up 1") but a quick Google search showed me that might be a bad way to go. Besides, even if I could do everything by running a mini command line interface, any time I wanted to test something, I would have to repeat my keyboard commands.

A better way would be to use Zigs "test" functionality where you can run tests and check for whatever you need. This definitely seems better. I could keep the independence and still draw the triangle if I really want to. Let's give that a go.

A little bit of reading up on the Zig documentation and some adjustments later... I get this output:

Test [1/1] prints...
Output: 12
All 1 tests passed.

From zig test .\src\test.zig

We are now off to the races.

Now I just need to do a simple player coordinate system with directional updates. While I'm at it, I could add a few other things I know for sure I'll need for even just the basic things in my game.

I will need NPCs so I may as well just clone the Player stuff and name it NPCs.

I will need some world data. A world can be swapped for other worlds. So I might consider the world to be what you travel as you're on your ship floating through the ocean. Then the world gets changed to a city if you harbor at a port. I'll keep the world simple for now. Just a size and maybe an array of "blocked off x/y coordinates" as if to simulate areas of the world a player and npc cannot get into.

I think there's enough for now.

In part 3, I will try to start piecing things together. My sprite2d example has the ability to import JSON files. I could pull in some JSON data for my player, NPC and world. Then I can use my newly tested structs to manage them in the game. I will also debate the idea of pulling in the rendering portion of my sprite2d example and actually have some rendered sprites using the new structs. This might be a good way to also visually test the idea of "blocked positions" in the world by having the user manuever their player around the world and not be able to walk over certain coordinates.

Stay tuned! I am just getting warmed up.