It has been some time since I last wrote anything. I thought that sharing something enjoyable with you could serve as a means to ease back into it. Thus, I patiently awaited something to rekindle my inspiration and interest, which eventually came in the form of the old yet addictive game, Minesweeper :)
My daughter recently discovered Minesweeper on her PC, and to my surprise, it has become a fun bonding activity for us. I sit beside her, offering my two cents every now and then, and I'm amazed at how quickly she's picked up the logic behind the game. She's been playing it faster and faster, breaking records by the day.
As I sat there, I couldn't help but wonder about the code behind the game. Specifically, I found myself pondering over the layout of the game board. Is it a 2-dimensional array or a flat array? How are the numbers being computed? I mean, what kind of wizardry did it take to build such a thing?
That’s enough to light a fire under my arse and get me going :)
So, Wanna jump in on the ride of creating a Minesweeper game using SolidJS?
Let’s go!
Hey! for more content like the one you're about to read check out @mattibarzeev on Twitter 🍻
The code can be found in this GitHub repository:
https://github.com/mbarzeev/solid-minesweeper
The Game Board
The board is made out of an array with 16 cells. This will allow us to create a 4x4 board. Currently it is hard-coded, later on we can do some random arrangements there:
const boardArray: number[] = [0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0];
The content of the array is made of 0’s and 1’s, where 1 indicates a tile with a mine in it, and 0 is an empty tile.
To display it I’m using Solid’s <For>
component:
const App: Component = () => {
return (
<div class={styles.App}>
<header class={styles.header}>
<For each={boardArray}>
{(item: number, index: () => number) => <div>{item}</div>}
</For>
</header>
</div>
);
};
Which results in this:
Wait… it will get better.
Obviously we need a way to arrange these in a 4x4 grid, and for that I will use a … css grid :)
I will put my <For …>
tag inside a div which has a board
css class to it, and here is the definition of that class:
.board {
--tile-dimension: 30px;
--row-length: 4;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(var(--tile-dimension), 1fr));
max-width: calc(var(--row-length) * var(--tile-dimension));
}
Now it looks like this:
Yep, more like it.
We know that those empty cells need to indicate the number of mines that are adjacent to them in all 8 directions. How do we do that?
Minesweeper Simple Algorithm
I’m going with a “brute-force” approach here, though it might be proven to be the most efficient manner of achieving it. I’m inspecting each tile for mines next to it, but since this array is flat we need to be smart about it. Let me put the code here first and then explain what goes on in the “getMinesCount()” function.
Here is the rendering, where we pass the index of the current cell to the getMinesCount function:
<div class={styles.board}>
<For each={boardArray} fallback={<div>Loading...</div>}>
{(item: number, index: () => number) => (
<div style={{width: '30px', height: '30px'}}>{getMinesCount(index())}</div>
)}
</For>
</div>
And here is the implementation of the getMinesCount function:
function getMinesCount(index: number) {
const cell = boardArray[index];
if (cell === 1) {
return 'x';
} else {
let minesCount = 0;
const hasLeftCells = index % ROW_LENGTH > 0;
const hasRightCells = (index + 1) % ROW_LENGTH !== 0;
const bottomCellIndex = index + ROW_LENGTH;
const topCellIndex = index - ROW_LENGTH;
if (boardArray[bottomCellIndex] === 1) {
minesCount++;
}
if (boardArray[topCellIndex] === 1) {
minesCount++;
}
if (hasLeftCells) {
boardArray[index - 1] === 1 && minesCount++;
boardArray[topCellIndex - 1] === 1 && minesCount++;
boardArray[bottomCellIndex - 1] === 1 && minesCount++;
}
if (hasRightCells) {
boardArray[index + 1] === 1 && minesCount++;
boardArray[topCellIndex + 1] && minesCount++;
hasRightCells && boardArray[bottomCellIndex + 1] && minesCount++;
}
return minesCount;
}
}
The logic is quite simple - we calculate the top and bottom cells, figure out if the cell has left or right neighbors and increment the minesCount accordingly.
It’s actually not that bad, given that we have O(n) complexity.
Here how it looks now:
The “X” indicate the mines and the numbers are indicating how many mines are adjacent to the cell. Time to create the Tile component.
You might have noticed that I’m using a constant named “ROW_LENGTH” in order to compute the board dimensions, and I also have this value in the CSS as a variable, and this kinda annoys me that changing the grid dimensions requires changing both the CSS and JS instead of just a single place.
For that I chose to define the CSS variable within the rendering code, like so:
<div class={styles.App} style={{'--row-length': ROW_LENGTH}}>
<header class={styles.header}>
<div class={styles.board}>
<For each={boardArray}>
{(item: number, index: () => number) => <Tile {...getTileData(index())} />}
</For>
</div>
</header>
</div>
In this way I only need to change the “ROW_LENGTH” constant and everything aligned perfectly.
The Tile Component
import {Component, createSignal} from 'solid-js';
import styles from './Tile.module.css';
export type TileData = {
isMine: boolean;
value?: number;
};
const Tile: Component<TileData> = (data: TileData) => {
const [isOpen, setIsOpen] = createSignal(false);
const [isMarked, setIsMarked] = createSignal(false);
const onTileClicked = (event: MouseEvent) => {
!isMarked() && setIsOpen(true);
};
const onTileContextClick = (event: MouseEvent) => {
event.preventDefault();
!isOpen() && setIsMarked(!isMarked());
};
const value = data.isMine ? 'X' : data.value;
return (
<div class={styles.Tile} onclick={onTileClicked} onContextMenu={onTileContextClick}>
<div class={styles.value} classList={{[styles.exposed]: isOpen() || isMarked()}}>
{isMarked() ? '🚩' : value !== 0 ? value : ''}
</div>
</div>
);
};
export default Tile;
As you can see, right-clicking marks the tile, while a regular click opens it. You can toggle the marking but once opened it is done.
And we use it like this in the main App:
<For each={boardArray} fallback={<div>Loading...</div>}>
{(item: number, index: () => number) => <Tile {...getTileData(index())} />}
</For>
And what we got is a board with “blank” tiles, and when we click on them they open - those with the numbers show the numbers, those with the mines show an “X” and the marked ones are flagged.
Random Board
Time to put some randomness into the pot. I would like to generate a flat array of 20x20 (400 cells) which has 40 mines scattered in it. Here is the code for it:
const totalMines = 40;
const ROW_LENGTH = 20;
const TOTAL_TILES = Math.pow(ROW_LENGTH, 2);
const boardArray = [...Array(TOTAL_TILES)].fill(0);
let count = 0;
while (count < totalMines) {
const randomCellIndex = Math.floor(Math.random() * TOTAL_TILES);
if (boardArray[randomCellIndex] !== 1) {
boardArray[randomCellIndex] = 1;
count++;
}
}
Here it how it looks without hiding the tiles value for the sake of demonstration:
That’s it for now.
We have a game board that allows us to customize its dimensions and the number of mines we want to include. Currently, we have the ability to expose or mark individual tiles.
The next step is to figure out the logic behind the "auto-expose" feature where multiple tiles are uncovered with a single click. We need to determine the appropriate logic to open adjacent tiles when a single tile is clicked. We also need to address the remaining mechanics of the game to ensure everything runs smoothly.
The code can be found in this GitHub repository:
https://github.com/mbarzeev/solid-minesweeper
That was fun :) stay tuned for more…
This article is one of a 4 parts post series:
- Creating a Minesweeper Game in SolidJS - The Board
- Creating a Minesweeper Game in SolidJS - The Zero-Opening
- Creating a Minesweeper Game in SolidJS - Score, Timer and Game State
- Creating a Minesweeper Game in SolidJS - Completing The Game
Hey! for more content like the one you've just read check out @mattibarzeev on Twitter 🍻
Top comments (3)
I am seeking for the highest-quality websites to play Minesweeper on, as I recently started playing the game online. The website 1000mines.com is one that I've seen. The developers behind it are enthusiasts for old-school video games such as Minesweeper. They want to provide accessible and entertaining games for both novice and expert players by bringing these classics into the present day. I'd be interested in knowing about any further suggestions or individual favorites. Discovering a website that strikes a mix between traditional gaming and contemporary upgrades is essential for the optimal Minesweeper experience.
I didn't know this was a three part series, you should include the other two links in this post.
Done :)
Thanks!