To make all the decoration perfect, we need a little smiley atop the
playfield which can be used to restart the game.
We create a Button called sm (from smiley btw), bind it to an event, and pack it at the top of the main window, padding it 20 pixels wide in both directions. The photo configuration assigns an image to any widget that can hold one, such as a button. In this case we have a collection of base64 encoded GIFs directly in the code (not shown here); this has the advantages of making the code stand-alone. As we will see later, there are also tiny Images for the empty field and the numbers and not only the flag; the main reason for this is to that all buttons should always have precisely the same size. We further have a wide selection of colours for the numbers. Just try to find out wich colour the 8 has...But back to the smiley.
Same as the menu commands, the Button is useless by itself. To get things started we create a channel named restartCh. We will set things up such that sending something (anything, really) over the channel will restart the game.
But before we can initialise the game field, we of course have to create it. To contain the field we create a new a frame with a given width. The width is just to have something to start with and will be adjusted by the packer as needed. The frame is packed below the smiley button. They both are told to be at the top of main, but because it is packed after the button it is placed below.
Until now this was all very plain and straight. The buttons for the
field are created in a more complex way. The function buttons
is used to create a number of buttons along with their position, and
assigns them to allbuttons (i.e. allbuttons has type
[((Int, Int), Button)]. To pack them, we have to iterate
through the whole list using
mapM_. And in order to have all
buttons appear on the screen at once, and not one by one, we wrap
delayWish around this.
Figure 11: The uninitialised hsMines field
In this case the grid packer is used to put all the
buttons at exactly stated positions in a grid. If
the code compilation does not reach any point where the game is
started, the packed but uninitialised game field including the smiley
and all thing looks like this. To make a distinction between
uninitialised and initialised fields, we put a little star on
each Button. The result is shown in
All that is left to do now in run is to initiate the game for
the first time. The actual playing happens in the event handling of
There is a bit more to clean up the window and such; let us go through this step by step. start is a function that reads the variable varDiff and sends the value over the channel restatCh. This has the effect of restarting the game (see buttons below, the event of receiving something on that channel restarts the game).
First, we build a composed event that handles the menu buttons: clicking the smiley (event startClick) should restart the game, and selecting the quit menu (event quitClick) should finish the game by destroying the main window. Selecting a new size (csr1 to csr5) should not do anything immediately, but we are polity and inform users that they have to restart the game-- clicking just the smiley is not enough!)
On the other hand, a separate event handles the restart. This is because if we restart we destroy the main window, and start the game again from the top. From the interface design point, this is bad design for two reasons: firstly, it makes the interface very jittery with windows unecessary opening and closing and what not, and secondly, there is the awkward handling of having to restart the game with the menu if you want to change the size.