Programming: Arduino
Check out my other articles:
- Galaga: Failed Attempt at writing a Game
- Sudoku
- Blockade / Snake
- Farkle
- Minesweeper
- Blackjack
- ADC Graph
Minesweeper
If you haven't seen or played Minesweeper then you probably never used a Windows-based computer in the '90s. Minesweeper is a really simple game and lends itself to the Arduino ..
Overview
My version of Minesweeper is inspired by the version found in Microsoft Windows. That version was created by Curt Johnson, originally for OS/2, and ported to Microsoft Windows by Robert Donner, both Microsoft employees at the time. First officially released as part of the Microsoft Entertainment Pack 1 in 1990, it was included in the standard install of Windows 3.1 in 1992, replacing Reversi from Windows 3.0.
Game Play
The goal of the Minesweeper is to locate and flag all of the mines without being "blown up" by clicking on a square with a mine underneath. The location of the mines is discovered by a process of logic. Clicking on the game board will reveal what is hidden underneath the chosen square or squares (a large number of blank squares may be revealed in one go if they are adjacent to each other). Some squares are blank but some contain numbers (1 to 8), each number being the number of mines adjacent to the uncovered square. To help avoid hitting a mine, the location of a suspected mine can be marked by flagging using the icon on the right hand side of play. Likewise, suspect mines can be marked using the question mark icon. The game is won once all mines are successfully flagged.
Code Fragments
The following code highlights areas of interest in the application. It is not intended to be a tutorial or a complete solution. I have provided the complete code for download and you may use or modify it as required.
Maintaining the Puzzle Solution and Game PlayThe solution and game play are stored in a two-dimension array. To conserve memory, I have used the same techniques that I used in Sudoku to compress the data of two cells into a single byte. A single cell can have the value of 0 (binary 0000) for a Mine, 2 (binary 0010) for a Blank or 3 (binary 0011) for a number (lines 001, 003, 004). When cells are marked with a flag or a question, they are logically ORed with the value 4 (binary 0100) and 8 (binary 1000) respectively. Conversely, the original value of a cell can be derived by logically ANDing it with the value 3 (binary 0011) to remove the high-order bits.
The function getBoardValue(byte xCell, byte yCell) retrieves the value of the cell at the nominated coordinates. As the data for two cells is compressed into a single byte in the X axis, the X value is divided by two to determine the byte index (Line 016). The remainder (if any) is stored to determine whether to use the high or low nibble (line 017). The value compressed value is retrieved from the array (line 019) and then masked to reveal the correct nibble (lines 024 and 028). If necessary, a bit-wise right shift is performed on the nibble (line 028).
The function void setBoardValue(int xCell, int yCell, byte value) sets a value in the underlying array using the coordinates and value supplied. Like the getBoardValue() function, the X coordinate is divided by two to determine the byte index (Line 039). The remainder (if any) is stored to determine whether to use the high or low nibble (line 040). The value to be stored is logically ORed with the existing contents of the other nibble in the byte (lines 047, 051).
Cells marked with a flag or question mark will have their original value logically ORed with the value 4 (binary 0100) and 8 (binary 1000) respectively. The original value of a cell can be derived by logically ANDing it with the value 3 (binary 0011) to remove the high-order bits (line 060).
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 032 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 |
#define BOARD_MINE 0 #define BOARD_BLANK_UNC 1 #define BOARD_BLANK 2 #define BOARD_NUMBER 3 #define BOARD_FLAG 4 #define BOARD_QUESTION 8 byte mask0011 = B00000011; byte mask0100 = B00000100; byte mask1000 = B00001000; byte mask11110000 = B11110000; byte mask00001111 = B00001111; int getBoardValue(byte xCell, byte yCell) { xMaj = xCell / 2; xMin = xCell % 2; val = screen[xMaj][yCell]; switch (xMin) { case 0: val = val & mask00001111; break; case 1: val = (val & mask11110000) >> 4; break; } return val; } void setBoardValue(byte xCell, byte yCell, byte value) { xMaj = xCell / 2; xMin = xCell % 2; val = screen[xMaj][yCell]; switch (xMin) { case 0: val = (val & mask11110000) | value; break; case 1: val = (val & mask00001111) | (value << 4); break; } screen[xMaj][yCell] = val; } if ((getBoardValue(x1, y1) & mask0011) == BOARD_MINE) { .. |
The Final Countdown
A timer in my version of Minesweeper counts the seconds down putting extra pressure on the player. If the player does not locate the mines before the time runs out, he loses and all mines are exposed.
The main loop of the game concludes with a delay of 100 milliseconds (line 013) or a tenth of a second. A counter is incremented and on the tenth iteration (lines 014 - 016), a second is taken from the current time (lines 019 - 026). If the time is exhausted (line 028) then the mines are revealed and the game concludes. The 'break' forces execution out of the main loop and control returns to the splash screen.
The drawTime() function splits the minutes and seconds components of the countdown timer into two digits each by dividing each element by ten to derive the 'tens' column (lines 047 and 049) and calculating the remainder to derive the 'units' column (lines 048 and 050). The digits are then rendered individually via the drawNumber(0 function (lines 060 - 079).
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 032 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 |
minutes = 10; seconds = 0; void playGame_Loop() { .. while (true) { .. delay(100); tenths++; if (tenths == 10) { tenths = 0; seconds--; if (seconds < 0 and minutes > 0) { minutes--; seconds = 59; } if (seconds < 0 and minutes == 0) { revealMines(); lcd.imageJPGSD(.., .., SCALE1_1, "Failure"); showBack(); break; } drawTime(); } } } void drawTime() { int minMaj = minutes / 10; int minMin = minutes % 10; int secMaj = seconds / 10; int secMin = seconds % 10; drawNumber(minMaj, DIGIT1_LEFT, DIGITS_TOP); drawNumber(minMin, DIGIT2_LEFT, DIGITS_TOP); lcd.imageJPGSD(DIGIT3_LEFT, DIGITS_TOP, SCALE1_1, "DigitColon"); drawNumber(secMaj, DIGIT4_LEFT, DIGITS_TOP); drawNumber(secMin, DIGIT5_LEFT, DIGITS_TOP); } void drawNumber(int number, int xCoord, int yCoord) { switch (number) { case 0: lcd.imageJPGSD(xCoord, yCoord, SCALE1_1, "Digit0"); break; case 1: lcd.imageJPGSD(xCoord, yCoord, SCALE1_1, "Digit1"); break; case 2: lcd.imageJPGSD(xCoord, yCoord, SCALE1_1, "Digit2"); break; case 3: .. } } |
Wiring Diagram
The diagram below show the wiring diagram when using a standard Arduino Uno (or compatible) and the SmartGPU2. Click on the image to see a detailed drawing.
Downloads
Feel free to download, modify and adapt this code to your needs. Please credit me if you use some or all of this code in your own project. If you generate more board configruations, please submit them back to me and I will add them to the download.
Arduino Source Code |