crobgol banner

crobgol

6 devlogs
12h 54m 9s

crobgol (C Robkoo's Game of Life) is a high-performance, zero-dependency simulator for Conway's Game of Life. Written in pure C23, it features memory-optimized bitmap grids (8x smaller than char-based implementations), customizable grid si…

crobgol (C Robkoo’s Game of Life) is a high-performance, zero-dependency simulator for Conway’s Game of Life. Written in pure C23, it features memory-optimized bitmap grids (8x smaller than char-based implementations), customizable grid sizes and frame rates, dual Unicode/ASCII rendering, a custom CLI parser, and built-in benchmarking to compare grid implementations.

Demo Repository

Loading README...

robkoo

And to finish up the initial release, I worked thoroughly on the README to make it pretty and contain lots of info. That includes a demo GIF, shields, badges and such (see attachment).

I also created the releases and set up everything to be as smooth as possible for you guys:) I hope you enjoy my neat little optimized game.


PS: Please ignore that my font/system does not support U+2605 (its meant to show this: ★)

Attachment
0
robkoo

Overview

In this rather short devlog, I simply improved the README to include details about the optimizations I had made and I also added Unicode support (and made the --ascii flag actually do something)


Commit 5e98a9c620

Nothing much to say here besides the fact I improved the README. Besides the aforementioned optimization section (that can be found here), I added a section where you can see a neat table that displays info about each flag available in this tool. You can find it here.


Commit 01a60aa3ed

In this commit I have added Unicode rendering support, which uses the U+2588 (█) character to indicate an alive cell. You can disable this by providing -a, --ascii while running the executable, which will use the # character to display an alive cell. I also slightly improved the game printing by adding a generation counter. You can see this in action by watching the provided video.

0
robkoo

Overview

This rather big devlog is entirely dedicated to optimizing the previous implementation as per the optimization sidequest. To sum it up, I implemented a more optimized data structure to hold the grid and a more optimized way of counting neighbors and computing the next generation in the game. Below you can find a more detailed explanation of the commits that contain this work.


Commit 04af1c83d5

Optimized data structure & functions

The biggest change here is the aforementioned optimized data structure, which is the bitmap. Instead of storing each cell as a char (8 bits per cell), each cell gets a bit inside a bitmap (1 bit per cell), which drastically decreases the memory usage of this program by 8 times.

For this to work, I had to implement functions to work with the bitmap instead of the char array. They’re named exactly the same as the older functions, just with Bitmap prefixed to clearly indicate the use case.

Benchmark

Another notable change in this commit is the addition of a benchmark, which compares the old and new implementations of the function that computes the next generation. You can run a benchmark by passing an option --bench <number> where <number> is the desired amount of iterations to test. Optionally, you can also pass the width and height of the grid that will be benchmarked.

The actual benchmark is not primitive either - it first warms up the caches by running a few iterations before actually timing the functions. Both the functions get the same starting grid to ensure fairness while comparing. The timing uses CLOCK_MONOTONIC, which is NOT affected by system time changes and it can only move forward.

Optimization results

As mentioned before, the memory usage is 8x lower now, due to storing one cell in one bit rather than one cell in 8 bits. The new function to compute the next generation is also much faster, and from my testing can vary between 3x-4x speedup (see image 1). These are by no means small gains, and you would see the difference especially if you’re trying to simulate huge grids. More details can be found in the Optimizations section of the README here.


Commit 0691bc561b

Overview

This is a bugfix/slight improvements commit. I focused on memory leaks, which I initially missed. I also fixed inconsistent naming of the new functions. I have also gone through and improved checks in some functions that were missing to ensure that no incorrect behavior will go on unnoticed.

One small improvement I have made that I find quite noticable is the fact that if you forget to include a value where you’re supposed to have one, the program won’t silently continue and just skip that argument, but rather terminate after printing the help message (see image 2).

Attachment
Attachment
0
robkoo

Summary

Quite a few changes to go over, but to summarize it, I made a CLI argument parser with the option of passing either a short flag (such as -a) or a longer version one (such as --ascii). Based on what you pass you can control some game settings (and more to come!)


Commit c754f6e2e1

This commit contains the implementation of the parser, which I have done completely from scratch. As mentioned in the summary, you can define flags to have values (numeric or generic), short and a long name. The option parser prints to the console if anything went wrong, e.g. you passed a non-numeric value to an option which expects a numeric value.

The actual parsing is done in a very simple way:

  1. Iterate over all the provided arguments starting from index 1 (index 0 is the name of the ran executable)
  2. Check for dashes in argument. This step also provides the count of dashes (0, 1 or 2)
  3. Parse the value of the argument.
  4. Validate whether the argument is defined in an array of structures.
  5. Parse the argument value if the argument requires one
  6. Save argument into a struct and repeat
    If, at any point, either of the steps fail, the current iteration is simply skipped.

[!WARNING]
For anyone checking out this specific commit, be aware of a stray exit(0); that I forgot to remove while I was testing at src/main.c:17


Commit e4b0bb07d6

This is the commit that connected the existing game to the CLI parser. I declared some default values, which are as follows:

What Default value Explanation Width 80 In cells Height 24 In cells FPS 15 In FPS, duh ASCII false True - render using ASCII, false - render using Unicode

If you do not provide an argument with a value, defaults are used. In the provided video you can see the functionality of these flags.

[!WARNING]
Passing a huge FPS value will probably disable the option of killing the app via <C-c> (Control+C)!

Same thing as the previous commit - I forgot to remove a stray exit(0); (at src/main.c:19)

This commit also includes a very useful change: You can now see a neat helpful menu by running the --help flag (see image 1).


Commit b9a9616448

This is just a bugfix commit. I fixed incorrect assignment of parsed values into the app settings, incorrect parsing of short and long options, removed some useless code, and finally, removed the exit(0); call which was blocking the app from being ran.

Attachment
0
robkoo

And now, the commit (48b6f59cab) with the actual Conway’s Game of Life. This was really easy to implement, as the game contains very simple rules. For a cell, with 8 neighbors, the rules are as follows:

  • If the cell is alive, and it has fewer than 2 neighbors, it dies, as if by underpopulation;
  • If the cell is alive, and has 2 or 3 neighbors, it lives on;
  • If the cell is alive, and it has more than 3 neighbors, it dies, as if by overpopulation;
  • If a cell is dead, and has exactly 3 neighbors, it gets revived, as if my reproduction.
    So, by implementing these simple rules, we get the output you see in the attachment video. That runs an infinite loop that always prints the next generation. Don’t worry, this isn’t the end of the project yet, I have a few things planned to make this unique from other implementations of Conway’s Game of Life. Stay tuned! :)
0
robkoo

First commit of this project; and it’s a simple one. All I did, in the first commit (36435c4e53), was prepare the project’s Makefile, README.md, LICENSE.md, directory structure and a simple main.c.
The next commit (27f29b7902) I implemented very basic functions to work with a grid, which hosts all the cells where the game will happen.


In the image you can see, on the top, the grid after filling it via GridRandomize() with 0.224 density. After that, I cleared it and set the cell at x: 22 and y: 4 to 1. This simply showcases the functionality of these functions.

Attachment
0