# Re: Factor

Factor: the language, the theory, and the practice.

## Minesweeper

Monday, February 12, 2018

Minesweeper is a fun game that was probably made most popular by its inclusion in various Microsoft Windows versions since the early 1990’s.

I thought it would be fun to build a simple Minesweeper clone using Factor.

You can run this by updating to the latest code and running:

``````IN: scratchpad "minesweeper" run
``````

### Game Engine

We are going to represent our game grid as a two-dimensional array of “cells”.

Each cell contains the number of mines contained in the (up to eight) adjacent cells, whether the cell contains a mine, and a “state” flag showing whether the cell was `+clicked+`, `+flagged+`, or marked with a `+question+` mark.

``````SYMBOLS: +clicked+ +flagged+ +question+ ;

TUPLE: cell #adjacent mined? state ;
``````

Making a `(rows, cols)` grid of cells:

``````: make-cells ( rows cols -- cells )
'[ _ [ cell new ] replicate ] replicate ;
``````

We can lookup a particular cell using its `(row, col)` index:

``````:: cell-at ( cells row col -- cell/f )
row cells ?nth [ col swap ?nth ] [ f ] if* ;
``````

Placing a number of mines into cells, just looks for a certain number of unmined cells at random, and then marks them as mined:

``````: unmined-cell ( cells -- cell )
f [ dup mined?>> ] [ drop dup random random ] do while nip ;

: place-mines ( cells n -- cells )
[ dup unmined-cell t >>mined? drop ] times ;
``````

We can count the number of adjacent mines for each cell, by looking at its neighbors:

``````CONSTANT: neighbors {
{ -1 -1 } { -1  0 } { -1  1 }
{  0 -1 }           {  0  1 }
{  1 -1 } {  1  0 } {  1  1 }
}

: adjacent-mines ( cells row col -- #mines )
neighbors [
first2 [ + ] bi-curry@ bi* cell-at
[ mined?>> ] [ f ] if*
] with with with count ;
``````

The `each-cell` word looks at all the cells, helping us update the “adjacent mines” counts:

``````:: each-cell ( ... cells quot: ( ... row col cell -- ... ) -- ... )
cells [| row |
[| cell col | row col cell quot call ] each-index
] each-index ; inline

:: update-counts ( cells -- cells )
cells [| row col cell |
] each-cell cells ;
``````

Since we aren’t storing the number of rows and columns, we can get it from the array of cells:

``````: cells-dim ( cells -- rows cols )
[ length ] [ first length ] bi ;
``````

We can get the number of mines contained in the grid by counting them up:

``````: #mines ( cells -- n )
[ [ mined?>> ] count ] map-sum ;
``````

We can reset the game by making new cells and then placing the same number of mines in them:

``````: reset-cells ( cells -- cells )
[ cells-dim make-cells ] [ #mines place-mines ] bi update-counts ;
``````

The player wins if they click on all cells that aren’t mines:

``````: won? ( cells -- ? )
[ [ { [ state>> +clicked+ = ] [ mined?>> ] } 1|| ] all? ] all? ;
``````

The player loses if they click on any cell that’s a mine:

``````: lost? ( cells -- ? )
[ [ { [ state>> +clicked+ = ] [ mined?>> ] } 1&& ] any? ] any? ;
``````

And then the game is over if the player either wins or loses:

``````: game-over? ( cells -- ? )
{ [ lost? ] [ won? ] } 1|| ;
``````

We can tell this is a new game if no cells are clicked on:

``````: new-game? ( cells -- ? )
[ [ state>> +clicked+ = ] any? ] any? not ;
``````

When we click on a cell, if it is not adjacent to any mines, we click on all the “clickable” (non-mined) cells around it:

``````DEFER: click-cell-at

:: click-cells-around ( cells row col -- )
neighbors [
first2 [ row + ] [ col + ] bi* :> ( row' col' )
cells row' col' cell-at [
mined?>> [
cells row' col' click-cell-at
] unless
] when*
] each ;
``````

Handle clicking a cell. If it’s the first click and the cell is mined, we move it to another random cell, then continue with the click. The click is ignored if the cell was already clicked or flagged. Continue clicking around any cells that have no adjacent mines and are not themselves mined.

``````:: click-cell-at ( cells row col -- )
cells row col cell-at [
cells new-game? [
! first click shouldn't be a mine
dup mined?>> [
cells unmined-cell t >>mined? drop f >>mined?
cells update-counts drop
] when
] when
dup state>> { +clicked+ +flagged+ } member? [ drop ] [
+clicked+ >>state
{ [ mined?>> not ] [ #adjacent>> 0 = ] } 1&& [
cells row col click-cells-around
] when
] if
] when* ;
``````

Handle marking a cell. First by flagging it as a likely mine, or marking with a question mark to come back to later. If the cell is not clicked, we just cycle through flagging, question, or not clicked.

``````:: mark-cell-at ( cells row col -- )
cells row col cell-at [
dup state>> {
{ +clicked+ [ +clicked+ ] }
{ +flagged+ [ +question+ ] }
{ +question+ [ f ] }
{ f [ +flagged+ ] }
} case >>state drop
] when* ;
``````

### Graphical Interface

Our graphical interface is going to consist of a gadget with an array of cells and a cache of OpenGL texture objects that can be easily drawn on the screen.

``````TUPLE: grid-gadget < gadget cells textures ;
``````

When you make a new `grid-gadget`, it initializes the game to a specified number of rows, columns, and number of mines:

``````:: <grid-gadget> ( rows cols mines -- gadget )
rows cols make-cells
mines place-mines update-counts >>cells
H{ } clone >>textures
COLOR: gray <solid> >>interior ;
``````

When ungraft* is called to indicate the gadget is no longer visible on the screen, we clean up the cached textures:

``````M: grid-gadget ungraft*
dup find-gl-context
[ values dispose-each H{ } clone ] change-textures
call-next-method ;
``````

Our images are going to be `32 x 32` squares, so the preferred dimension is number of rows and columns times 32 pixels for each square.

``````M: grid-gadget pref-dim*
cells>> cells-dim [ 32 * ] bi@ swap 2array ;
``````

Some slightly complex logic to decide which image to display for each cell, taking into account whether the game is over so we can show the positions of all the mines and whether the player was correct in flagging a cell as mined, etc:

``````:: cell-image-path ( cell game-over? -- image-path )
game-over? cell mined?>> and [
cell state>> +clicked+ = "mineclicked.gif" "mine.gif" ?
] [
cell state>
{
{ +question+ [ "question.gif" ] }
{ +flagged+ [ game-over? "misflagged.gif" "flagged.gif" ? ] }
{ +clicked+ [
cell mined?>> [
"mine.gif"
] [
"open" ".gif" surround
] if ] }
{ f [ "blank.gif" ] }
} case
] if "vocab:minesweeper/_resources/" prepend ;
``````

Drawing a cached texture is a matter of looking up the image in our texture cache and then rendering to the screen:

``````: draw-cached-texture ( path gadget -- )
textures>> [ load-image { 0 0 } <texture> ] cache
[ dim>> ] [ draw-scaled-texture ] bi ;
``````

Drawing our gadget, is basically drawing all of the cells at their proper locations on the screen:

``````M:: grid-gadget draw-gadget* ( gadget -- )
gadget cells>> [| row col cell |
col row [ 32 * ] bi@ 2array [
cell game-over? cell-image-path
] with-translation
] each-cell ;
``````

Basic handling for the gadget being left-clicked on:

``````:: on-click ( gadget -- )
gadget hand-rel first2 :> ( w h )
h w [ 32 /i ] bi@ :> ( row col )
cells game-over? [
cells row col click-cell-at
``````

Basic handling for the gadget being right-clicked on:

``````:: on-mark ( gadget -- )
gadget hand-rel first2 :> ( w h )
h w [ 32 /i ] bi@ :> ( row col )
cells game-over? [
cells row col mark-cell-at
``````

Logic for creating new games of varying difficulties: easy, medium, and hard:

``````: new-game ( gadget rows cols mines -- )
[ make-cells ] dip place-mines update-counts >>cells
relayout-window ;

: com-easy ( gadget -- ) 7 7 10 new-game ;

: com-medium ( gadget -- ) 15 15 40 new-game ;

: com-hard ( gadget -- ) 15 30 99 new-game ;
``````

We set our gesture handlers for keyboard and mouse inputs:

``````grid-gadget {
{ T{ key-down { sym "1" } } [ com-easy ] }
{ T{ key-down { sym "2" } } [ com-medium ] }
{ T{ key-down { sym "3" } } [ com-hard ] }
{ T{ button-up { # 1 } } [ on-click ] }
{ T{ button-up { # 3 } } [ on-mark ] }
{ T{ key-down { sym " " } } [ on-mark ] }
} set-gestures
``````

And a main word that creates an easy game in our `grid-gadget` and opens it in a new window:

``````MAIN-WINDOW: run-minesweeper {
{ title "Minesweeper" }
{ window-controls
{ normal-title-bar close-button minimize-button } }
• support for retina displays using `2x` images