Arduino Game - Minesweeper with 8x8 Neomatrix

Arduino Game - Minesweeper with 8x8 Neomatrix © CC BY-SA

A Fun version of the Minesweeper game, using Arduino Leonardo, 8x8 NeoMatrix Led Array, Button and Joystick Module.

  • 499 views
  • 0 comments
  • 3 respects

Components and supplies

Apps and online services

About this project


It was then that I found the works of Rachit Belwariar, on the page https://www.geeksforgeeks.org/cpp-implementation-minesweeper-game/ and also that of Cagdas Basaraner, at https://codebuild.blogspot.com/2015/01/algorithms-explained-minesweeper-game.html

I decided to adapt the Belwariar code to show the minefield in a matrix of leds, changing the colors of the leds to indicate the number of mines nearby.

The most interesting part of his code is the mine detector algorithm. The software detect the user click in a cell and start to count mines at nearby position (defined by this array). If it find a mine, increments a counter, and choose a color for the Led.




I am still going to implement the routine to flag suspected mine sites. And create some animations for the end of the game, in the conditions of victory and defeat.

The result is quite fun and guarantees a few hours of leisure.

Ps;.At first I was unable to use the joystick module button, because the maneuver was uncomfortable. But with a few adjustments to the code, I was able to abandon the additional button.

Code

Minesweeper ArduinoArduino
#include <Adafruit_GFX.h>
#include <FastLED_NeoMatrix.h>
#include <FastLED.h>

#define ROW 8 // You can increase that, for more LEDs
#define COL 8

#define SIDE 8
#define MAXSIDE 8

#define MINES 8 // Increase, for more difficult condition
#define MAXMINES 8

#define JOYX A0
#define JOYY A1
#define BUTTON 10 // Thumb Button.

#define LED 8 // Pin for Led Array

#define BRIGHTNESS 8 // To preserve batteries.
#define NUMMATRIX (ROW*COL)

CRGB myBoardColor [ROW] [COL]; 
CRGB realBoardColor[ROW] [COL]; 

CRGB notClicked = CRGB::DarkGreen;
CRGB empty = CRGB::Black;
CRGB haveBomb = CRGB::DarkBlue;
CRGB myPos = CRGB::White;

CRGB leds [NUMMATRIX]; 

CRGB mineColor [9] = {CRGB::Black, CRGB::Yellow, CRGB::Chocolate, CRGB::Red, CRGB::Magenta, CRGB::DarkRed, CRGB::DeepPink, CRGB::DarkMagenta, CRGB::FireBrick};
CRGB fieldColor[3] = {CRGB::Black, CRGB::DarkGreen, CRGB::White};
CRGB bomb = {CRGB::DarkBlue};
CRGB flag = {CRGB::Cyan}; // No flag yet... Next Version

int xMap, yMap, xValue, yValue, xPos, yPos; // Joystick Coord

// Minefield is the Led Board

FastLED_NeoMatrix *minefield = new FastLED_NeoMatrix(leds, 8, 8, 1, 1 );

void setup() {
  delay(1000);
  Serial.begin(115200);
  Serial.println ("Starting Game - Debug Window");
  FastLED.addLeds<NEOPIXEL, LED>(  leds, NUMMATRIX  );

  pinMode (BUTTON, INPUT_PULLUP);

  Serial.println ("Preparing Board");
  minefield->begin();
  minefield->setBrightness(BRIGHTNESS);

  Serial.println ("End Setup");
}

void loop() {
  playMinesweeper ();
  delay(200);
}

// Explosion of circles on ending. Maybe sound?
void gameOver() {
  minefield->clear();
  for (int ray = 0; ray < 10; ray++) {
    minefield->drawCircle(xMap, yMap, ray, CRGB::Blue);
    minefield->show();
    delay(100);
  }
  delay(2000);
}

// I will improve this ending window. 
void victory() {
  minefield->clear();
  for (int ray = 0; ray < 10; ray++) {
    minefield->drawCircle(xMap, yMap, ray, CRGB::White);
    minefield->show();
    delay(100);
  }
  delay(2000);
}

bool isValid(int row, int col)
{
  // Returns true if row number and column number
  // is in range
  return (row >= 0) && (row < SIDE) &&
         (col >= 0) && (col < SIDE);
}

bool isMine (int row, int col, char board[][MAXSIDE])
{
  if (board[row][col] == '*')
    return (true);
  else
    return (false);
}

void printBoard(char myBoard[][MAXSIDE])
{
  Serial.println ("Current Status of Board :");
  int i, j;
  for (i = 0; i < SIDE; i++)
  {
    for (j = 0; j < SIDE; j++) {
      Serial.print(myBoard[i][j]);
      minefield->drawPixel(i , j , myBoardColor[i][j]); // Draw board in led array
    }
    Serial.println();
  }
  minefield->show();
  return;
}

int countAdjacentMines(int row, int col, int mines[][2],
                       char realBoard[][MAXSIDE])
{

  int i;
  int count = 0;


  if (isValid (row - 1, col) == true)
  {
    if (isMine (row - 1, col, realBoard) == true)
      count++;
  }

  if (isValid (row + 1, col) == true)
  {
    if (isMine (row + 1, col, realBoard) == true)
      count++;
  }

  if (isValid (row, col + 1) == true)
  {
    if (isMine (row, col + 1, realBoard) == true)
      count++;
  }

  if (isValid (row, col - 1) == true)
  {
    if (isMine (row, col - 1, realBoard) == true)
      count++;
  }

  if (isValid (row - 1, col + 1) == true)
  {
    if (isMine (row - 1, col + 1, realBoard) == true)
      count++;
  }


  if (isValid (row - 1, col - 1) == true)
  {
    if (isMine (row - 1, col - 1, realBoard) == true)
      count++;
  }

  if (isValid (row + 1, col + 1) == true)
  {
    if (isMine (row + 1, col + 1, realBoard) == true)
      count++;
  }

  if (isValid (row + 1, col - 1) == true)
  {
    if (isMine (row + 1, col - 1, realBoard) == true)
      count++;
  }

  return (count);
}

// A Recursive Function to play the Minesweeper Game
bool playMinesweeperUtil(char myBoard[][MAXSIDE], char realBoard[][MAXSIDE],
                         int mines[][2], int row, int col, int *movesLeft)
{

  // Base Case of Recursion
  if (myBoard[row][col] != 'o') {
    return (false);
  }
  int i, j;

  // You opened a mine
  // You are going to lose
  if (realBoard[row][col] == '*')
  {
    myBoard[row][col] = '*';
    myBoardColor [row][col] = bomb;
    minefield->drawPixel(row, col,  myBoardColor [row][col] ); // Blow mine

    for (i = 0; i < MINES; i++) {
      myBoard[mines[i][0]][mines[i][1]] = '*';
      myBoardColor[mines [i][0]] [mines[i][1]] = bomb;
      minefield->drawPixel(mines[i][0], mines[i][1], bomb ); // Mine color

    }
    // minefield->show();
    printBoard (myBoard);
    Serial.println ("You lost!");
    delay(2000);
    gameOver();
    return (true) ;
  }

  else
  {
    // Calculate the number of adjacent mines and put it
    // on the board
    int count = countAdjacentMines(row, col, mines, realBoard);
    (*movesLeft)--;

    myBoard[row][col] = count + '0';

    if (!count)
    {

      if (isValid (row - 1, col) == true)
      {
        if (isMine (row - 1, col, realBoard) == false)
          playMinesweeperUtil(myBoard, realBoard, mines, row - 1, col, movesLeft);
      }

      if (isValid (row + 1, col) == true)
      {
        if (isMine (row + 1, col, realBoard) == false)
          playMinesweeperUtil(myBoard, realBoard, mines, row + 1, col, movesLeft);
      }

      if (isValid (row, col + 1) == true)
      {
        if (isMine (row, col + 1, realBoard) == false)
          playMinesweeperUtil(myBoard, realBoard, mines, row, col + 1, movesLeft);
      }

      if (isValid (row, col - 1) == true)
      {
        if (isMine (row, col - 1, realBoard) == false)
          playMinesweeperUtil(myBoard, realBoard, mines, row, col - 1, movesLeft);
      }

      if (isValid (row - 1, col + 1) == true)
      {
        if (isMine (row - 1, col + 1, realBoard) == false)
          playMinesweeperUtil(myBoard, realBoard, mines, row - 1, col + 1, movesLeft);
      }

      if (isValid (row - 1, col - 1) == true)
      {
        if (isMine (row - 1, col - 1, realBoard) == false)
          playMinesweeperUtil(myBoard, realBoard, mines, row - 1, col - 1, movesLeft);
      }

      if (isValid (row + 1, col + 1) == true)
      {
        if (isMine (row + 1, col + 1, realBoard) == false)
          playMinesweeperUtil(myBoard, realBoard, mines, row + 1, col + 1, movesLeft);
      }

      if (isValid (row + 1, col - 1) == true)
      {
        if (isMine (row + 1, col - 1, realBoard) == false)
          playMinesweeperUtil(myBoard, realBoard, mines, row + 1, col - 1, movesLeft);
      }
    }
    myBoardColor [row][col] = mineColor[count];
    minefield->drawPixel(mines[i][0], mines[i][1], myBoardColor [row][col] );
    //minefield->show();
    return (false);
  }
}

void placeMines(int mines[][2], char realBoard[][MAXSIDE])
{
  bool mark[MAXSIDE * MAXSIDE];

  memset (mark, false, sizeof (mark));

  // Continue until all random mines have been created.
  for (int i = 0; i < MINES; )
  {
    int random = rand() % (SIDE * SIDE);
    int x = random / SIDE;
    int y = random % SIDE;

    // Add the mine if no mine is placed at this
    // position on the board
    if (mark[random] == false)
    {
      // Row Index of the Mine
      mines[i][0] = x;
      // Column Index of the Mine
      mines[i][1] = y;

      // Place the mine
      realBoard[mines[i][0]][mines[i][1]] = '*';
      mark[random] = true;
      i++;
    }
  }

  return;
}

// A Function to initialise the game
void initialise(char realBoard[][MAXSIDE], char myBoard[][MAXSIDE])
{
  // Initiate the random number generator so that
  // the same configuration doesn't arises
  randomSeed(analogRead(5));

  // Assign all the cells as mine-free
  for (int i = 0; i < SIDE; i++)
  {
    for (int j = 0; j < SIDE; j++)
    {
      myBoard[i][j] = realBoard[i][j] = 'o';
      myBoardColor [i] [j] =  realBoardColor [i] [j] = notClicked;
      minefield->drawPixel (i, j, myBoardColor [i] [j]);
      minefield->show();
    }
  }

  return;
}

// A Function to cheat by revealing where the mines are
// placed.
void cheatMinesweeper (char realBoard[][MAXSIDE])
{
  Serial.println ("The mines locations are");
  printBoard (realBoard);
  return;
}

// A function to replace the mine from (row, col) and put
// it to a vacant space
void replaceMine (int row, int col, char board[][MAXSIDE])
{
  for (int i = 0; i < SIDE; i++)
  {
    for (int j = 0; j < SIDE; j++)
    {
      // Find the first location in the board
      // which is not having a mine and put a mine
      // there.
      if (board[i][j] != '*')
      {
        board[i][j] = '*';
        board[row][col] = 'o';
        return;
      }
    }
  }
  return;
}

// A Function to play Minesweeper game
void playMinesweeper ()
{
  // Initially the game is not over
  bool gameOver = false;

  // Actual Board and My Board
  char realBoard[ROW][COL], myBoard[ROW][COL];

  int movesLeft = SIDE * SIDE - MINES, x, y;
  int mines[MAXMINES][2]; // stores (x,y) coordinates of all mines.

  initialise (realBoard, myBoard);

  // Place the Mines randomly
  placeMines (mines, realBoard);

  /*
    If you want to cheat and know
    where mines are before playing the game
    then uncomment this part


  */
  cheatMinesweeper(realBoard);
  // You are in the game until you have not opened a mine
  // So keep playing

  int currentMoveIndex = 0;
  while (gameOver == false)
  {

    printBoard (myBoard);
    xValue = analogRead(JOYX);
    yValue = analogRead(JOYY);

    xPos = map(xValue, 0, 950, -3, 3); // max 1023 
    yPos = map(yValue, 0, 950, -3, 3); // max 1023 depends of your module

    if (yPos == 1 || yPos == -1) {
      yPos = 0;
    }

    if (xPos == 1 || xPos == -1) {
      xPos = 0;
    }
    
    xMap = xMap + xPos;
    yMap = yMap + yPos;

    if (xMap < 0) {
      xMap = 0;
    }

    if (xMap > 7) {
      xMap = 7;
    }

    if (yMap < 0) {
      yMap = 0;
    }

    if (yMap > 7) {
      yMap = 7;
    }

    minefield->drawPixel(xMap , yMap , myPos ); // Update user cursor
    minefield->show();
    delay(100);

    if (digitalRead(BUTTON) == LOW) {

      if (currentMoveIndex == 0)
      {

        if (isMine (xMap, yMap, realBoard) == true)
          replaceMine (xMap, yMap, realBoard);
      }

      currentMoveIndex ++;

      minefield->drawPixel(xMap, yMap,  myBoardColor[xMap][yMap] ); //Restore color of minefield after move

      delay(100);

      gameOver = playMinesweeperUtil (myBoard, realBoard, mines, xMap, yMap, &movesLeft);
    }


    if ((gameOver == false) && (movesLeft == 0))
    {
      Serial.println ("You won !");
      delay(2000);
      victory();
      gameOver = true;
    }
  }
  minefield->show();
  return;
}

Schematics

Minesweeper Arduino
Wiring for the project
Minesweeper bb k32oclscim

Comments

Similar projects you might like

Arduino Pocket Game Console + A-Maze - Maze Game

Project tutorial by Alojz Jakob

  • 10,679 views
  • 8 comments
  • 28 respects

Arduino Game By LCD

Project tutorial by Mohammed Magdy

  • 70,039 views
  • 71 comments
  • 218 respects

Space Trash Game using Arduino and OLED Display

Project tutorial by Pi BOTS MakerHub

  • 1,345 views
  • 2 comments
  • 10 respects

Minesweeper Game with Arduino

Project tutorial by Lima NEXTPCB

  • 1,160 views
  • 0 comments
  • 0 respects

Ninja Dollar - LCD game

Project tutorial by anime

  • 6,450 views
  • 12 comments
  • 11 respects

LED Roulette Game

Project tutorial by Arduino “having11” Guy

  • 14,027 views
  • 7 comments
  • 23 respects
Add projectSign up / Login