Advertisement
  1. Code
  2. Coding Fundamentals
  3. Game Development

Build a Minesweeper Game Within 200 Lines of Code

Scroll to top

In this tutorial we will learn how to plan and code a minesweeper game using AS3 and Flash. Can we make it in just 200 lines?! Let's see...


Final Result Preview

Let's take a look at the final result we will be working towards:

Because Flash Player doesn't support right-click, shift-click on the squares you think contain mines in order to mark them.


Minesweeper Rules

If you're a minesweeper player, then probably you know the game rules -- but just in case you're not:

Minesweeper game consists of a group of squares where each square represents a region. Each region either contains a mine or not. Your mission is to reveal (or "open") all the mine-free regions and mark the ones that contain mines. You open the region by left-clicking it, and mark a region by shift-clicking. The key for determining whether a certain region contains a mine or not is that for every 3x3 grid (9 regions) of squares, the number written on the region in the center represents the number of mines in the 8 other surrounding regions.

So, when starting your game, your first few clicks will depend on your luck to find mine-free regions, but once you open a fair amount of regions, you can use logic to find the regions that contain mines.

A good example for a minesweeper game, is the one that ships with Windows (any version); a simple search for [online minesweeper game] will also direct you to several versions, which all have the same concept. Otherwise, play the game from the final SWF file, embedded above.


Modeling Minesweeper the OOP Way

In a minesweeper game, if we want to think of the game from the OOP point of view, we can consider each region as an object. We will call this object a 'zone', and all of the game zones will be in a container object that we will call the 'board'.

For every zone object, there is a bunch of properties like:

  • state: whether it contains a mine or not.
  • revealed: if it's revealed, the user has opened the zone.
  • marked: whether the user has marked the zone as containing a mine.
  • zoneValue: the number written inside the zone representing the number of mines in the surrounding zones.
  • xcor: the horizontal position of the zone in the board.
  • ycor: the vertical position of the zone in the board.

And that's how we will build the game. there will be a Zone class linked with a zone movie clip in the library, and we will instantiate the amount of zones we need from our main class to make the game board.


Addressing the Right-Click Problem within Flash

For a regular minesweeper game -- a native one that's not in the browser -- you can have access to both mouse buttons, so that the user playing the game can open regions by left clicking and mark them by right clicking. If you were to publish the game as an AIR application, then you would have access to all of the mouse features including the right and middle mouse buttons, and you should do that for better user experience while playing the game.

But if you want to publish the game via Flash Player in the browser, then you don't have access to the right mouse button, since the right mouse button will open the context menu for the Flash Player; so we have to figure out a solution for this problem.

The solution that I came up with is through using the keyboard. I made it by detecting the shift key, and found that the easiest way to do that is by using the shiftKey property for the MouseEvent class. there are a bunch of other properties for some special keyboard keys like the Alt (for windows), Option (for mac), and the Ctrl (Command) key. I recommend that you check them out in the documentation help for the MouseEvent class if you don't already know them.


Contents of the FLA

Let's take a look at the FLA that we will be working with.

Contents of the flash fileContents of the flash fileContents of the flash file

The Flash file contains the graphical elements only for the game.

Note: you can start from scratch creating a new .fla file and making your own graphics. or you can start with the FLA included in the source download.

First: there are three objects on the stage:

  • gameover_mc: movie clip that contains the graphic for the Game Over screen.
  • playagain_btn: the Play Again button.
  • minesleft_txt: the textbox that displays the number of mines left.

In addition to that, the library contains another symbol called zone -- it's the movie clip representing the Zone object. This symbol will be linked to the Zone class that we will create later to add the zone functionality, and is set to be 40x40 pixels. this zone object consists of the following:

  • square rectangular shape.
  • block_mc: the graphic for the zone object before it's revealed (opened). It's just a 40x40 square with a bevel effect on it.
  • bomb_mc: a simple graphic to appear when the zone is marked as containing a mine.
  • num_txt: a text field to display the number of mines in the surrounding zones.
  • highlight_mc: a simple movieclip with a radial gradient for the hover effect.
Contents of the zone objectContents of the zone objectContents of the zone object

Section 1: Starting the Main Class

Create a new class and call it Main. save it as Main.as, link it to the Flash file as the document class and let's dive into the code.

Firstly: we will start the class by extending the Sprite class, importing the classes that we will need - in this case we will import flash.display.Sprite & flash.events.mouseEvent - and defining some variables as follows:

  • board: Sprite object containing the whole board that will contain all of the zone objects.
  • boardWidth: integer representing the number of zones across the board horizontally.
  • boardHeight: integer representing the number of zones across the board vertically.
  • numberOfMines: integer representing the number of zones that contain mines across all over the board.
  • zones: Array object that will hold a reference for all the zone objects within the board.

Then, inside the constructor, we will initialize those variables that we've defined with some values. Your class should look like this:

1
2
package  {
3
	import flash.display.Sprite;
4
	import flash.events.MouseEvent;
5
	public class minesweeper_game extends Sprite {
6
		public var zones:Array;
7
		public var board:Sprite;
8
		public var boardWidth:int;
9
		public var boardHeight:int;
10
		public var numberOfMines:int;
11
		public function Main() {			//constructor----

12
			zones = new Array();
13
			board = new Sprite();
14
			boardWidth = 15;
15
			boardHeight = 15;
16
			numberOfMines = 30;
17
		}	//end of constructor

18
	}	//end of class

19
}	//end of package

Next we need to perform these tasks as well for initializing the game:

  • removing the game over movieclip from the stage.
  • removing the play again buttons from the stage.
  • changing the value of the "mines left" counter to show the number of markers yet to place.
  • adding the board object to the bottom of the display list using the addChildAt() method.

And for the code to be organized as well as reusable, we will split the process of creating the game into three parts: creating the board and zones within the board, placing the mines, and placing the correct numbers on the free zones (not containing mines).

Each step from those three parts will be in a separate function; these functions are createBoard(), placeBombs(), and placeNumbers() respectively, as previously mentioned.

So make these functions and leave them empty for now, and call them in your constructor respectively within the same order.

Your Main class should look like this:

1
2
package  {
3
	import flash.display.Sprite;
4
	import flash.events.MouseEvent;
5
	public class minesweeper_game extends Sprite {
6
		public var zones:Array;
7
		public var board:Sprite;
8
		public var boardWidth:int;
9
		public var boardHeight:int;
10
		public var numberofMines:int;
11
		public function Main() {			//constructor----

12
			zones = new Array();
13
			board = new Sprite();
14
			boardWidth = 15;
15
			boardHeight = 15;
16
			numberOfMines = 30;
17
			gameover_btn.visible = false;
18
			again_btn.visible = false;
19
			minesleft_txt.text = String(numberofbombs);
20
			addChildAt(board, 0);
21
			createBoard();
22
			placeMines();
23
			placeNumbers();
24
		}	//end of constructor

25
		private function createBoard():void{
26
		
27
		}
28
		private function placeMines():void{
29
		
30
		}
31
		private function placeNumbers():void{
32
		
33
		}
34
	}	//end of class

35
}	//end of package

Now we will pause with this class, and start making the Zone class.


Section 2: Making the Zone Class

Now let's start making the functionality of each zone. First, in the FLA file, link the zone movie clip with the Zone class. You can do that by right-clicking the zone movie clip in the library and choosing Properties, then opening the advanced section if it's not already opened, checking 'Export for ActionScript' and in the class field typing 'Zone'.

Secondly while we are creating the Zone class, we will need to test our progress. So we will make an instance of the Zone object by adding these lines of code into the constructor function within our Main class file:

1
2
var testZone:Zone = new Zone();
3
addChild(testZone);
4
testZone.x = 150;
5
testZone.y = 150;

Thirdly create a new class and save it as Zone.as within the same folder as the FLA.

Then we will need to make our Zone class extend the MovieClip class as follows. We will also deal with some mouse events, so you should import this class as well.

1
2
package{
3
	import flash.display.MovieClip;
4
	import flash.events.MouseEvent;
5
	public class Zone extends MovieClip{
6
		public function Zone(){
7
			
8
		}	//end of constructor

9
	}	//end of class

10
}	//end of package

Now, if you test the movie you should see an instance on the stage:

Making a sample test zoneMaking a sample test zoneMaking a sample test zone

Next we will define the properties that we have discussed earlier when talking about the zone object.

These properties will be as follows:

  • state: a Boolean to determine the state of the zone (whether it actually contains a mine or not). This property will be set as false when the zone is instantiated, and then when placing the mines we will adjust this property as needed.
  • revealed: a Boolean to determine whether the user has opened the zone or not. It will also be set to false.
  • marked: a Boolean to determine whether the user has marked the zone as containing a mine. At the begining it, too, will be set to false.
  • zoneValue: an integer representing the number of mine-containing zones surrounding this zone.
  • xcor: an integer representing the horizontal position of the zone within the board. this property will take its value from a parameter that we will pass to the constructor.
  • ycor: an integer representing the vertical position of the zone within the board. this property will also take its value from a parameter that we will pass to the constructor as well.

Your class should look like this.

1
2
package  {
3
	import flash.display.MovieClip;
4
	import flash.events.MouseEvent;
5
	public class Zone extends MovieClip {
6
		public var state:Boolean;		//true contains mine, false not containing mine

7
		public var revealed:Boolean;
8
		public var xcor:uint;
9
		public var ycor:uint;
10
		public var marked:Boolean;
11
		public var zoneValue:int;
12
		public function Zone(corx:uint, cory:uint) {
13
			this.state = false;
14
			this.revealed = false;
15
			this.marked = false;
16
			this.zonevalue = 0;
17
			this.xcor = corx;
18
			this.ycor = cory;
19
		} //end of constructor ----

20
	}	//end of class

21
}	//end of package

Note: since we have made the constructor accept the values of corx and cory as parameters, we need to enter those parameters into our test instance that we have made, otherwise we will get an error. So adjust this line where we define the instance by adding any couple of positive integers like this:

1
2
var testZone:Zone = new Zone(5, 6);

Next we will need to hide the highlight_mc movieclip - so that it appears only when the mouse is over the zone object - and we need to hide the bomb_mc movieclip as well - so that it doesn't appear unless the user marks the zone as containing a mine. So add the following lines to your class constructor.

1
2
this.highlight_mc.visible = false;
3
this.bomb_mc.visible = false;

Next we need to add the event listeners for the hover functionality as well as mouse clicking. So add those event listeners as well as their corresponding handler functions.

Your class sould look like this:

1
2
package  {
3
	import flash.display.MovieClip;
4
	import flash.events.MouseEvent;
5
	public class zone extends MovieClip {
6
		public var xcor:uint;
7
		public var ycor:uint;
8
		public var state:Boolean;		//true => contains mine

9
		public var revealed:Boolean;
10
		public var marked:Boolean;
11
		public var zonevalue:int;
12
		public function zone(corx:uint, cory:uint) {
13
			this.state = false;
14
			this.revealed = false;
15
			this.marked = false;
16
			this.zoneValue = 0;
17
			this.xcor = corx;
18
			this.ycor = cory;
19
			this.highlight_mc.visible = false;
20
			this.bomb_mc.visible = false;
21
			this.addEventListener(MouseEvent.MOUSE_OVER, showHighlight);
22
			this.addEventListener(MouseEvent.MOUSE_OUT, removeHighlight);
23
			this.addEventListener(MouseEvent.CLICK, zoneClicked);
24
		} //end of constructor ----

25
		private function showHighlight(event:MouseEvent):void{
26
			this.highlight_mc.visible = true;
27
		}
28
		private function removeHighlight(event:MouseEvent):void{
29
			this.highlight_mc.visible = false;
30
		}
31
		private function zoneClicked(event:MouseEvent):void{
32
			trace("zone clicked!");
33
		}
34
	}	//end of class

35
}	//end of package

If you test the movie you can see the highlight functionality working.

Highlight functionality workingHighlight functionality workingHighlight functionality working

Now, let's work with the zoneClicked() function.

Earlier we discussed the problem of right clicking within Flash Player, and we decided that we will use the shiftKey property of the mouse event to determine wheather the user has the shift key pressed, to resemble the right click functionality.

So remove the trace statement and add an if-else block inside the zoneClicked() function as follows:

1
2
if(event.shiftKey){		//resemble right clicking

3
4
} else{					//normal left clicking

5
6
}

Note: if you want to add the right click functionality within Adobe AIR, you should split the first part of the if statement into your right click event handler function.

Now within the right click part, we need to let the user mark the zone as containing a mine or remove marking for the zone if it was previously marked. This includes three steps to be reversed each time:

  • changing the marked property of the zone.
  • changing the visibilty of bomb_mc movieclip.
  • decrementing/incrementing the value displayed in the "mines left" textfield by one.

This is achieved using the following lines.. pretty simple.

1
2
if(this.marked){
3
	this.bomb_mc.visible = false;
4
	this.marked = false;
5
	this.parent.minesleft_txt.text = String(int(this.parent.minesleft_txt.text) + 1);
6
} else{
7
	this.bomb_mc.visible = true;
8
	this.marked = true;
9
	this.parent.minesleft_txt.text = String(int(this.parent.minesleft_txt.text) - 1);
10
}

Note that if we want to call an object that is on the stage from the Zone class, we use this.parent property to target the stage; from there and you can target the object you want. Later on when we make all of the zones, they will be wrapped within a sprite container. so you'll need to come back and adjust these stage objects calls to be this.parent.parent.

(This is arguably not great OOP practice, but it is very useful for staying within our 200 line limit!)

Test the movie and see our progress. Click on the test zone several times with your shift key pressed.


Next, in the normal clicking part of the if statement, we need to test whether the zone contains a mine, using the state property - if it's true, then it's game over. else then we need to open the zone.

This is done through the following lines of code.

1
2
if(!state){
3
	openZone();
4
} else{				//game over ----

5
	this.parent.gameover_btn.visible = true;
6
	this.parent.again_btn.visible = true;
7
}

For opening the zone, we will move this part into its own function called openZone() in order to keep our code organized and clean. Also we will need to call this function from outside the class later on. So call it, and define it at the end of the class.

For the "game over" part, we will make it simple by just displaying the gameover movieclip and the play again button. if you want to extend this into something robust, you can create a new event called GAME_OVER, dispatch it from here and listen to this event from your Main class, but this is beyond the scope of this tutorial.

But there is a little gotcha in the logic within the previous lines. can you figure it? well, it occurs if the user pressed a zone that he has already marked - the one who knows how to play the game probably won't do that but it may be done by mistake - so we will prevent this by wrapping this part with a conditional to check whether the zone is marked or not, as follows:

1
2
if(!state){
3
	openZone();
4
} else{				//game over ----

5
	if(!this.marked){
6
		this.parent.gameover_btn.visible = true;
7
		this.parent.again_btn.visible = true;
8
	}
9
}

So your whole zoneclicked() function should be like this.

1
2
private function zoneclicked(event:MouseEvent):void{
3
	if(event.shiftKey){			//resemble right clicking----

4
		if(this.marked){
5
			this.bomb_mc.visible = false;
6
			this.marked = false;
7
			this.parent.minesleft_txt.text = String(int(this.parent.parent.minesleft_txt.text) + 1);
8
		} else{
9
			this.bomb_mc.visible = true;
10
			this.marked = true;
11
			this.parent.minesleft_txt.text = String(int(this.parent.parent.minesleft_txt.text) - 1);
12
		}
13
	} else{					//resemble normal left clicking----

14
		if(!state){
15
			openZone();
16
		} else{				//game over ----

17
			if(!this.marked){
18
				this.parent.gameover_btn.visible = true;
19
				this.parent.again_btn.visible = true;
20
			}
21
		}
22
	}
23
}

Test the movie to see our progress.


Now, we want to work on the opening zone part, so define the openzone() function if you haven't already done this and let's wrap our heads around the logic of this part.

For opening a zone, the following steps need to be done:

  • Changing the value of the revealed propery.
  • Hiding the block_mc (blue graphic) movieclip.
  • Removing the highlight_mc movieclip.
  • Disabling the event listeners.

But we want to perform these steps only when the zone is not marked. - otherwise if the user wrongly marked a zone that doesn't contain mine, then it'd be opened if he clicked on it in the marked state, which is what we don't want. also, if it's already revealed, then there's no need to repeat these steps again. so we will check for these couple of conditions in an if statement before doing these steps.

So the function will look like this:

1
2
private function openZone():void{
3
	if(!this.marked && !this.revealed){
4
		this.block_mc.visible = false;
5
		this.revealed = true;
6
		this.highlight_mc.visible = false;
7
		this.removeEventListener(MouseEvent.MOUSE_OVER, showHighlight);
8
		this.removeEventListener(MouseEvent.MOUSE_OUT, removeHighlight);
9
		this.removeEventListener(MouseEvent.CLICK, zoneClicked);
10
	}
11
}

Okay, now we have made most of the functionality we need for the zone class. we will stop at this point and will come back later if we need to. your class should look like this:

1
2
package{
3
	import flash.display.MovieClip;
4
	import flash.events.MouseEvent;
5
	public class Zone extends MovieClip{
6
		public var state:Boolean;		//true contains mine, false not containing mine

7
		public var revealed:Boolean;
8
		public var marked:Boolean;
9
		public var xcor:uint;
10
		public var ycor:uint;
11
		public var zoneValue:int;
12
		public function Zone(corx:uint, cory:uint){
13
			this.state = false;
14
			this.revealed = false;
15
			this.marked = false;
16
			this.zoneValue = 0;
17
			this.xcor = corx;
18
			this.ycor = cory;
19
			this.highlight_mc.visible = false;
20
			this.bomb_mc.visible = false;
21
			this.addEventListener(MouseEvent.MOUSE_OVER, showHighlight);
22
			this.addEventListener(MouseEvent.MOUSE_OUT, removeHighlight);
23
			this.addEventListener(MouseEvent.CLICK, zoneClicked);
24
		}	//end of constructor

25
		private function showHighlight(event:MouseEvent):void{
26
			this.highlight_mc.visible = true;
27
		}
28
		private function removeHighlight(event:MouseEvent):void{
29
			this.highlight_mc.visible = false;
30
		}
31
		private function zoneClicked(event:MouseEvent):void{
32
			if(event.shiftKey){		//resemble right clicking

33
				if(this.marked){
34
					this.bomb_mc.visible = false;
35
					this.marked = false;
36
				this.parent.minesleft_txt.text = String(int(this.parent.minesleft_txt.text) + 1);
37
				} else{
38
					this.bomb_mc.visible = true;
39
					this.marked = true;
40
					this.parent.minesleft_txt.text = String(int(this.parent.minesleft_txt.text) - 1);
41
				}
42
			} else{				//normal left clicking

43
				if(!state){
44
					openZone();
45
				} else{				//game over ----

46
					if(!this.marked){
47
						this.parent.gameover_btn.visible = true;
48
						this.parent.again_btn.visible = true;
49
					}
50
				}
51
			}
52
		}
53
		private function openZone(){
54
			if(!this.marked && !this.revealed){
55
				this.block_mc.visible = false;
56
				this.revealed = true;
57
				this.highlight_mc.visible = false;
58
				this.removeEventListener(MouseEvent.MOUSE_OVER, showHighlight);
59
				this.removeEventListener(MouseEvent.MOUSE_OUT, removeHighlight);
60
				this.removeEventListener(MouseEvent.CLICK, zoneClicked);
61
			}
62
		}
63
	}	//end of class

64
}	//end of package

63 lines of code.. great. Can we make it through what is left for us from the 200 lines?! :)

Next we will go back to our Main class.


Dealing with Grids and Nested Arrays

Now, back to our Main class, don't forget to remove the lines that make the test zone.

To make a grid or a series of objects in a table structure, we will make an array that will hold a reference to all the other objects. this array will represent the whole grid or table - in our case it's the zones array that we've defined at the begining of the Main class.

This array will contain sub-arrays where each sub-array will represent a complete row within the grid or table. the number of subarrays within the main array will represent the number of rows within the grid. See this article for more information.

Each one of those sub-arrays will contain a number of elements where each one of those elements will represent one spot or cell within the grid or table (in our case it will be Zone). the number of elements within every sub-array will represent the number of columns within the grid or table.

This is done by using two nested for loops, where, within each iteration of the first loop, we create the sub-array and loop through the second loop to make the elements of the row.

Note that you can use this hierarchy for any objects that you want to arrange in a grid.


Section 3: Creating the Board

Now within the createBoard() function, we will make a for loop that loops through the height of the board.

Within each iteration of the loop, we will make a sub-array that will represent a complete horizontal row of zones (hzones), pushing this array to the parent zones array at the index of the position of the row, and then we will make another nested for loop, as follows:

1
2
private function createBoard():void{
3
	for(var i:Number = 0; i < boardHeight; i++){
4
		var hzones:Array = new Array();
5
		zones[i] = hzones;
6
		for(var j:Number = 0; j < boardWidth; j++){
7
			
8
		}
9
	}
10
}

Now within every iteration of the second loop, we create a new Zone object - passing to it corx & cory parameters - add it to the related/current hzones sub-array, and then add it to the board sprite object container, and adjust its position. as follows:

1
2
private function createBoard():void{
3
	for(var i:Number = 0; i < boardHeight; i++){
4
		var hzones:Array = new Array();
5
		zones[i] = hzones;
6
		for(var j:Number = 0; j < boardWidth; j++){
7
			var newzone:Zone = new Zone(j, i);
8
			zones[i][j] = newzone;
9
			board.addChild(zones[i][j]);
10
			zones[i][j].x = j*40 + 20;
11
			zones[i][j].y = i*40 + 20;
12
		}
13
	}
14
}

Note that to access a single element in an array where this array is nested within a second array (making a multidimensional array), we use the double array access operator zones[i][j].

Also note that we have made our zones as children within a display object container - which is the board Sprite object - so we need to modify every call to any stage object from the zone class from this.parent into this.parent.parent, otherwise we will get a reference error. you can do this either manually or using the "find and replace" feature within your editor.

Now test your movie to see the progress of our work.

Creating all the zones in the boardCreating all the zones in the boardCreating all the zones in the board

Section 4: Placing Mines

Great, we've made the board with all the zones but none of them contain any mines. so it's time to place some mines.

The idea in placing mines is to choose a number of random zones, and change their state property from false to true (which means that they each contain a mine).

The number of zones to be changed will be equal to the numberOfMines property which we've defined at the beginning of the main class.

So, we will start the function with a while loop - though I always like to use for loops, I used a while loop for a reason that will be explained shortly - the while loop will count down for the number of mines that's taken from the numberOfMines property.

1
2
private function placeMines():void{
3
	var bombsCounter:uint = numberOfMines;
4
	while(bombsCounter > 0){
5
		bombsCounter--;
6
	}
7
}

Now, how do we target a random zone for our grid of zones? well, it's done by a series of simple math operations to get their x and y positions within the board. let me show you the code for this first and then I'll explain it in steps.

1
2
private function placeMines():void{
3
	var bombsCounter:uint = numberOfMines;
4
	while(bombsCounter > 0){
5
		var randomNumber:Number = Math.round(Math.random()* ((boardHeight * boardWidth) - 1));
6
		var rowCount:Number = Math.floor(randomNumber / boardWidth);
7
		var columnCount:Number = randomNumber % boardWidth;
8
		zones[rowCount][columnCount].state = true;
9
		bombsCounter--;
10
	}
11
}

First: we get the total number of zones, that would be the width times the height of the board. since we are dealing with array indices and they start by zero, we need to subtract one from this number.

Second: we get a random number between zero and the previous number, and round the whole thing to get a whole integer. this way we get a random whole number between zero and the last zone number subtracted by one.

Third: by dividing the random number that we've got from the previous step by the width of the board (number of zones within each row), the result will be a mixed number with a whole number part and a fraction. The whole number will represent the row count for the targeted zone. i.e. the index of the zones array representing the vertical index for the zone within the grid. We get the closest integer that is less than the specified number using the floor() function of the Math class.

Fourth: the remainder from the previous division will be the column count for the targeted zone. i.e. the index of the sub-array inside the zones array representing the vertical index for the zone within the grid.

For example: suppose we made a 15x10 grid of zones, and lets assume that we've generated the number 83 randomly. then dividing 83 over the width of the board, which is 15, will be equal to 5.5333. so the rowcount will be 5, and the remainder will be 8, which is the column count.

Now we can target the zone easily as zones[rowCount][columnCount], changing its state property to true and so on continuing the loop.

In order to see the effect of this, add to the function this line that will display the bomb_mc movieclip for the targeted zones - just for demonstration and seeing our progress - and then test the movie:

1
2
zones[rowCount][columnCount].bomb_mc.visible = true;
Mines placed randomly across the boardMines placed randomly across the boardMines placed randomly across the board

Now if you click on any one of the mines that have the bomb_mc moviclip visible, you get the game over message. great, we are doing well.

But there's another problem that we must solve: what if one or more specific numbers were generated randomly more than one time? then the number of zones that will contain mines will be less than the specified number. This is probably going to happen when you generate lots of random numbers. If you tested your movie as it is now and counted their number, you might be lucky to get the exact number -- if so, then try it again and I assure you you will face the problem, especially if you increased the number of mines.

Well, to solve this problem we will check for the state of the zone generated within an if conditional. if it's true, then this zone has been generated before and state has been changed. Within the if block, not only will we put the statement that changes the zone state, but also the statement that decrements the counter of the while loop -- and only if the state of the current zone has not already been set to true. That's why I used a while loop instead of for loop; this way the counter won't decrement unless a new zone is generated and state changed.

Further explanation: normally a typical while loop would be as follows:

1
2
var counter:int = 0;
3
while(counter < 10){
4
	.....
5
	counter++
6
}

But by wrapping the counter changing statement within a conditional, the loop will continue to loop as long as the condition is not satisfied without changing the counter, as follows:

1
2
var counter:int = 0;
3
while(counter < 10){
4
	.....
5
	if(condition){
6
		counter++
7
	}
8
}

So the final placeMines() function will look like this:

1
2
private function placeMines():void{
3
	var bombsCounter:uint = numberOfMines;
4
	while(bombsCounter > 0){
5
		var randomNumber:Number = Math.round(Math.random()* ((boardHeight * boardWidth) - 1));
6
		var rowCount:Number = Math.floor(randomNumber / boardWidth);
7
		var columnCount:Number = randomNumber % boardWidth;
8
		if(!zones[rowCount][columnCount].state){
9
			zones[rowCount][columnCount].state = true;
10
			bombsCounter--;
11
		}
12
	}
13
}

Section 5: Placing Numbers

Great, now the final step in the core functionality of a minesweeper game is to give the user a clue on how to find zones that contain mines, using numbers. so instead of always displaying the number 7 (which we used while designing to see how the zone would look), we will change that within the placeNumbers() function.

The idea behind placing the numbers is to find the number of zones surrounding a given zone in the board that have a state of true - i.e. that contain a mine - from all the eight zones surrounding that zone.

But there's a couple of little problems in front of us:

  1. How would we target the surrounding zones?
  2. Not all zones have 8 zones surrounding them. the four zones at the corners have only 3 surrounding zones each. those that are on the four sides (left, right, top & bottom) other than those on the corners have 5 surrounding zones. the others have 8 surrounding zones.
Number of neighbours surrounding each zoneNumber of neighbours surrounding each zoneNumber of neighbours surrounding each zone

So to solve this problem, we will leave the placeNumbers() function for awhile, and make a helper function to get references for the surrounding zones based on a single zone that we will pass to that function. I called this function getNeighbours(). the function will accept a Zone object as a parameter, and it will return an array containing references of the surrounding zones for the passed zone. In this, we will make extense use of the xcor & ycor properties of the Zone object. (Note that this function will be public as I know that we will use it from outside the class later.)

So start making the function as follows within your Main class, and define the array that will be returned - I called it neighboursArray:

1
2
public function getNeighbours(z:Zone):Array{
3
	var neighboursArray:Array = [];
4
	
5
	return neighboursArray;
6
}

What we will be doing within this function is getting the neighbours, pushing them on to this array, and then returning the array at the end of the function.

Well, now we will test for the ycor property, which will have either one of three possibilities:

  • zero, which means that the passed zone is on the top row of zones.
  • boardHeight - 1, which means that the passed zone is at the bottom row of zones.
  • Any other value between the previous two values, which mean that the passed zone is on one of the middle rows.
indices of zones within the boardindices of zones within the boardindices of zones within the board
1
2
public function getNeighbours(z:Zone):Array{
3
	var neighboursArray:Array = [];
4
	if(z.ycor == 0){
5
		
6
	} else if(z.ycor == (boardHeight - 1)){
7
		
8
	} else {
9
		
10
	}
11
	return neighboursArray;
12
}

Within each condition, we will test for the xcor property. As with the ycor property it has three possibilities:

  • zero, which means that the passed zone is on the left column of zones.
  • boardWidth - 1, which means that the passed zone is at the right column of zones.
  • Any other value between the previous two values, which means that the zone is in one of the middle columns.
1
2
public function getNeighbours(z:Zone):Array{
3
	var neighboursArray:Array = [];
4
	if(z.ycor == 0){
5
		if(z.xcor == 0){	//three surrounding zones

6
			
7
		} else if(z.xcor == (boardWidth - 1)){	//three surrounding zones

8
			
9
		} else{		//five surrounding zones

10
			
11
		}
12
	} else if(z.ycor == (boardHeight - 1)){
13
		if(z.xcor == 0){	//three surrounding zones

14
			
15
		} else if(z.xcor == (boardWidth - 1)){	//three surrounding zones

16
			
17
		} else{	//five surrounding zones

18
			
19
		}
20
	} else {
21
		if(z.xcor == 0){	//five surrounding zones

22
			
23
		} else if(z.xcor == (boardWidth - 1)){	//five surrounding zones

24
			
25
		} else{		//eight surrounding zones

26
			
27
		}
28
	}
29
	return neighboursArray;
30
}

So we are left with nine different possibilities for any zone passed to this function.


Now, to target a single zone that is next to the current zone, we just call its reference from the zones array object, using the xcor & ycor properties of the current zone.

For example, to target a zone that is on the left of the current zone, we call zones[xcor - 1][ycor]; to target the one below, we call zones[xcor][ycor + 1].

In this way we push the targeted zones to the neighboursArray.

Targeting the neighbours of each zone objectTargeting the neighbours of each zone objectTargeting the neighbours of each zone object

Now, your function should look like this.

1
2
public function getNeighbours(z:Zone):Array{
3
	var neighboursArray:Array = [];
4
	if(z.ycor == 0){
5
		if(z.xcor == 0){	//three surrounding zones

6
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

7
			neighboursArray.push(zones[z.ycor + 1][z.xcor]);		//bottom center

8
			neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

9
		} else if(z.xcor == (boardWidth - 1)){	//three surrounding zones

10
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

11
			neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

12
			neighboursArray.push(zones[z.ycor + 1][z.xcor]);		//bottom center

13
		} else{		//five surrounding zones

14
			neighboursArray.push(zones[z.ycor + 1][z.xcor]);		//bottom center

15
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

16
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

17
			neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

18
			neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

19
		}
20
	} else if(z.ycor == (boardHeight - 1)){
21
		if(z.xcor == 0){	//three surrounding zones

22
			neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

23
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

24
			neighboursArray.push(zones[z.ycor - 1][z.xcor]);		//top center

25
		} else if(z.xcor == (boardWidth - 1)){	//three surrounding zones

26
			neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

27
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

28
			neighboursArray.push(zones[z.ycor - 1][z.xcor]);		//top center

29
		} else{	//five surrounding zones

30
			neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

31
			neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

32
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

33
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

34
			neighboursArray.push(zones[z.ycor - 1][z.xcor]);		//top center

35
		}
36
	} else {
37
		if(z.xcor == 0){	//five surrounding zones

38
			neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

39
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

40
			neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

41
			neighboursArray.push(zones[z.ycor - 1][z.xcor]);		//top center

42
			neighboursArray.push(zones[z.ycor + 1][z.xcor]);		//bottom center

43
		} else if(z.xcor == (boardWidth - 1)){	//five surrounding zones

44
			neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

45
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

46
			neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

47
			neighboursArray.push(zones[z.ycor - 1][z.xcor]);		//top center

48
			neighboursArray.push(zones[z.ycor + 1][z.xcor]);		//bottom center

49
		} else{		//eight surrounding zones

50
			neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

51
			neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

52
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

53
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

54
			neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

55
			neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

56
			neighboursArray.push(zones[z.ycor - 1][z.xcor]);		//top center

57
			neighboursArray.push(zones[z.ycor + 1][z.xcor]);		//bottom center

58
		}
59
	}
60
	return neighboursArray;
61
}

But if you look at the lines highlighted within the previous code, you will find that they repeat several conditions. for example: all the zones that are on the top row (within the ycor == 0 condition) have a bottom center zone beneath each of them, and this statement

1
2
neighboursArray.push(zones[z.ycor + 1][z.xcor]);		//bottom center

Is repeated for all the three conditions of the xcor property. I want to keep up my promise to finish the game within 200 lines and so I will merge those three repeated lines into one at the beginning of the first if block.

So, your function should look like this:

1
2
public function getNeighbours(z:Zone):Array{
3
	var neighboursArray:Array = [];
4
	if(z.ycor == 0){
5
	neighboursArray.push(zones[z.ycor + 1][z.xcor]);		//bottom center

6
		if(z.xcor == 0){	//three surrounding zones

7
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

8
			neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

9
		} else if(z.xcor == (boardWidth - 1)){	//three surrounding zones

10
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

11
			neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

12
		} else{		//five surrounding zones

13
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

14
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

15
			neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

16
			neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

17
		}
18
	} else if(z.ycor == (boardHeight - 1)){
19
	neighboursArray.push(zones[z.ycor - 1][z.xcor]);		//top center

20
		if(z.xcor == 0){	//three surrounding zones

21
			neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

22
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

23
		} else if(z.xcor == (boardWidth - 1)){	//three surrounding zones

24
			neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

25
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

26
		} else{	//five surrounding zones

27
			neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

28
			neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

29
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

30
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

31
		}
32
	} else {
33
	neighboursArray.push(zones[z.ycor - 1][z.xcor]);		//top center

34
	neighboursArray.push(zones[z.ycor + 1][z.xcor]);		//bottom center

35
		if(z.xcor == 0){						//five surrounding zones

36
			neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

37
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

38
			neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

39
		} else if(z.xcor == (boardWidth - 1)){	//five surrounding zones

40
			neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

41
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

42
			neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

43
		} else{									//eight surrounding zones

44
			neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

45
			neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

46
			neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

47
			neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

48
			neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

49
			neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

50
		}
51
	}
52
	return neighboursArray;
53
}

Great, you can see that with this simple refactoring we've saved eight lines.

Now our function is ready, and we can use it.


Back to our placeNumbers() function, we will start by looping through all the zones using two nested for loops. So that we can see the progress of our work, we will add this statement to hide the block_mc of all of the zones at the beginning of the second loop:

1
2
zones[e][f].block_mc.visible = false;

So your function should look like this

1
2
private function placeNumbers():void{
3
	for(var e:Number = 0; e < boardHeight; e++){
4
		for(var f:Number = 0; f < boardWidth; f++){
5
			zones[e][f].block_mc.visible = false;
6
			
7
		}
8
	}
9
}

For each zone, we will get its neighbours using the getNeighbours() function we just made, then loop through these neighbours; we will check for the state of each neighbour counting those with state == true within a counter variable that we call zoneNumber.

1
2
private function placeNumbers():void{
3
	var zoneNumber:int = 0;
4
	for(var e:Number = 0; e < boardHeight; e++){
5
		for(var f:Number = 0; f < boardWidth; f++){
6
			zones[e][f].block_mc.visible = false;
7
			var zoneNeighbours:Array = getNeighbours(zones[e][f]);
8
			var len:uint = zoneNeighbours.length;
9
			zoneNumber = 0;			//resetting the value for each zone

10
			for(var d:Number = 0; d < len; d++){
11
				if(zoneNeighbours[d].state){ zoneNumber++; }
12
			}
13
			
14
		}
15
	}
16
}

Now, the value of zoneNumber is what we were aiming for. Next, we change the value of both zoneValue property and num_txt text field value to be equal to zoneNumber.

1
2
private function placeNumbers():void{
3
	var zoneNumber:int = 0;
4
	for(var e:Number = 0; e < boardHeight; e++){
5
		for(var f:Number = 0; f < boardWidth; f++){
6
			zones[e][f].block_mc.visible = false;
7
			var zoneNeighbours:Array = getNeighbours(zones[e][f]);
8
			var len:uint = zoneNeighbours.length;
9
			zoneNumber = 0;			//resetting the value for each zone

10
			for(var d:Number = 0; d < len; d++){
11
				if(zoneNeighbours[d].state){ zoneNumber++; }
12
			}
13
			zones[e][f].num_txt.text = String(zoneNumber);
14
			zones[e][f].zoneValue = zoneNumber;
15
		}
16
	}
17
}

Test the movie to see that all the zones are numbered correctly.

Numbers placed correctly on all the zones of the boardNumbers placed correctly on all the zones of the boardNumbers placed correctly on all the zones of the board

Finally, the last step that we would like to do for testing purposes is to hide the num_txt text field if the zoneValue is equal to zero or if state is true. So we will add this conditional:

1
2
if(zoneNumber == 0 || zones[e][f].state == true){
3
	zones[e][f].num_txt.visible = false;
4
}
Numbers placed correctly on all the zones of the boardNumbers placed correctly on all the zones of the boardNumbers placed correctly on all the zones of the board

After that, remove the line that hides the block_mc movieclip for the zones.

Your function should finally look like this:

1
2
private function placeNumbers():void{
3
	var zoneNumber:int = 0;
4
	for(var e:Number = 0; e < boardHeight; e++){
5
		for(var f:Number = 0; f < boardWidth; f++){
6
			var zoneNeighbours:Array = getNeighbours(zones[e][f]);
7
			var len:uint = zoneNeighbours.length;
8
			zoneNumber = 0;			//resetting the value for each zone

9
			for(var d:Number = 0; d < len; d++){
10
				if(zoneNeighbours[d].state){ zoneNumber++; }
11
			}
12
			zones[e][f].num_txt.text = String(zoneNumber);
13
			zones[e][f].zoneValue = zoneNumber;
14
			if(zoneNumber == 0 || zones[e][f].state == true){
15
				zones[e][f].num_txt.visible = false;
16
			}
17
		}
18
	}
19
}

Congratulations! Now we have the got the core minimum minesweeper functionality, so you can finish a minesweeper gameplay using what we've reached up to this point.


Section 6: Opening Neighbours

Now we want to extend on it for a better game. a common thing in minesweeper games is that if a zone with a value of zero is opened, its surrounding zones are opened automatically.

Well, since we have laid our ground up planning the game correctly and using our helper function getNeighbours(), this is done easily.

We will simply make a new function within our zone class called openNeighbours(), and within this function we will get an array of the surrounding zones using the getNeighbours() function that's on the Main class - so calling it will be using this.parent.parent property for our heirarchy, and that's why I've made this function public - then loop through each one of those zones and call the openZone() function on each one of them.

The function will be as follows:

1
2
private function openNeighbours():void{
3
	var neighbours:Array = this.parent.parent.getNeighbours(this);
4
	for(var h:Number = 0; h < neighbours.length; h++){
5
		if(!neighbours[h].revealed){ neighbours[h].openZone(); }
6
	}
7
}

Then within the openZone() function, we will add a conditional at the end of the function to check if the value equals zero, and, if so, call the openNeighbours() function.

1
2
		if(this.zoneValue == 0){ openNeighbours(); }

Test the movie and play around to see the result that we have got.

Neighbours opened automatically when a zero value zone is openedNeighbours opened automatically when a zero value zone is openedNeighbours opened automatically when a zero value zone is opened

Section 7: "Play Again" Functionality

The last and final step, to finish this tutorial, is to get the Play Again button to work.

So, anywhere inside the constructor of the Main class, we will add an event listener for the CLICK event and attach it to the makeAnotherGame() event handler function.

1
2
again_btn.addEventListener(MouseEvent.CLICK, makeAnotherGame);

Then, define the makeAnotherGame() function, in which we will perform the following steps:

  • hide Game Over graphic as well as Play Again button.
  • remove the board Sprite object from the display list.
  • reset the board object and the zones array into a new Sprite and new Array, respectively.
  • reset the value of "mines left" text field.
  • call the createBoard(), placeMines() and placeNumbers() functions, respectively.
1
2
private function makeAnotherGame(event:MouseEvent):void{
3
	gameover_btn.visible = false;
4
	again_btn.visible = false;
5
	removeChild(board);
6
	board = new Sprite();
7
	addChildAt(board, 0);
8
	zones = new Array();
9
	createBoard();
10
	placeMines();
11
	placeNumbers();
12
	minesleft_txt.text = String(numberOfMines);
13
}

And, that's it. So easy.


Wrapping up

Congratulations, you have done it to the end. If you want, you can extend on this game. Ideas around my head right now are making different difficulty levels, simply by changing the values of the boardWidth, boardHeight and numberOfMines properties. You can also add time limits. Your only limit is your imagination.

Your final Main class should look like this.

1
2
package  {
3
	import flash.display.Sprite;
4
	import flash.events.MouseEvent;
5
	public class Main extends Sprite {
6
		public var zones:Array;
7
		public var board:Sprite;
8
		public var boardWidth:int;
9
		public var boardHeight:int;
10
		public var numberOfMines:int;
11
		public function Main() {	//constructor----

12
			zones = new Array();
13
			board = new Sprite();
14
			boardWidth = 15;
15
			boardHeight = 15;
16
			numberOfMines = 30;
17
			gameover_btn.visible = false;
18
			again_btn.visible = false;
19
			minesleft_txt.text = String(numberOfMines);
20
			addChildAt(board, 0);
21
			createBoard();
22
			placeMines();
23
			placeNumbers();
24
			again_btn.addEventListener(MouseEvent.CLICK, makeAnotherGame);
25
		}	//end of constructor

26
		private function createBoard():void{
27
			for(var i:Number = 0; i < boardHeight; i++){
28
				var hzones:Array = new Array();
29
				zones[i] = hzones;
30
				for(var j:Number = 0; j < boardWidth; j++){
31
					var newzone:Zone = new Zone(j, i);
32
					zones[i][j] = newzone;
33
					board.addChild(zones[i][j]);
34
					zones[i][j].x = j*40 + 20;
35
					zones[i][j].y = i*40 + 20;
36
				}
37
			}
38
		}
39
		private function placeMines():void{
40
			var bombsCounter:uint = numberOfMines;
41
			while(bombsCounter > 0){
42
				var randomNumber:Number = Math.round(Math.random()* ((boardHeight * boardWidth) - 1));
43
				var rowCount:Number = Math.floor(randomNumber / boardWidth);
44
				var columnCount:Number = randomNumber % boardWidth;
45
				if(!zones[rowCount][columnCount].state){
46
					zones[rowCount][columnCount].state = true;
47
					bombsCounter--;
48
				}
49
			}
50
		}
51
		private function placeNumbers():void{
52
			var zoneNumber:int = 0;
53
			for(var e:Number = 0; e < boardHeight; e++){
54
				for(var f:Number = 0; f < boardWidth; f++){
55
					var zoneNeighbours:Array = getNeighbours(zones[e][f]);
56
					var len:uint = zoneNeighbours.length;
57
					zoneNumber = 0;			//resetting the value for each zone

58
					for(var d:Number = 0; d < len; d++){
59
						if(zoneNeighbours[d].state){ zoneNumber++; }
60
					}
61
					zones[e][f].num_txt.text = String(zoneNumber);
62
					zones[e][f].zoneValue = zoneNumber;
63
					if(zoneNumber == 0 || zones[e][f].state == true){
64
						zones[e][f].num_txt.visible = false;
65
					}
66
				}
67
			}
68
		}
69
		public function getNeighbours(z:Zone):Array{
70
			var neighboursArray:Array = [];
71
			if(z.ycor == 0){
72
			neighboursArray.push(zones[z.ycor + 1][z.xcor]);				//bottom center

73
				if(z.xcor == 0){						//three surrounding zones

74
					neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

75
					neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

76
				} else if(z.xcor == (boardWidth - 1)){	//three surrounding zones

77
					neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

78
					neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

79
				} else{									//five surrounding zones

80
					neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

81
					neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

82
					neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

83
					neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

84
				}
85
			} else if(z.ycor == (boardHeight - 1)){
86
			neighboursArray.push(zones[z.ycor - 1][z.xcor]);				//top center

87
				if(z.xcor == 0){						//three surrounding zones

88
					neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

89
					neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

90
				} else if(z.xcor == (boardWidth - 1)){	//three surrounding zones

91
					neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

92
					neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

93
				} else{									//five surrounding zones

94
					neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

95
					neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

96
					neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

97
					neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

98
				}
99
			} else {
100
			neighboursArray.push(zones[z.ycor - 1][z.xcor]);				//top center

101
			neighboursArray.push(zones[z.ycor + 1][z.xcor]);				//bottom center

102
				if(z.xcor == 0){						//five surrounding zones

103
					neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

104
					neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

105
					neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

106
				} else if(z.xcor == (boardWidth - 1)){	//five surrounding zones

107
					neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

108
					neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

109
					neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

110
				} else{									//eight surrounding zones

111
					neighboursArray.push(zones[z.ycor - 1][z.xcor - 1]);	//top left

112
					neighboursArray.push(zones[z.ycor - 1][z.xcor + 1]);	//top right

113
					neighboursArray.push(zones[z.ycor][z.xcor - 1]);		//middle left

114
					neighboursArray.push(zones[z.ycor][z.xcor + 1]);		//middle right

115
					neighboursArray.push(zones[z.ycor + 1][z.xcor - 1]);	//bottom left

116
					neighboursArray.push(zones[z.ycor + 1][z.xcor + 1]);	//bottom right

117
				}
118
			}
119
			return neighboursArray;
120
		}
121
		private function makeAnotherGame(event:MouseEvent):void{
122
			gameover_btn.visible = false;
123
			again_btn.visible = false;
124
			removeChild(board);
125
			board = new Sprite();
126
			addChildAt(board, 0);
127
			zones = new Array();
128
			createBoard();
129
			placeMines();
130
			placeNumbers();
131
			minesleft_txt.text = String(numberOfMines);
132
		}
133
	}	//end of class

134
}	//end of package

And your final Zone class should look like this:

1
2
package{
3
	import flash.display.MovieClip;
4
	import flash.events.MouseEvent;
5
	public class Zone extends MovieClip{
6
		public var state:Boolean;		//true contains mine, false not containing mine

7
		public var revealed:Boolean;
8
		public var marked:Boolean;
9
		public var xcor:uint;
10
		public var ycor:uint;
11
		public var zoneValue:int;
12
		public function Zone(corx:uint, cory:uint){
13
			this.state = false;
14
			this.revealed = false;
15
			this.marked = false;
16
			this.zoneValue = 0;
17
			this.xcor = corx;
18
			this.ycor = cory;
19
			this.highlight_mc.visible = false;
20
			this.bomb_mc.visible = false;
21
			this.addEventListener(MouseEvent.MOUSE_OVER, showHighlight);
22
			this.addEventListener(MouseEvent.MOUSE_OUT, removeHighlight);
23
			this.addEventListener(MouseEvent.CLICK, zoneClicked);
24
		}	//end of constructor

25
		private function showHighlight(event:MouseEvent):void{
26
			this.highlight_mc.visible = true;
27
		}
28
		private function removeHighlight(event:MouseEvent):void{
29
			this.highlight_mc.visible = false;
30
		}
31
		private function zoneClicked(event:MouseEvent):void{
32
			if(event.shiftKey){		//resemble right clicking

33
				if(this.marked){
34
					this.bomb_mc.visible = false;
35
					this.marked = false;
36
				this.parent.parent.minesleft_txt.text = String(int(this.parent.parent.minesleft_txt.text) + 1);
37
				} else{
38
					this.bomb_mc.visible = true;
39
					this.marked = true;
40
					this.parent.parent.minesleft_txt.text = String(int(this.parent.parent.minesleft_txt.text) - 1);
41
				}
42
			} else{				//normal left clicking

43
				if(!state){
44
					openZone();
45
				} else{				//game over ----

46
					if(!this.marked){
47
						this.parent.parent.gameover_btn.visible = true;
48
						this.parent.parent.again_btn.visible = true;
49
					}
50
				}
51
			}
52
		}
53
		private function openZone(){
54
			if(!this.marked && !this.revealed){
55
				this.block_mc.visible = false;
56
				this.revealed = true;
57
				this.highlight_mc.visible = false;
58
				this.removeEventListener(MouseEvent.MOUSE_OVER, showHighlight);
59
				this.removeEventListener(MouseEvent.MOUSE_OUT, removeHighlight);
60
				this.removeEventListener(MouseEvent.CLICK, zoneClicked);
61
				if(this.zoneValue == 0){ openNeighbours(); }
62
			}
63
		}
64
		private function openNeighbours():void{
65
			var neighbours:Array = this.parent.parent.getNeighbours(this);
66
			for(var h:Number = 0; h < neighbours.length; h++){
67
				if(!neighbours[h].revealed){ neighbours[h].openZone(); }
68
			}
69
		}
70
	}	//end of class

71
}	//end of package

Ooh, 203 lines, we missed it, but it wasn't too far, at least we got the core functionality of the game under 190 lines.

However it's not about the number of lines or how big or compact your code is. It's about good planning and making reusable and well readable code that's easily understood by others -- or even by you, after a fair amount of time, if you come back to this code later.


Conclusion

Wheew, it was a long tutorial, but it was fun and I enjoyed making it a lot. If you have any questions or suggestions, feel free to ask in the comments.

I hope that you've found this tutorial useful and learned something from it. Now you have a complete, full, functional game that you can extend upon... try making difficulty levels, time limits, etc. Also you can publish it for various devices & platforms: desktop, iOS, android, blackberry. Feel free to extend upon it and I would be happy to see the results that you've got.

In the end, thanks for bearing me and sticking with me all that time - I'll see you in later tutorials.

Thanks!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.