Programming: Arduino

Check out my other articles:


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.

Δ Top

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.

Δ Top


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 Play

The 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) { ..

Δ Top

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:
      ..
    
  }
  
}
Δ Top

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.


Δ Top

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
Δ Top

Comments?

 Anything to say?
Name :
Email :
Comments :
Confirmation: