I Built the Best Minesweeper Online

Posted on Jun 28, 2025

Header showing the GUI

Foss, clean and responsive.

To-do list, tic-tac-toe, snake, Tetris — all great baby's first program choices for sure. However, I was personally kind of frustrated with the selection of Minesweeper reimplementations on the web, so here we are.

Check the game out at https://minesweepe.rs and the code at https://git.sr.ht/~eppuh/Minesweepe.rs .

Now, the notion of tHe BeSt is obviously highly subjective: here are a few great minefields available online for your sweeping pleasure:

Those are all snappy and provide useful features. Yet, each has inconsistencies, bugs, or what seems like weird behaviour to me. As this post is about my creation, I will leave it at that. My code certainly couldn't contain any mistakes!

The Frustrations

Responsiveness

With every version I could find online, one of my biggest issues was the lack of responsive design. So, when you first open up my version, whether that be on desktop or mobile, you are greeted with the screen as full as possible of that which you came for.

Screenshot from my phone of 9x9 field

Mobile view.

Screenshot from my desktop of 9x9 field

Desktop view.

The field will fill up your browser window up to whichever border it hits first. If that scares you, there is a zoom slider in the settings.

Most of the magic happens in this snippet of css:

:root {
  /* Level selection adjusts w & h */
  --width: 9;
  --height: 11;
  --vh: 100dvh;
  --vw: 100dvw;
}
.field {
  display: grid;
  grid-template-columns:
    repeat(var(--width), 1fr);
  grid-template-rows:
    repeat(var(--height), 1fr);
  width:var(--vw);
  height:var(--vh);
  max-width: calc(
     var(--vh) *
    (var(--width) / var(--height))
  );
  max-height: calc(
     var(--vw) *
    (var(--height) / var(--width))
  );
}

Furthermore, the expert level will even swap from 30x16 to 16x30 when your window is taller than it is wide.

Screenshot from my phone of 16x30 field

The field becomes vertical.

For a tad more extra space or concentration, the button to the left of the smiley triggers fullscreen on everything but darn Safari — the Internet Explorer of the 2020s — which does not allow such dangerous options for anything but video elements. To hide the browser UI elements on Apple, you need to install the site as a Progressive Web App (PWA).

Offline usage

Of the four mentioned "competitors" at the beginning, two provide a PWA. Yet, they all still require an internet connection! Fear not, as my PWA works fully offline. The installation happens via "Add to home screen" or some similar option in the browser, in case you weren't aware.

There are of course better, native applications available, but the strength of a PWA is in its universality: it works the same both on desktop and mobile, on iOS and Android.

Also, I personally try to avoid installing closed source software as much as possible, and the open source versions I found honestly were not up to my standards. EDIT: Due to the way F-Droid's search works with translated software, Minesweeper - Antimine had managed to slip under the radar for me. It seems like a good FOSS application, but it is Android exclusive.

Touch support

I want to see a reaction when I put my finger on the screen. I want to be able to slide my finger and not have the window scroll. I want multitouch. Et voilà:

Animations follow touch. The "pointer down" popup is for this video only.

The code for this is admittedly somewhat fragile, and the animation framerate goes down when you slide multiple fingers at the same time (hence no multitouch shown in the video). Nevertheless, I'm mostly content with it, as none of the others provide this kind of touch functionality and feedback.

Graphics

You're looking at CSS and SVGs, which result in sharp retro graphics. Everything copies the original, from colours to the sizes/ratios, except for the missing game area borders — I thought they would be annoying to implement and just waste space. Overall, of the ones trying to mimick the look, I think mine looks the cleanest.

A little detail I would like to point out is the crossed-out mine graphic. In the original game, the red cross is not centered to the mine, so I went and "fixed" that. It does not look odd in the zoomed-in image below, but in the game I found the off-centered one to stick out distractingly.

Image of the old and my version of a crossed-out mine

Original on the left, mine on the right.

Additionally, I made sure that both the mine counter and the timer can grow just a bit if necessary. Here I want to shout out minesweeper.us one more time, as they have also solved this issue in their own way.

Flags go brrr.

The way I wrote this behaviour means the maximum size of a custom field is 100x100, since that guarantees the counter cannot go below -9999, which the code would not be able to handle.

As an aside, the custom field supports a height of 1, which is sort of funny.

The Algorithms

The code is not the prettiest nor the most performant, but it does the job. I would like to focus on one part to which I paid the most attention: the distribution of mines.

A naïve way to go about things is to have a while-loop pick a random number until all the mines are placed, which I suppose is O(m) where m is the number of mines. Initially, this was what I did upon the first click, and it worked fine. The things is, if you create a mine-dense custom field, as the while-loop places more and more mines, it gets increasingly difficult to randomly pick a free cell. I was not happy.

Next, I went with shuffling the array holding the cells, but this has the opposite problem: even for a sparse field in which m << n, where n is the length of the array, it will do an O(n) shuffle. That is far from optimal.

Finally, a realisation: I can place the mines at the beginning of the array and stop the Fisher-Yates shuffle after just m loops, meaning I get the O(m) of the first attempt with the niceties of the second! I tested this empirically and the randomness of the shuffle seems to hold. A code block for those so inclined:

private def init() =
  // add mines
  for i <- 0 until nofmines do
    arr(i).state = MineClosed
  end for
  // shuffle with Fisher-Yates
  for i <- 0 until nofmines do
    val j = i + r.nextInt(arr.length-i)
    val t = matrix(i)
    matrix(i) = matrix(j)
    matrix(j) = t
  end for
  // add numbers
  for i <- matrix.indices do
    val cell = matrix(i)
    cell.row = i / cols
    cell.col = i % cols
    numberize(cell)
  end for
end init

I changed things so that the mines are placed as soon as a new field is requested, instead of at the point of the first click, in order to lower latency where it matters. This decision lead to another algorithmic problem: safe first click.

I believe in the original game, only the single cell you click on is guaranteed to be safe and any mine that might have been there goes to the first available free space starting from top left. For better playability, especially in expert mode, I wanted the 3x3 area around the first click to be safe, but that would mean that there is an alteration to the randomness of mine placement; the top left will always be more dangerous.

Thus, an idea: for each mine in the 3x3, pick a random index in the array from which to start the hunt for a new abode and loop back to the start if necessary. I find this to be a simple but effective improvement over the original logic.

The Missing Features

This is not a perfection creation. I am just quickly going to list some of the worst shortcomings:

  • Lack of a no-guessing mode
  • Lack of play statistics
  • Lack of a leaderboard
  • Lack of import/export
  • Lack of a zoom-in implementation for the poorly-sighted or the less-dexterous
  • Lack of settings such as:
    • How many milliseconds to hold touch to flag a cell
    • When to vibrate when holding (only Chrome and derivaties support such an option)
    • Chording with something else besides left+right click on desktop

For the first one, many papers have been written on how to effectively create solvable fields, indicating that it is not a simple problem. Add to that the fact that I myself have no desire for such a feature, and thus its absence is explained.

Of all of them, the import/export might be one I could come back to.

I should mention that some existing features break on some alternative browsers — even on some that are based on Blink. It is what it is.

I consider this a finished project, or at the very least something to which I am not thinking of returning anytime soon. It's open source — fork it! I will probably be happy to merge anything cool.

In summary

I did not mention it anywhere, but you can start a new game quickly with the space bar.

Anyway, what I added to the status quo:

  • Responsive design
  • Offline usage
  • Touch optimisation
  • Sharp graphics
  • Attention to detail (subjective)
  • Libre license!

Altogether, I am proud of the result: minesweepe.rs

Thanks for reading.

Image of 100x1 board with 96 mines solved with luck]