Click here to Skip to main content
Click here to Skip to main content

Minesweeper - Minesweeper game for Android

, 28 Sep 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
The Minesweeper game for Android.
Minesweeper - Startup screenshot Minesweeper - New game screenshot
Minesweeper - Startup screenshot
Minesweeper - New game screenshot
Minesweeper - Game won screenshot Minesweeper - Game lost screenshot
Minesweeper - Game won screenshot
Minesweeper - Game lost screenshot

Contents

Introduction

Minesweeper is a single player game. The object of the game is to clear a minefield without hitting a mine. Minesweeper is available not only for Windows but also for other platforms (including most of the Linux variants). Minesweeper is very popular in the Windows world, it has been bundled with Windows since Windows 3.1.

In this article, we will create a Minesweeper clone for Android. We will try to implement most of the features which are available in the Windows Minesweeper. This article is targeted at Intermediate-Advanced level developers and expects familiarity with Java and development for Android.

About the game

In Minesweeper, we are presented with a grid of blocks and some of them randomly contain mines. In our implementation, we will limit to typical Beginner Level implementation. The number of rows and columns in our implementation will be 9 and the total number of mines will be 10. Extending the game for Intermediate, Advanced, is easy though (just require a change in value of 3 variables in our code).

Well, in this article, we will not talk much about how to play the game but we will talk about some of the features that we should think about before implementing it (Windows version):

  1. Left click on a block opens the block.
  2. Right click on a block allows to mark a block as flagged (confirmed mine underneath); flagged blocks can be marked with question marks (doubt about presence of mine), and question marked blocks can be un-marked as well.
  3. First block never contains a mine underneath; this reduces the pain of guessing even the first block.
  4. If an uncovered block is blank, nearby blocks are recursively opened till a numbered block is opened; simulating a ripple effect.
  5. Clicking left-right/middle button on a block, where all mines in nearby blocks are already flagged, uncovers all nearby covered blocks.
  6. Timer starts on clicking the first block and not after selecting a new game.

Enough talking now, let's start working on it.

One step at a time

The best way to explain or implement any complex system is to take one step at a time. We will follow that approach here. First of all, we will talk about the GUI, layout, and look-and-feel of the application. We will also talk about some techniques used while creating the layout. After that we will talk about keeping track of time in an Android application. Next we will discuss the differences between mouse, click, and touch events and how we will respond to user actions. The last step will be about implementing the complete game and most of the features discussed in the About the game section.

The look and feel

Let's talk about some of the aspects of designing a GUI for a Minesweeper game. We will talk about the overall application layout and also a few techniques used in creating the game.

Application layout

For Minesweeper, we will use a TableLayout. We add three rows to the TableLayout:

  1. The first row contains three columns for timer, new game button, and mine count display. For timer and mine count display, we have used a TextView. For the new game button, we have used an ImageButton.
  2. The second row contains an empty TextView with a height of 50 pixels. It just helps as a spacer between the top row and the mine field.
  3. This row contains another TableLayout, which is used for displaying the minefield. We add rows of buttons to this TableLayout dynamically.

GUI Layout

The code for the layout looks like (removed some additional attributes to save space):

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:stretchColumns="0,2"
  android:background="@drawable/back">
  <TableRow>
    <TextView
      android:id="@+id/Timer"
      android:layout_column="0"
      android:text="000" />
    <ImageButton android:id="@+id/Smiley"
      android:layout_column="1"
      android:background="@drawable/smiley_button_states"
      android:layout_height="48px"/>
    <TextView
      android:id="@+id/MineCount"
      android:layout_column="2"
      android:text="000" />
  </TableRow>
  <TableRow>
    <TextView
      android:layout_column="0"
      android:layout_height="50px"
      android:layout_span="3"
      android:padding="10dip"/>
  </TableRow>
  <TableRow>
    <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/MineField"
      android:layout_width="260px"
      android:layout_height="260px"
      android:gravity="bottom"
      android:stretchColumns="*"
      android:layout_span="3"
      android:padding="5dip" >
    </TableLayout>
  </TableRow>
</TableLayout>

Using external fonts

For timer and mine count display, we have used an external font. We have used the LCD mono font (specified in the Resources section). Using external fonts is fairly easy in Android and is a two-step process:

  1. Create a fonts folder under the assets folder of the project. Copy the TTF (True Type Font) file to the fonts folder.
  2. Create an object of Typeface by calling createFromAsset and passing the TTF file name and set the Typeface of the TextView to this object. The code for doing this looks like:
  3. private TextView txtMineCount;
    private TextView txtTimer;
    txtMineCount = (TextView) findViewById(R.id.MineCount);
    txtTimer = (TextView) findViewById(R.id.Timer);
    
    // set font style for timer and mine count to LCD style
    Typeface lcdFont = Typeface.createFromAsset(getAssets(),
        "fonts/lcd2mono.ttf");
    txtMineCount.setTypeface(lcdFont);
    txtTimer.setTypeface(lcdFont);

Use styles

In our Minesweeper application, the smiley on the new game button changes to a nervous smiley when the button is clicked. Basically, we want a different image when the button is in pressed state (nervous smiley) and want another image when it is in normal state (smiley). In order to achieve this, we have used styles. The effect looks like:

New Game button states

Using styles is also a two-step process:

  1. Create a new XML file (style definition file) which specifies the images to be used with the corresponding button states. For example, in pressed state, we want to use an image named surprise, and in normal state, we want to use the image named smile. Of course both these images are already copied to our res/drawable folder. The style file looks like:
  2. <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
      <item android:state_focused="true" 
        android:state_pressed="false" 
        android:drawable="@drawable/smile" />
      <item android:state_focused="true" 
        android:state_pressed="true"
        android:drawable="@drawable/surprise" />
      <item android:state_focused="false" 
        android:state_pressed="true"
        android:drawable="@drawable/surprise" />
      <item android:drawable="@drawable/smile" />
    </selector>
  3. Update/add the background property attribute for the New Game button and set its values to the above created style file. The updated ImageButton code looks like:
  4. <ImageButton android:id="@+id/Smiley"
      android:layout_column="1"
      android:background="@drawable/smiley_button_states"
      android:layout_height="48px"/>

Dynamic rows to TableLayout

Adding blocks (Block is a class derived from the Button class with added functionality to support the implementation) dynamically works the same way we think it should work or expect it to work. We can divide it into the following steps:

  1. Create an object of TableRow and set its layout parameters.
  2. Add blocks (extended buttons) to the row object created above.
  3. Get the instance of TableLayout (minefield) using the findViewById method.
  4. Add the row created above to the TableLayout.

The final code resembles:

private TableLayout mineField; // table layout to add mines to

private Block blocks[][]; // blocks for mine field

public void onCreate(Bundle savedInstanceState)
{
  ...
  mineField = (TableLayout)findViewById(R.id.MineField);
}

private void showMineField()
{
  for (int row = 1; row < numberOfRowsInMineField + 1; row++)
  {
    TableRow tableRow = new TableRow(this);  
    tableRow.setLayoutParams(new LayoutParams((blockDimension + 2 * blockPadding) * 
        numberOfColumnsInMineField, blockDimension + 2 * blockPadding));

    for (int column = 1; column < numberOfColumnsInMineField + 1; column++)
    {
      blocks[row][column].setLayoutParams(new LayoutParams(  
          blockDimension + 2 * blockPadding,  
          blockDimension + 2 * blockPadding)); 
      blocks[row][column].setPadding(blockPadding, blockPadding, 
             blockPadding, blockPadding);
      tableRow.addView(blocks[row][column]);
    }
    mineField.addView(tableRow,new TableLayout.LayoutParams(  
        (blockDimension + 2 * blockPadding) * numberOfColumnsInMineField, 
         blockDimension + 2 * blockPadding));  
  }
}

Set Minesweeper icon

Changing the application icon (this icon is displayed at the Home screen and the Launcher window, commonly called Launcher icon) is simple. It is simple because there is a default icon already supplied with the project, named icon.png in the res/drawable folder. Change/Update/Replace the icon.png provided with the project and the change will be reflected in the Launcher window. For more details about icon design for Android, read Icon Design Guidelines.

Minesweeper icon in Launcher Window

Images for multiple resolutions

As we all know, Android supports and runs on a wide range of devices. Devices may differ in screen size, aspect ratio, resolution, density, and pixel support. In order to support all these devices, we have to customize images according to each device. Now, this is really not possible. The recommended approach by Google is to have a separate set of images for three generalized screen densities, namely low DPI, medium DPI, and high DPI. Images should be copied to the res/drawable-hdpi, res/drawable-mdpi, res/drawable-ldpi folders for the respective densities. If we want Android to take care of the image adjustments (which is not always the best option), then we should just create a single set of images (we have used this approach in Minesweeper) and should copy them to the res/drawable-nodpi folder. For more information, read Supporting Multiple Screens.

Measure time

When you are developing a game, the most important aspect is keeping track of time. Using java.util.Timer or java.util.TimerTask is a standard approach for time keeping scenarios in Java world. The only thing that bothers here is that this way we create a new thread. This is not what we may be looking for, in some cases. Android has a better solution for this scenario. We can use the android.os.Handler class for our purpose.

Handler in place of timer

A handler can be used in two ways, either by sending a message to the handler and performing specific actions when the message is received, or by scheduling a Runnable object with the handler. We have used the second approach in Minesweeper. The benefit of using a handler is that it is associated with the thread/message queue of the creator thread. Read more about handler here. The code for implementing a handler is similar to:

private Handler timer = new Handler();
private int secondsPassed = 0;

public void startTimer()
{
  if (secondsPassed == 0) 
  {
    timer.removeCallbacks(updateTimeElasped);
    // tell timer to run call back after 1 second
    timer.postDelayed(updateTimeElasped, 1000);
  }
}

public void stopTimer()
{
  // disable call backs
  timer.removeCallbacks(updateTimeElasped);
}

// timer call back when timer is ticked
private Runnable updateTimeElasped = new Runnable()
{
  public void run()
  {
    long currentMilliseconds = System.currentTimeMillis();
    ++secondsPassed;
    txtTimer.setText(Integer.toString(secondsPassed));

    // add notification
    timer.postAtTime(this, currentMilliseconds);
    // notify to call back after 1 seconds
    // basically to remain in the timer loop
    timer.postDelayed(updateTimeElasped, 1000);
  }
};

Handle user actions

In Minesweeper, we have implemented listeners for the Click and Long Click events. The Click event serves the purpose of mouse left click and the Long Click event serves as the mouse right click event. We haven't implemented the Touch event for the same, we could have achieved the same functionality with Touch events also but that way it would be limited to Touch capable devices only. On the contrary, on Touch enabled devices, Click and Long Click events are generated by touching also, and on non-Touch enabled devices, these events can be generated by Trackball or Enter keys.

Understand mouse click and phone click events

Java offers functionality to implement mouse button Click/Press/Release, Mouse move/drag, Mouse enter/exit, and Scroller event support. In Android, there is no concept of mouse over (Enter/Exit) events and Scroller events. Android provides Click and Touch capabilities only. Click can be a Click or a Long Click. The Click event doesn't support drag functionality. The Touch event offers drag capabilities.

Understand phone click and phone touch events

Click events are generated when the user either touches the item (when in touch mode), or focuses upon the item with the navigation-keys or trackball and presses the suitable "enter" key or presses down on the trackball. Touch events are called when the user performs an action qualified as a touch event, including a press, a release, or any movement gesture on the screen (within the bounds of the item).

Understand phone click and phone long click events

Click events are generated when user either touches the item (when in touch mode), or focuses upon the item with the navigation-keys or trackball and presses the suitable "enter" key or presses down on the trackball. Long Click events are generated when the user either touches and holds the item (when in touch mode), or focuses upon the item with the navigation-keys or trackball and presses and holds the suitable "enter" key or presses and holds down on the trackball (for one second).

Left-Right button simulation

Android doesn't support the middle button click event; in order to implement this functionality, we have simply used the Long Click event. If Long Click is received on an open block with a number on it, then we trigger the related functionality. The code for this part looks like:

blocks[row][column].setOnLongClickListener(new OnLongClickListener()
{
  public boolean onLongClick(View view)
  {
    // simulate a left-right (middle) click
    // if it is a long click on an opened mine then
    // open all surrounding blocks
    if (!blocks[currentRow][currentColumn].isCovered() 
        && (blocks[currentRow][currentColumn].getNumberOfMinesInSorrounding() > 0) && !isGameOver)
    {
      int nearbyFlaggedBlocks = 0;
      for (int previousRow = -1; previousRow < 2; previousRow++)
      {
        for (int previousColumn = -1; previousColumn < 2; previousColumn++)
        {
          if (blocks[currentRow + previousRow][currentColumn + previousColumn].isFlagged())
          {
            nearbyFlaggedBlocks++;
          }
        }
      }

      // if flagged block count is equal to nearby mine count
      // then open nearby blocks
      if (nearbyFlaggedBlocks == blocks[currentRow][currentColumn].getNumberOfMinesInSorrounding())
      {
        for (int previousRow = -1; previousRow < 2; previousRow++)
        {
          for (int previousColumn = -1; previousColumn < 2; previousColumn++)
          {
            // don't open flagged blocks
            if (!blocks[currentRow + previousRow][currentColumn + previousColumn].isFlagged())
            {
              // open blocks till we get numbered block
              rippleUncover(currentRow + previousRow, currentColumn + previousColumn);

              // check status of game
              ...
            }
          }
        }
      }

      // as we no longer want to judge this gesture so return
      // not returning from here will actually trigger other action
      // which can be marking as a flag or question mark or blank
      return true;
    }

    // if clicked block is enabled, clickable or flagged
    ...
    }

    return true;
  }
});

Events for disabled buttons

If a button is disabled we can't receive events for that button. In order to overcome this limitation, we mark the button as disabled and change its background. In reality, the button is never disabled, it always remains enabled. This way the button is still enabled but pretends to be disabled and thus can accept events.

Decide the strategy

Let's concentrate on the most important aspect of the game, implementing it. In this section, we will talk about some of the features discussed in the About the game section. We will talk about almost each one of them one by one (one of them is already discussed above in the Left-Right button simulation section).

Complete cycle

The most important part of the Minesweeper game is handling user actions. It starts from waiting and receiving user inputs and processing them appropriately. I believe that in place of explaining it in words, it would be better if I can present a pictorial representation for the same. After all it is rightly said, a picture speaks louder than words. The whole cycle can be summarized in this flowchart:

Start handle with first click

As we already described, the timer should start with the first click (opening of first block) and not with the New Game button click. This is really necessary to keep proper control over time. In order to do this, we just create a boolean variable and as soon as we receive the Click event, we check that variable and start the Handler and then flip the value of the variable. The code for this part looks like:

private boolean isTimerStarted; // check if timer already started or not

blocks[row][column].setOnClickListener(new OnClickListener()
{
  @Override
  public void onClick(View view)
  {
    // start timer on first click
    if (!isTimerStarted)
    {
      startTimer();
      isTimerStarted = true;
    }
    ...
  }
});

No mines on first click

The user should never get a mine at the first click as it saves the user from guessing the first block. In order to implement this feature, we set mines after the first click. We set mines in blocks excluding the block that the user just clicked. Mines are planted in blocks randomly (by generating random row and column numbers). After planting mines, we update the nearby mine count for all blocks. The code for this part looks like:

private boolean areMinesSet; // check if mines are planted in blocks

blocks[row][column].setOnClickListener(new OnClickListener()
{
  @Override
  public void onClick(View view)
  {
    ...
    // set mines on first click
    if (!areMinesSet)
    {
      areMinesSet = true;
      setMines(currentRow, currentColumn);
    }
  }
});

private void setMines(int currentRow, int currentColumn)
{
  // set mines excluding the location where user clicked
  Random rand = new Random();
  int mineRow, mineColumn;

  for (int row = 0; row < totalNumberOfMines; row++)
  {
    mineRow = rand.nextInt(numberOfColumnsInMineField);
    mineColumn = rand.nextInt(numberOfRowsInMineField);
    if ((mineRow + 1 != currentColumn) || (mineColumn + 1 != currentRow))
    {
      if (blocks[mineColumn + 1][mineRow + 1].hasMine())
      {
        row--; // mine is already there, don't repeat for same block
      }
      // plant mine at this location
      blocks[mineColumn + 1][mineRow + 1].plantMine();
    }
    // exclude the user clicked location
    else
    {
      row--;
    }
  }

  int nearByMineCount;

  // count number of mines in surrounding blocks 
  ...
}

Ripple effect for opening blocks

When a user opens the block, the user should get some hint about the next step. The user cannot make a guess or decision about the next step if the opened block is empty/blank. In order to avoid this situation, we open nearby blocks and keep opening them recursively till we receive a block with the number beneath. This creates a ripple effect. The code for recursive uncovering of blocks (ripple effect) looks like:

private void rippleUncover(int rowClicked, int columnClicked)
{
  // don't open flagged or mined rows
  if (blocks[rowClicked][columnClicked].hasMine() || 
         blocks[rowClicked][columnClicked].isFlagged())
  {
    return;
  }

  // open clicked block
  blocks[rowClicked][columnClicked].OpenBlock();

  // if clicked block have nearby mines then don't open further
  if (blocks[rowClicked][columnClicked].getNumberOfMinesInSorrounding() != 0 )
  {
    return;
  }

  // open next 3 rows and 3 columns recursively
  for (int row = 0; row < 3; row++)
  {
    for (int column = 0; column < 3; column++)
    {
      // check all the above checked conditions
      // if met then open subsequent blocks
      if (blocks[rowClicked + row - 1][columnClicked + column - 1].isCovered()
          && (rowClicked + row - 1 > 0) && (columnClicked + column - 1 > 0)
          && (rowClicked + row - 1 < numberOfRowsInMineField + 1) 
          && (columnClicked + column - 1 < numberOfColumnsInMineField + 1))
      {
        rippleUncover(rowClicked + row - 1, columnClicked + column - 1 );
      } 
    }
  }
  return;
}

Blank block to flagged to question mark to blank

We discussed one more aspect about the game, marking blocks as Flagged, Question Mark, or clearing the mark again. The feature is straightforward to implement. When we receive a Long Click event and it is not a Left-Right click, we check the current status of the block. If the block is Blank we mark it as Flagged (mines inside), if it is flagged we can mark it as Question (doubt about presence of mine), and if it is marked as Question we can clear the mark. I hope anyone can figure out the fact that we just need a few conditional statements:

blocks[row][column].setOnLongClickListener(new OnLongClickListener()
{
  public boolean onLongClick(View view)
  {
    // simulate a left-right (middle) click
    // if it is a long click on an opened mine then
    // open all surrounding blocks
    ...

    // if clicked block is enabled, clickable or flagged
    if (blocks[currentRow][currentColumn].isClickable() && 
        (blocks[currentRow][currentColumn].isEnabled() || 
         blocks[currentRow][currentColumn].isFlagged()))
    {

      // for long clicks set:
      // 1. empty blocks to flagged
      // 2. flagged to question mark
      // 3. question mark to blank

      // case 1. set blank block to flagged
      if (!blocks[currentRow][currentColumn].isFlagged() && 
           !blocks[currentRow][currentColumn].isQuestionMarked())
      {
        blocks[currentRow][currentColumn].setBlockAsDisabled(false);
        blocks[currentRow][currentColumn].setFlagIcon(true);
        blocks[currentRow][currentColumn].setFlagged(true);
        minesToFind--; //reduce mine count
        updateMineCountDisplay();
      }
      // case 2. set flagged to question mark
      else if (!blocks[currentRow][currentColumn].isQuestionMarked())
      {
        blocks[currentRow][currentColumn].setBlockAsDisabled(true);
        blocks[currentRow][currentColumn].setQuestionMarkIcon(true);
        blocks[currentRow][currentColumn].setFlagged(false);
        blocks[currentRow][currentColumn].setQuestionMarked(true);
        minesToFind++; // increase mine count
        updateMineCountDisplay();
      }
      // case 3. change to blank square
      else
      {
        blocks[currentRow][currentColumn].setBlockAsDisabled(true);
        blocks[currentRow][currentColumn].clearAllIcons();
        blocks[currentRow][currentColumn].setQuestionMarked(false);
        // if it is flagged then increment mine count
        if (blocks[currentRow][currentColumn].isFlagged())
        {
          minesToFind++; // increase mine count
          updateMineCountDisplay();
        }
        // remove flagged status
        blocks[currentRow][currentColumn].setFlagged(false);
      }
      
      updateMineCountDisplay(); // update mine display
    }

    return true;
  }
});

Check game win/loss at each step

Yes, this step is very important; we need to check the status of the game after each click. This is essential to make sure we don't miss any click or any block. We lose the game if we click on a block with a mine underneath. We win the game if all the blocks marked with mines are flagged. The code for this part looks like:

// check status of the game at each step
if (blocks[currentRow + previousRow][currentColumn + previousColumn].hasMine())
{
  // oops game over
  finishGame(currentRow + previousRow, currentColumn + previousColumn);
}

// did we win the game
if (checkGameWin())
{
  // mark game as win
  winGame();
}

private boolean checkGameWin()
{
  for (int row = 1; row < numberOfRowsInMineField + 1; row++)
  {
    for (int column = 1; column < numberOfColumnsInMineField + 1; column++)
    {
      if (!blocks[row][column].hasMine() && blocks[row][column].isCovered())
      {
        return false;
      }
    }
  }
  return true;
}

Testing/How to play

Testing/Playing the game is straightforward and resembles the way we play it on Windows. Some key points to mention are:

  • Click the smiley icon to start a new game.
  • Click or Touch the block to open it. This is same as left click on Windows.
  • Click and hold (for one second) the block to mark it as Flagged, Question Mark, or clear all marks. This is same as right click on Windows.
  • Click and hold a numbered block to open all covered blocks (if all mines are already flagged). This is same as middle click on Windows.
  • There is no icon for flagged blocks. Flagged blocks are marked with an F symbol.
  • Go ahead, play it, and give your feedback.

Summary

Explaining a full flexed game in the boundaries of an article is not really very easy. I have tried my best to explain the working of Minesweeper and how to handle some of the important parts of the game. The full source code for the game is attached. We kept ourselves limited to Beginner mode of the game but implementing Intermediate, Expert, and Custom modes is also very easy. It just needs a few modifications to the code. Feel free to use it and extend it. Please provide your feedback and suggestions.

Resources

The images used for creating the GUI for Minesweeper belong to the respective owners. The sources for the images are:

History

  • Sep 28, 2010: Initial draft.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

The Manoj Kumar
Team Leader
India India

Manoj Kumar is a Humble Programmer and a trainer having dozens of trainings, publications and articles to his wallet.

His programming adventures began with Basic at an age of 11. Being a mathematician at core, soon he started looking for some more and moved to assembly language, later to C, C++, VC++ and finally to .Net.

He started his professional career as a VC++ 6 trainer, moved to embedded systems and device driver development, then to complex biological systems and finally moved to pure application development.

He has been teaching and training people for more than 12 years on a wide range of topics including Mathematics, Algorithms, Data Structures, C, C++, VC++, MFC, C#, Design Patterns and now a days he is working extensively with Visual Studio and .Net framework which includes VSX, WPF, Silverlight, WCF, WF, XAML and RIAs.

Awards:

  • Ideablade RIA Service Challenge winner
  • Visual Studio 2010 Extension Contest winner (Best Use of Editor)

Visit my website and blog or drop me a mail.

Feel free to connect with me on Linkedin.

Follow on   Twitter

Comments and Discussions

 
Questionandroid minesweeper errors Pinmemberamogh_87819-Jul-12 23:50 
AnswerRe: android minesweeper errors PinmemberThe Manoj Kumar2-Aug-12 12:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 29 Sep 2010
Article Copyright 2010 by The Manoj Kumar
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid