Introduction
Have you ever played the Minesweeper game? I didn't start playing until a few weeks ago. I found the beginner and intermediate levels not too difficult, but the expert level was a real pain. So I decided to write an app that would solve the Minesweeper game for me.
Background
While playing the Minesweeper game, I found on the expert level I was often making silly mistakes. Usually after finding 40 or 50 mines I would get mentally tired and mark something as a bomb or click somewhere that was a bomb. Anyway, I found it really difficult to get down to the last few bombs. Then when I finally would be able to get there, I would always have to guess. So I figured I would write a program that could solve the Minesweeper game for me and at least it wouldn't make the same mistakes I was making.
The Game
If you have never played the Minesweeper game, here is a quick intro to it. Depending on the level you are on, you have a certain number of squares. The goal is to find all the mines and mark them as mines without clicking on one. When you start the game you have no idea where the mines are, so you randomly left click a few squares. As you click you start to see numbers. The numbers relate to the number of mines that surround the number on the eight touching sides. When you think you know where a mine is you right click to flag it as a mine. You try to find all the mines as quickly as possible. The game is timed so you try to get the best time you can to get on the best times list.
The Problem
Being a human, I make silly mistakes when I get tired or just don't pay close enough attention. I just could not beat the expert level. So instead of playing more and more of the Minesweeper game, I wrote a program that could solve it for me.
The Solution
First I would like to note that I used these two articles to help out with the Windows messaging and screen print part of the program.
First I make sure the Minesweeper program is running. Then I get the handle to the program and force it to the forefront and put it in the upper left hand corner of the screen. This is important so I have an idea of where I need to be sending mouse clicks. Base off the screen width of the Minesweeper game I make an assumption on which level the user is playing at. Next I use the screen print of the Minesweeper game to figure out which boxes I have found. I use the colors found in the boxes to help me figure out which number I have clicked, etc. Next I wrote some code to help me figure out what all the surrounding boxes are to the current box I am looking at. Then I pass all this info into TheBrain
method. I was surprised to find how simple the brain method ended up being:
private Boolean TheBrain(SquareType currPos, SquareType top,
SquareType topRight, SquareType right, SquareType bottomRight,
SquareType bottom, SquareType bottomLeft, SquareType left,
SquareType topLeft, int x, int y)
{
Boolean ret = true;
int bombCnt = GetCounts(SquareType.Bomb,top,topRight,right,
bottomRight,bottom,bottomLeft,left,topLeft);
int unKnownsCnt = GetCounts(SquareType.Unknown, top, topRight,
right, bottomRight, bottom, bottomLeft, left, topLeft);
if (unKnownsCnt == 0)
{
TheSquaresPos[x, y].Complete = true;
return ret;
}
int maxBombs = 0;
String errorNumber = String.Empty;
switch (currPos)
{
case SquareType.One :
maxBombs = 1;
errorNumber = "One";
break;
case SquareType.Two :
maxBombs = 2;
errorNumber = "Two";
break;
case SquareType.Three :
maxBombs = 3;
errorNumber = "Three";
break;
case SquareType.Four :
maxBombs = 4;
errorNumber = "Four";
break;
case SquareType.Five :
maxBombs = 5;
errorNumber = "Five";
break;
case SquareType.Six :
maxBombs = 6;
errorNumber = "Six";
break;
case SquareType.Seven :
maxBombs = 7;
errorNumber = "Seven";
break;
}
if (bombCnt == maxBombs)
{
ProcessUnknowns(top, topRight, right, bottomRight, bottom,
bottomLeft, left, topLeft, x, y);
}
else if (maxBombs >= 7 && bombCnt == 7 && unKnownsCnt == (maxBombs - 7))
{
RightClickUnknowns(top, topRight, right, bottomRight, bottom,
bottomLeft, left, topLeft, x, y);
}
else if (maxBombs >= 6 && bombCnt == 6 && unKnownsCnt == (maxBombs - 6))
{
RightClickUnknowns(top, topRight, right, bottomRight, bottom,
bottomLeft, left, topLeft, x, y);
}
else if (maxBombs >= 5 && bombCnt == 5 && unKnownsCnt == (maxBombs - 5))
{
RightClickUnknowns(top, topRight, right, bottomRight, bottom,
bottomLeft, left, topLeft, x, y);
}
else if (maxBombs >= 4 && bombCnt == 4 && unKnownsCnt == (maxBombs - 4))
{
RightClickUnknowns(top, topRight, right, bottomRight, bottom,
bottomLeft, left, topLeft, x, y);
}
else if (maxBombs >= 3 && bombCnt == 3 && unKnownsCnt == (maxBombs - 3))
{
RightClickUnknowns(top, topRight, right, bottomRight, bottom,
bottomLeft, left, topLeft, x, y);
}
else if (maxBombs >= 2 && bombCnt == 2 && unKnownsCnt == (maxBombs -2))
{
RightClickUnknowns(top, topRight, right, bottomRight, bottom,
bottomLeft, left, topLeft, x, y);
}
else if (bombCnt == 1 && unKnownsCnt == (maxBombs - 1))
{
RightClickUnknowns(top, topRight, right, bottomRight, bottom,
bottomLeft, left, topLeft, x, y);
}
else if (bombCnt == 0 && unKnownsCnt == maxBombs)
{
RightClickUnknowns(top, topRight, right, bottomRight, bottom,
bottomLeft, left, topLeft, x, y);
}
else if (bombCnt > maxBombs)
{
toolStripStatusLabel1.Text = "Error on "+errorNumber+" processing too many bombs!";
ret = false;
}
return ret;
}
Once the brain method made a decision on what should be done, I would either send right mouse clicks or left mouse clicks. When starting a game or when the program would get stuck, I would randomly pick the next box to click.
The Best Times from Minesweeper Solver
Note these times are legitimate. That is how long it took the program to solve the game. I found some interesting things that really sped up the time that it took the program to solve the game. First it appears the more RAM memory and CPU the Minesweeper solver has, the faster it will run. Second, I stopped reading all the pixels in the squares. I started to read only half of them. Third, I started to scan the board left to right on the first pass and then right to left on the second pass. This switching of the way I was scanning the board also helped with the speed in which the game could be solved.
Some Odds and Ends
I found that some computers and monitors would pick slightly different colors for the same colors. So I ended up putting those into the app.config file so they can be easily changed. I also found that screen resolution could affect the starting position of boxes. So when trying to get this to run on your PC, you might need to tweak the app.config file values a little before it will work. I have a check 1 button that will check on box in the grid. You can use this to figure out if you need to modify the app.config values.
Known Issues
For some reason, the Minesweeper solver program will mark a bomb twice which switches that square to a question mark. When the app randomly picks a square to click it is random, some intelligence could be programmed into it.
Conclusion
I had some time on my hands, although it only took me two days to write the core of this code. Still it was a fun exercise and helped me improve my programming skills. I found that the Minesweeper solver would solve the beginner level almost every time. It would solve the intermediate level some of the time. You guessed it. It had trouble with the expert level as well. It might solve the expert level once in a hundred tries.
History
- 15th June, 2007: Initial post