MadcoreTom

In my journey to better understand the programming language Rust, I decided to re-implement the classic Minesweeper game.

I'll be using SDL2 including its image feature to draw sections of a single PNG image, which contains the numbers, tiles, and other HUD elements

Screenshot of RustMine on Windows

Links

Game features

You pick a difficulty first, defaulting to easy, which defines the number of hidden mines, and the size of the board.

Then you can choose one of two things

  • Flag a tile (that has not already been revealed) as a suspected mine, so you don't accidentally reveal it, or
  • Reveal a tile
    • If it's a mine, you lose
    • If it's near a mine, it shows the number of neighbouring mines (1-8)
    • If there are no neighbouring mines, it reveals itself and its neighbours (since we know none are mines). This will cascade if some of its neighbours also have no neighbouring mines

If you reveal everything except the mines, you win!

The Game State

I like to make an object that represents the entire game state. The update functions can mutate the object, and the render functions can use the same object to draw the whole scene.

Here's the basic object, with the enum Phase and the impl of Game excluded

pub struct Game {
    pub width: usize,
    pub height: usize,
    pub mine_count: u16,
    pub hover_x: u8,
    pub hover_y: u8,
    pub hover_button: u8,
    pub selected_button: u8,
    pub board: Vec<Tile>,
    pub needs_update: bool,
    pub scale: f32,
    pub phase: Phase,
}

The "Cool" Part

The Tile type was interesting to me. Originally I was using an unsigned byte (u8) and some bit masks to store:

  • the count of neighbouring mines (or 9 if its a mine) in the first 6 bits
  • a bit for whether it has been flagged
  • a bit for whether it has been revealed

I started by having the bit masking copy-pasted everywhere, and then it moved into some functions, but then I learnt that you can make a struct that is really just a u8 in memory

#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct Tile(u8);

And then I added some functions to get/set the neighbour count and the bit flags.

impl Tile {
    const SIX_BIT_MASK: u8 = 0b0011_1111; // The bits holding the value
    const FLAGGED_BIT: u8 = 0b0100_0000; // the bit indicating if it is flagged
    const REVEALED_BIT: u8 = 0b1000_0000; // the bit indicating if its revealed

    pub fn new(val: u8) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> u8 {
        self.0 & Self::SIX_BIT_MASK
    }

    pub fn set_value(&mut self, value: u8) {
        self.0 = (self.0 & !Self::SIX_BIT_MASK) | (value & Self::SIX_BIT_MASK);
    }

    pub fn get_flagged(&self) -> bool {
        (self.0 & Self::FLAGGED_BIT) != 0
    }
    // ... and more 
}

Drawing

The drawing logic basically takes this image

an image comprised of many smaller images that make up the game user interface

and draws segments of this (defined as a Rect) on sections of the canvas (also using Rect) like

    let tile_src = Rect::new(tx, 0, TILE_PX, TILE_PX);
    let tile_dst = Rect::new(
        (x * TILE_PX as usize) as i32,
        HEADER_HEIGHT as i32 + (y * TILE_PX as usize) as i32,
        TILE_PX,
        TILE_PX,
    );

The above code is used to draw the tile defined by tx (e.g. 5 for the digit 5) and draw it at x, y.

It's worth noting also that there's a scaling algorithm that scales the canvas up in whole numbers so that it fits most of the screen. Since the higher difficulties have more rows and columns, this can mean it resizes when you update difficulty

Building the project

I wanted to be able to build and run this project on Windows and on my Chromebook. This time using SDL2 (as opposed to my Rust Life project) which was more stable, and just required the following for the Chromebook

apt install libsdl2-dev libsdl2-image-dev

and for Windows I had to grab the lib and dll from

Library Version Link Filename
SDL2 2.30.11 https://github.com/libsdl-org/SDL/releases/tag/release-2.30.11 SDL2-devel-2.30.11-VC.zip
SDL2_image 2.8.8 https://github.com/libsdl-org/SDL_image/releases/tag/release-2.8.8 SDL2_image-devel-2.8.8-VC.zip

Feedback

I suggest you give it a try, and if you've got the time, I'd accept feedback via GitHub issues.

I decided not to generate any of the code using AI, and simply using it the same way I would use a Google search, to learn the options and when to choose one over the other. I'm sure it could probably one-shot making Minsweeper, but where's the fun in that?