Minesweeper-nw banner

Minesweeper-nw

17 devlogs
28h 20m 24s

Minesweeper-nw is a Minesweeper game for the Numworks calculator, written in Rust (no_std/bare metal / NO OS). It runs on embedded hardware with approximately 100KB of RAM (heap and stack combined) on a Cortex-M7.
Due to severe screen tearing, th…

Minesweeper-nw is a Minesweeper game for the Numworks calculator, written in Rust (no_std/bare metal / NO OS). It runs on embedded hardware with approximately 100KB of RAM (heap and stack combined) on a Cortex-M7.
Due to severe screen tearing, the LCD cannot be fully redrawn every frame, even though the CPU is powerful enough. This required implementing a custom “dirty rect” or partial redraw system to update only the necessary areas (:
There is no pre-existing game or UI engine, i had to code everything myself while dealing with the screen’s constraints. However.
I made sure to avoid hardcoding as much as possible, designing the whole system to be (in my opinion) quite modular and well-structured.

This project uses AI

I’m using Gemini to translate documentation and to ask about how game engines and UI systems work.

Demo Repository

Loading README...

Oignon

Shipped this project!

Hours: 28.34
Cookies: 🍪 912
Multiplier: 26.81 cookies/hr

Minesweeper-nw is a Minesweeper game written in Rust no_std (bare metal, no OS) for the NumWorks graphing calculator. It is my second Rust project, learned self-taught, and also my second low-level embedded project (in fact, I’ve only done bare-metal Rust so far).

The NumWorks allocates around 100 KB of RAM (stack and heap combined) to external applications, and uses a Cortex-M7 processor.

This project is built on Eadkp (my first Rust project), a framework I developed myself to abstract the hardware and the different NumWorks models in Rust. It handles display, keyboard input, storage, images, battery management, and also includes a PC simulator for development. I created it to avoid rewriting the same boilerplate for every project.

There is no game engine available for this hardware.
The screen suffers from heavy screen tearing, making full redraws every frame impractical. Because of that, I designed an event-driven graphics rendering pipeline: only the elements that actually need updating are redrawn. The system relies on a list of RenderCommands generated by the game logic and consumed by the renderer.

Since RAM is the critical resource, the game board uses bitpacking.
All data for a cell (mine, revealed, flagged, adjacent mine count) is stored in a single byte using bitmask manipulation. A 10*10 grid therefore uses only 100 bytes instead of 0.39 KB with a naïve struct layout.

The reveal propagation also uses an iterative BFS with an explicit stack instead of recursion, to avoid blowing the stack (stack overflow) on the Cortex-M7.

The current game is automatically saved when closing the app and restored on the next launch, without affecting the timer. This feature was planned from the very beginning of the architecture, which made implementing it much less painful than I had imagined.

If you would like more technical details about optimization, see the documentation page: Optimization (French version available).

Demo video (Compilation + Gameplay):
https://youtu.be/Im5sqfB5wsw?si=7NMjHolD1MnU9g4v

Statistics:

  • 1435 lines of code (excluding Eadkp)
  • Two difficulty modes
  • Win/loss/time statistics
  • Current game save system

For months, I had wanted to create a game for the NumWorks calculator. It would have been infinitely faster and easier to make it in C/C++, since you can build on existing projects and the limited community documentation.

But I chose Rust because I don’t particularly like C or C++, and I built a framework because I would have had to write that code anyway, and now I can easily reuse it for future projects.

PS: Now I love doing this (bare metal dev) 🙂

Oignon

I’ve documented the optimization techniques used in the app.

Attachment
0
Oignon

v1.2.2 - Statistics!

I’ve added a statistics and scoring system to the game!

It does exactly what you’d expect: it tracks your wins, losses, and average play time, while keeping Normal and Hard modes separate.

Of course, all stats are saved, so you won’t lose your data when closing the app (:

Aside from that, I did some refactoring here and there, minor tweaks, etc.


This is officially the last content update for the game. It’s finally feature-complete!

Attachment
0
Oignon

I was fed up with hardcoding text display in the menus, so I refactored the entire menu rendering system. To do this, I built a component rendering module with a simplified Layout system.

I probably over-invested in this new system, it was a huge sink in terms of time and brainpower, but I truly hated how I was handling UI text before.

Attachment
0
Oignon

v1.1.0 - The States saving!

This version introduces a game state saving system! When you quit the game while in the middle of a match, it is automatically saved. As soon as you relaunch the game, it reloads and you can continue playing as if nothing happened. Your game time will not be affected.

This is very useful, especially if you are working on your Hard Grid and suddenly need to calculate 1 + 1well, you can now exit the game, do your math, and come back. Or, if you don’t want anyone to see you playing Minesweeper, you can close it without losing your current progress.


The principle is simple: when you exit the game, it saves all the important parameters of the current match into a minesweeper_game.sav file. Then, when the app launches, if the file exists, the data inside is loaded.

Technically, this feature was not easy to implement at all! Fortunately, I had already planned the game’s structure from the beginning to potentially add this feature, which saved me from having to build something flimsy or rewrite part of the engine.

I realized there was an error, there was a dummy version of the file-reading functions in eadkp (the lib I wrote and am using here), so I ended up doing some bug hunting.

In any case, this was an addition I was dreading, but it all came together nicely in the end, so it’s all good (:

0
Oignon

v1.0.0

First version of Minesweeper for the Numworks calculator.

New features:

  • Added a rust.yml workflow to automatically include the compiled package (NWA) in every release and tag.
  • Added a README (you just gotta have one).
  • Added a LOGO (I didn’t have one yet ¯_(ツ)_/¯ ).

Test video on the real hardware! (It worked on the very first try!)

0
Oignon

I added explanatory text and the final time to the EndGame screen. I also added a ‘Hard’ mode with the largest possible grid (small cells and 15% mines).

Attachment
Attachment
0
Oignon

I added a timer and the theoretical number of remaining mines.

Attachment
0
Oignon

I added the title bar rendering for both the game and the main menu. I also refactored part of the code for these bars and moved the rendering functions into a separate file.

Attachment
Attachment
0
Oignon

I improved a few details, like centering the grid on the screen, fixing the main menu text alignment, and adding a background to the main menu.

I also made some code improvements, such as reducing memory fragmentation by eliminating unnecessary heap allocations and deallocations whenever possible.

0
Oignon

I added the Game End screen (won and lose). Also, when you lose, all the mines are revealed.

0
Oignon

I’ve implemented the system that reveals all empty tiles (I don’t know what this system is called, but there you go).

Next step: the Game Over system!

0
Oignon

I added mine rendering

0
Oignon

I’ve added the main interactions (reveal, flag). I also fixed a bit-shift bug in how I was storing the number of adjacent mines, as well as a bug in the randint function of my eadkp library used here.

0
Oignon

So, you can’t tell just by looking at it, but I’ve written the state management code and the cell display logic (which was more complicated than expected!).

I’ve also coded the cursor movement. Now, I’m going to tackle the interaction system.

0
Oignon

I added:

  • Background rendering
  • Cell margins
  • Different cell rendering based on size (large or small)

And fixed:

  • Cell sizing
0
Oignon

I am currently creating a Minesweeper game for the Numworks calculator.

Well then! It turns out I might have forgotten to install Wakatime in my
Docker container, so a good portion of my coding time wasn’t tracked ):
You can easily add at least 2 more hours to the total.

First off, I spent 2 days thinking about the code structure. Since there’s
no engine, no multi-threading, and especially some nasty screen tearing
that forces me to only update what’s necessary, I can’t redraw the whole
screen every time (that’s my main constraint).

First, I coded a State system to manage what to render (menu, game,
game over). Then, I built an event-driven graphical rendering pipeline.
Everything works through a shared object, allowing all components to
access the same data (position, score, etc.).


I admit the current rendering isn’t great (I forgot the background
instruction, sorry), but you’ve got to have the vision! This is literally
the first test render (by the way, my code worked in only two tries,
which is remarkable).

0