function mineSweeper(varargin);

%

%The game is probably familiar to everyone, but the game has a slight

%twist. You may play the game such that there may be up to 7 mines per

%square instead of just one.

%

%Left click:

% - If the square is not a mine then a number will appear counting the

% number of mines in the uncovered surrounding squares.

%

% - If the square is a mine, the game is over.

%

% - If the square was already uncovered, and all the flags have been set,

% then it will uncover the surrounding squares - however, if any of

% those are actually a mine, the game is over.

%

% - If the square was flagged, one flag will be removed.

%

%Right click:

% - Set and cycle through the flags, each right click will add another

% flag.

%

% - To clear all the flags from the square, continue to right click and

% cycle back to the reset position. (Or, left click down to 0 flags.)

%

% - If the square was uncovered, nothing will happen.

%

%% Initial set up

%Change MATLAB's rand state

rand('twister',sum(100*clock));

%Number of board columns

gameBoard.gameCols = 30;

%Number of board rows

gameBoard.gameRows = 16;

gameBoard.numberBoxes = gameBoard.gameRows * gameBoard.gameCols;

%Maximum number of mines per square, default value of 1 if not set

gameBoard.maxMines = 1;

if nargin == 1;

gameBoard.maxMines = varargin{1};

if ischar(gameBoard.maxMines);%Surprise me

gameBoard.maxMines = ceil(7 * rand);

end;

end;

%Initial mine distribution matrix, each column represents a maxMines

%value and each row represents the number of mines to be placed on the

%board

mineDistribution = [...

100,60,50,40,40,35,30;...

0, 40,30,30,24,25,24;...

0, 0, 20,20,18,16,18;...

0, 0, 0, 10,12,11,12;...

0, 0, 0, 0, 6, 8, 8;...

0, 0, 0, 0, 0, 5, 5;...

0, 0, 0, 0, 0, 0, 3];

%Initial distribution vector for the case we will play

mineDistribution = mineDistribution(:,gameBoard.maxMines);

%Will be used later to determine the number of flags left to mark

flagsLeft = 0 * mineDistribution;

%gameBoard will hold the user clicks and the state of the board:

%The first page:

%0 means covered, 1 means uncovered, -X means X flags set

%The second page:

%The handles to the text representing the flags set

%The third page:

%The handles to the boxes drawn

%The fourth page:

%The mine values

temp = zeros(gameBoard.gameRows,gameBoard.gameCols);

gameBoard.mineValues = temp;

gameBoard.boxHandles = temp;

gameBoard.flagHandle = temp;

gameBoard.userClicks = temp;

%% Create game board display

%Create figure

gameHandle = figure;

figColor = [0.80 0.80 0.80];

set(gameHandle,'Units','Normalized',...

'Position',[0.15 0.20 0.70 0.50],...

'Color',figColor,...

'Name','Mine Sweeper',...

'NumberTitle','Off',...

'Resize','Off');

%Add game menu

set(gameHandle,'MenuBar','None');

menuHandle = uimenu('Label','Start New Game');

uimenu(menuHandle,'Label','At most 1 mine,',...

'Callback','closereq;mineSweeper(1);','Accelerator','1');

uimenu(menuHandle,'Label','At most 2 mines,',...

'Callback','closereq;mineSweeper(2);','Accelerator','2');

uimenu(menuHandle,'Label','At most 3 mines,',...

'Callback','closereq;mineSweeper(3);','Accelerator','3');

uimenu(menuHandle,'Label','At most 4 mines,',...

'Callback','closereq;mineSweeper(4);','Accelerator','4');

uimenu(menuHandle,'Label','At most 5 mines,',...

'Callback','closereq;mineSweeper(5);','Accelerator','5');

uimenu(menuHandle,'Label','At most 6 mines,',...

'Callback','closereq;mineSweeper(6);','Accelerator','6');

uimenu(menuHandle,'Label','At most 7 mines,',...

'Callback','closereq;mineSweeper(7);','Accelerator','7');

uimenu(menuHandle,'Label','Surprise me,',...

'Callback','closereq;mineSweeper(''s'');','Accelerator','S');

uimenu(menuHandle,'Label','End game,',...

'Callback','closereq;','Accelerator','Q');

%Add box to count number of flags remaining

flagHandle = uicontrol(gameHandle,'Style','text');

set(flagHandle,'Units','Normalized',...

'Position',[0.02 0.55 0.14 0.40],...

'BackgroundColor',figColor);

%Add message dialog box

talkHandle = uicontrol(gameHandle,'Style','text');

set(talkHandle,'Units','Normalized',...

'Position',[0.02 0.35 0.14 0.10],...

'BackgroundColor',figColor,...

'String','Good luck!','FontSize',16);

%Add game board axes

axisHandle = axes;

set(axisHandle,'Units','Normalized',...

'Position',[0.18 0.05 0.80 0.90],...

'XLim',[0 gameBoard.gameCols],'YLim',[0 gameBoard.gameRows],...

'YDir','Reverse',...

'XTick',[],'YTick',[],'Box','On');

%% Create game board values

%The indices into the board which contain a mine will be randomly

%selected using randperm

gameBoardIndex = randperm(gameBoard.numberBoxes);

mineCumulative = cumsum(mineDistribution);

%Set the first set as containing one mine

gameBoard.mineValues(gameBoardIndex(1:mineCumulative)) = -1;

%Loop through remaining random spots for next mine increment

for dMine = 2:gameBoard.maxMines;

gameBoard.mineValues(gameBoardIndex...

(mineCumulative(dMine - 1) + 1:mineCumulative(dMine))) = -dMine;

end;

%For each non-mine position, need to count the number of mines around,

%start by computing all the rows and columns

[dIndexRow,dIndexCol] = ind2sub...

([gameBoard.gameRows gameBoard.gameCols],...

1:gameBoard.numberBoxes);

dIndexRow = repmat(dIndexRow',[1 3]);

dIndexCol = repmat(dIndexCol',[1 3]);

%The rows and columns that surround are +/- 1 from the middle

surroundingPoints = repmat([-1 0 1],[size(dIndexRow,1),1]);

dIndexRow = dIndexRow + surroundingPoints;

dIndexCol = dIndexCol + surroundingPoints;

for dIndex = 1:gameBoard.numberBoxes;

if gameBoard.mineValues(dIndex) < 0;

%Nothing to count, index is flagged

continue;

end;

%Find the surrounding squares

surroundingIndices = surroundingSquares(...

dIndexRow(dIndex,:),gameBoard.gameRows,...

dIndexCol(dIndex,:),gameBoard.gameCols);

%Find these gameBoard values

temp = gameBoard.mineValues(surroundingIndices);

%Only count the flags (negative values)

k = temp < 0;

%Sum the number of flags surrounding this index

gameBoard.mineValues(dIndex) = abs(sum(sum(temp .* k)));

end;

%Display the game board in the background of the figure

gameBoard.xOffset = -0.75;%Offset from edge of row/col

gameBoard.yOffset = -0.50;%Offset from edge of row/col

[x,y] = meshgrid(...

1 + gameBoard.xOffset:1:gameBoard.gameCols + gameBoard.xOffset,...

1 + gameBoard.yOffset:1:gameBoard.gameRows + gameBoard.yOffset);

%Display text

textHandles = text(x(:),y(:),int2str(gameBoard.mineValues(:)),...

'FontSize',8);

set(textHandles,'Color',[0.00 0.00 1.00]);

%The 0 values should not display, and mines are to be colored red

k = gameBoard.mineValues(:);

set(textHandles(k == 0),'Visible','Off');

set(textHandles(k < 0),'Color',[1.00 0.00 0.00]);

%Now, cover the number of mines (otherwise the game would be pointless)

gameBoard.uncoveredColor = [0.40 0.40 0.40];%dark gray covering square

gameBoard.markedColor = [1.00 1.00 0.50];%yellow indicating flagged

%For each grid point, create a patch of a box covering the area

for dRow = 0:gameBoard.gameRows - 1;

for dCol = 0:gameBoard.gameCols - 1;

gameBoard.boxHandles(dRow + 1,dCol + 1) = patch(...

[dCol, dCol, dCol + 1, dCol + 1, dCol],...

[dRow, dRow + 1, dRow + 1, dRow, dRow],...

gameBoard.uncoveredColor);

set(gameBoard.boxHandles(dRow + 1,dCol + 1),...

'EdgeColor',[0.00 0.00 0.00]);

end;

end;

%Setup the text containing the flag information

flagsLeftString = char(zeros(gameBoard.maxMines,6));

for dMine = 1:gameBoard.maxMines;

switch dMine;

case 1;

flagsLeftString(dMine,:) = ' I - ';

case 2;

flagsLeftString(dMine,:) = 'II - ';

case 3;

flagsLeftString(dMine,:) = 'III - ';

case 4;

flagsLeftString(dMine,:) = 'IV - ';

case 5;

flagsLeftString(dMine,:) = ' V - ';

case 6;

flagsLeftString(dMine,:) = 'VI - ';

case 7;

flagsLeftString(dMine,:) = 'VII - ';

end;

end;

%% Play game

gameContinues = true;

gameWon = false;

while gameContinues;

%Update mine count

for dMine = 1:gameBoard.maxMines;

flagsLeft(dMine,1) = mineDistribution(dMine) -...

sum(sum(gameBoard.userClicks == -dMine));

end;

set(flagHandle,...

'String',[flagsLeftString,...

int2str(flagsLeft(1:gameBoard.maxMines))],'FontSize',12);

%Determine if game has been won

k = gameBoard.mineValues < 0;

if all(all(gameBoard.mineValues(k) == gameBoard.userClicks(k))) &&...

all(all(gameBoard.userClicks(~k) == 1));

%All flags set and match the mine value, and all boxes uncovered

gameContinues = false;

gameWon = true;

continue;

end;

%Get user click

try;

m = waitforbuttonpress;

if m == 1;

continue;

end;

catch;

%User closed game

return;

end;

%Return mouse position

thisPoint = get(axisHandle,'CurrentPoint');

thisRow = floor(thisPoint(1,2)) + 1;

thisCol = floor(thisPoint(1,1)) + 1;

%Return mouse button used

thisButton = get(gameHandle,'SelectionType');

if thisRow <= 0 || thisRow > gameBoard.gameRows ||...

thisCol <= 0 || thisCol > gameBoard.gameCols;

%Clicked outside the board, try again

continue;

end;

switch thisButton;

case 'normal';%Left button

%Square has a flag on it, left click void

if gameBoard.userClicks(thisRow,thisCol) < 0;

gameBoard.userClicks(thisRow,thisCol) =...

gameBoard.userClicks(thisRow,thisCol) + 1;

%Place/update flag on board

gameBoard = setFlag(gameBoard,thisRow,thisCol);

continue;

end;

%Square was not clear, but is a mine

if gameBoard.mineValues(thisRow,thisCol) < 0 &&...

gameBoard.userClicks(thisRow,thisCol) == 0;

%Oops, stepped on a mine

set(gameBoard.boxHandles(thisRow,thisCol),...

'FaceColor','None');

gameContinues = false;

continue;

end;

%Square was not clear, not a mine

if gameBoard.mineValues(thisRow,thisCol) >= 0 &&...

gameBoard.userClicks(thisRow,thisCol) == 0;

set(gameBoard.boxHandles(thisRow,thisCol),...

'FaceColor','None');

gameBoard.userClicks(thisRow,thisCol) = 1;

newlyUncovered = sub2ind...

([gameBoard.gameRows gameBoard.gameCols],...

thisRow,thisCol);

end;

%Square clear, number of flags set, clear surrounding area

surroundingIndices = surroundingSquares(...

thisRow,gameBoard.gameRows,...

thisCol,gameBoard.gameCols);

temp = gameBoard.userClicks(surroundingIndices);

k = temp < 0;

flagsSet = abs(sum(sum(temp .* k)));

if flagsSet == gameBoard.mineValues(thisRow,thisCol) &&...

flagsSet > 0 &&...

gameBoard.userClicks(thisRow,thisCol) == 1;

%Find all surrounding points that aren't flagged

k = gameBoard.userClicks(surroundingIndices) < 0;

set(gameBoard.boxHandles(surroundingIndices(~k)),...

'FaceColor','None');

gameBoard.userClicks(surroundingIndices(~k)) = 1;

newlyUncovered = surroundingIndices(~k);

if any(gameBoard.mineValues(surroundingIndices(~k)) < 0);

%Oops, set a flag incorrectly and uncovered a mine

gameContinues = false;

continue;

end;

end;

%Call a recursive function to uncover all the connected 0

%values, this will happen if the square was a 0 or if the

%flags were set and a 0 was uncovered

[newlyUncovered,gameBoard] = uncoverZeros...

(newlyUncovered,gameBoard,[]);

case 'alt';%Right button

%Box already cleared, right click void

if gameBoard.userClicks(thisRow,thisCol) == 1;

continue;

end;

%Mark flag, subtract 1 to mean another flag added

gameBoard.userClicks(thisRow,thisCol) =...

gameBoard.userClicks(thisRow,thisCol) - 1;

%Place/update flag on board

gameBoard = setFlag(gameBoard,thisRow,thisCol);

end;

end;

%Game over, display end game message

if gameWon;

newColor = [0.30 0.30 1.00];

set(talkHandle,'String','Well Done!');

else;

newColor = [1.00 0.50 0.00];

set(talkHandle,'String',' O O P S! ');

%Display flags set incorrectly

k = gameBoard.userClicks < 0 &...

gameBoard.mineValues ~= gameBoard.userClicks;

set(gameBoard.boxHandles(k),'FaceColor',[1.00 0.00 0.00]);

%Display mines not flagged

k = gameBoard.mineValues < 0 & gameBoard.userClicks >= 0;

set(gameBoard.boxHandles(k),'FaceColor','None');

end;

set(gameHandle,'Color',newColor);

set(flagHandle,'BackgroundColor',newColor);

set(talkHandle,'BackgroundColor',newColor);

end

%% Set Flags

%Function to place/update the correct flag on the board

function gameBoard = setFlag(gameBoard,thisRow,thisCol);

%Use mod arithmetic only

temp = mod(-gameBoard.userClicks(thisRow,thisCol),...

gameBoard.maxMines + 1);

gameBoard.userClicks(thisRow,thisCol) = -temp;

%If text has previously been set...

if gameBoard.flagHandle(thisRow,thisCol) ~= 0;

%...Clear flag string

set(gameBoard.flagHandle(thisRow,thisCol),'String','');

end;

%Set square color to indicate it is marked

thisColor = gameBoard.markedColor;

switch temp;%Number of flags set

case 0;%Reset square to uncovered state

flagString = '';

thisColor = gameBoard.uncoveredColor;

case 1;

flagString = ' I ';

case 2;

flagString = ' I I ';

case 3;

flagString = 'I I I';

case 4;

flagString = 'I V ';

case 5;

flagString = ' V ';

case 6;

flagString = 'V I ';

case 7;

flagString = 'VI I';

end;

%Display flags, set square color

gameBoard.flagHandle(thisRow,thisCol) =...

text(thisCol + gameBoard.xOffset,thisRow + gameBoard.yOffset,...

flagString,'FontName','Arial','FontSize',6);

set(gameBoard.boxHandles(thisRow,thisCol),...

'FaceColor',thisColor);

end

%% Surrounding Squares Indices

%Return the indices surrounding the current square(s)

function surroundingIndices = surroundingSquares(rowIndices,gameRows,...

colIndices,gameCols);

if length(rowIndices) == 1;

rowIndices = rowIndices + [-1 0 1];

end;

if length(colIndices) == 1;

colIndices = colIndices + [-1 0 1];

end;

%For one index at a time, determine the rows and columns that

%surround the index, removing those off the game board.

m = ~(rowIndices <= 0 | rowIndices > gameRows);

n = ~(colIndices <= 0 | colIndices > gameCols);

%Compute the "all-possible" pairings of rows and columns

[surroundingRows,surroundingCols] = meshgrid...

(rowIndices(m),colIndices(n));

%Easier to use indices

surroundingIndices = sub2ind...

([gameRows gameCols],surroundingRows,surroundingCols);

end

%% Uncover 0 Squares

%Recursive function to uncover squares with a 0 value

function [newlyUncovered,gameBoard] =...

uncoverZeros(newlyUncovered,gameBoard,alreadyCleared);

%Game size

[gameRows,gameCols] = size(gameBoard.mineValues);

%Keep only the new squares that are a 0

newlyUncovered = newlyUncovered...

(gameBoard.mineValues(newlyUncovered) == 0);

while ~isempty(newlyUncovered);

%Take the first index

[rowIndex,colIndex] = ind2sub...

([gameRows gameCols],newlyUncovered(1));

%Return all points the surround this square

temp = surroundingSquares(rowIndex,gameRows,colIndex,gameCols);

%Add to a list of squares already dealt with to avoid an infinite

%recursion

alreadyCleared = unique([alreadyCleared;newlyUncovered(1)]);

%Remove squares marked as already clear

temp = setdiff(temp(:),alreadyCleared);

%Remove point currently being cleared

newlyUncovered(1) = [];

%Add the surrounding squares to this list

newlyUncovered = unique([newlyUncovered;temp(:)]);

%If the user set a 0 as a flag don't uncover

temp = temp(gameBoard.userClicks(temp) >= 0);

%Mark these squares as cleared

gameBoard.userClicks(temp) = 1;

set(gameBoard.boxHandles(temp),'FaceColor','None');

%Recursive call, will continue until the trail of 0s has been

%uncovered

[newlyUncovered,gameBoard] = uncoverZeros...

(newlyUncovered,gameBoard,alreadyCleared);

end;

end