Advertisement
Scroll to top
Read Time: 35 min

In this tutorial I would like to show you how easy it is to create a classic "Snake" game in Flash. I will try to explain everything easily, step by step, so that you can develop the game further to your needs! The Game will be developed in AS3 and I will use the FlashDevelop IDE.


Introduction

The game won't be complex. Whenever we hit a wall, it will restart the game. After eating an apple the snake will grow, and a 'new' Apple will appear. (Actually, it will be the same apple, but I'll explain this later.)

One of the most important aspects of the game is the code's reaction to KEY_DOWN events. The snake will only then change its direction after a tick has passed, not immediately after a keypress. This means that, if the snake is going right, and you press down and left very fast, the snake will go down, not down AND left. Without this 'feature' the snake would allow us to go left while we are going right, which would mean it hit itself.


Let's Look at the Game Already!

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


Step 1: Creating the Project

In FlashDevelop, create a new Project, and inside the 'src' folder create a 'com' folder. In the 'com' folder create a new class, and call it 'Element.as'.

Set the dimensions of the project to 600x600px.

The FlashDevelop project structure

Step 2: Wait... What's an Element?

The snake is make up of blue squares, which I call elements. We will create an Element Class, which draws the element. The red apple is going to be an element too, so we will extend the code with a few more lines.

Therefore we won't create a new class for the apple. (But if you really want to, you can.)


Step 3: Writing the Element Class

The Element class creates a square. It doesn't draw it on the stage, it just creates it. The registration point of the element - the position referred to by its x- and y-coordinates - is in the top-left.

After opening the Element.as you will see something like this:

1
2
package com 
3
{
4
	/**

5
	 * ...

6
	 * @author Fuszenecker Zsombor

7
	 */
8
	public class Element 
9
	{
10
		
11
		public function Element() 
12
		{
13
			
14
		}
15
		
16
	}
17
}

First we need this to extend the Shape class, so we can use the graphics object to draw the square. After this, create two variables: one for the direction (if it's part of the snake), and one for the score value (if it's an apple), and then change the parameters of the constructor function:

1
2
package com 
3
{
4
	import flash.display.Shape;
5
	
6
	public class Element extends Shape
7
	{
8
		protected var _direction:String;
9
		//IF IT IS AN APPLE ->

10
		protected var _catchValue:Number;
11
		
12
		//color,alpha,width,height

13
		public function Element(_c:uint,_a:Number,_w:Number,_h:Number) 
14
		{
15
16
		}
17
	}
18
}

Now fill the function with some code:

1
2
package com 
3
{
4
	import flash.display.Shape;
5
	
6
	public class Element extends Shape
7
	{
8
		protected var _direction:String;
9
		//IF IT IS AN APPLE ->

10
		protected var _catchValue:Number;
11
		
12
		//color,alpha,width,height

13
		public function Element(_c:uint,_a:Number,_w:Number,_h:Number) 
14
		{
15
			graphics.lineStyle(0, _c, _a);
16
			graphics.beginFill(_c, _a);
17
			graphics.drawRect(0, 0, _w, _h);
18
			graphics.endFill();
19
			
20
			_catchValue = 0;
21
		}
22
	}
23
}

Now, whenever we create an element, it will draw a rectangle and set the score value of the element to 0 by default. (It won't put the rectangle on stage, it just draws it within itself. Notice that we have not called the addChild() function.)

Let's finish this class and then we can finally test how much we have done already:

1
2
package com 
3
{
4
	import flash.display.Shape;
5
	
6
	public class Element extends Shape
7
	{
8
		protected var _direction:String;
9
		//IF IT IS AN APPLE ->

10
		protected var _catchValue:Number;
11
		
12
		//color,alpha,width,height

13
		public function Element(_c:uint,_a:Number,_w:Number,_h:Number) 
14
		{
15
			graphics.lineStyle(0, _c, _a);
16
			graphics.beginFill(_c, _a);
17
			graphics.drawRect(0, 0, _w, _h);
18
			graphics.endFill();
19
			
20
			_catchValue = 0;
21
		}
22
		
23
		//ONLY USED IN CASE OF A PART OF THE SNAKE

24
		public function set direction(value:String):void
25
		{
26
			_direction = value;
27
		}
28
		public function get direction():String
29
		{
30
			return _direction;
31
		}
32
		
33
		//ONLY USED IN CASE OF AN APPLE

34
		public function set catchValue(value:Number):void
35
		{
36
			_catchValue = value;
37
		}
38
		public function get catchValue():Number
39
		{
40
			return _catchValue;
41
		}
42
	}
43
44
}

We created four functions to change the directions and the value of the apple. We achieved this by using setters and getters. More about Setters/Getters in this article!


Step 4: Testing the Element Class

Open Main.as now.

Import the com.Element class and create an Element in the init() function:

1
2
package  
3
{
4
	import flash.display.Sprite;
5
	import flash.events.Event;
6
	import com.Element;
7
		
8
	public class Main extends Sprite
9
	{
10
		public function Main() 
11
		{
12
			if(stage)
13
				addEventListener(Event.ADDED_TO_STAGE, init);
14
			else
15
				init();
16
		}
17
		
18
		private function init(e:Event = null):void
19
		{
20
			var testElement:Element = new Element(0x00AAFF, 1, 10, 10);
21
			testElement.x = 50;
22
			testElement.y = 50;
23
			this.addChild(testElement);
24
			
25
		}
26
		
27
	}
28
}

First we create the testElement variable which holds our element. We create a new Element and assign that to our testElement variable. Note the arguments we passed: first we give it a color, then the alpha, width and height. If you look in the Element class's Element function, you can see how it uses this data to draw the rectangle.

After creating the Element, we position it and put it on the stage!


Step 5: Setting Up the Variables

Look at the following code. I wrote the functions of the variables next to them (notice that we imported the necessary classes too):

1
2
package  
3
{
4
	import flash.display.Sprite;
5
	import flash.text.TextField;
6
	import flash.utils.Timer;
7
	import flash.events.TimerEvent;
8
	import flash.ui.Keyboard;
9
	import flash.events.KeyboardEvent;
10
	import flash.events.MouseEvent;
11
	import flash.events.Event;
12
	
13
	import com.Element;
14
		
15
	public class Main extends Sprite
16
	{
17
		
18
		//DO NOT GIVE THESE VARS A VALUE HERE! 

19
		//Give them their values in the init() function.

20
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here

21
		private var markers_vector:Vector.<Object>; //the markers are held in here

22
		private var timer:Timer;
23
		private var dead:Boolean;
24
		private var min_elements:int; //holds how many parts the snake should have at the beginning

25
		private var apple:Element; //Our apple

26
		private var space_value:Number; //space between the snake's parts

27
		private var last_button_down:uint; //the keyCode of the last button pressed

28
		private var flag:Boolean; //is it allowed to change direction?

29
		private var score:Number;
30
		private var score_tf:TextField; //the Textfield showing the score

31
		
32
		public function Main() 
33
		{
34
			if(stage)
35
				addEventListener(Event.ADDED_TO_STAGE, init);
36
			else
37
				init();
38
		}
39
		
40
		private function init(e:Event = null):void
41
		{
42
			snake_vector = new Vector.<Element>;
43
			markers_vector = new Vector.<Object>;
44
			space_value = 2; //There will be 2px space between every Element

45
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired! This will set the SPEED of the snake

46
			dead = false;
47
			min_elements = 10; //We will begin with 10 elements.

48
			apple = new Element(0xFF0000, 1, 10, 10); //red, not transparent, width:10, height: 10;

49
			apple.catchValue = 0; //pretty obvious - the score of the apple

50
			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable

51
			score = 0;
52
			score_tf = new TextField(); //this is the TextField which shows our score.

53
			this.addChild(score_tf);
54
		}	
55
	}
56
}

The most important variable is the snake_vector. We will put every Element of the snake in this Vector.

Then there is the markers_vector. We will use markers to set the direction of the snake's parts. Each object in this Vector will have a position and a type. The type will tell us whether the snake should go right, left, up, or down after 'hitting' the object. (They won't collide, only the position of the markers and the snake's parts will be checked.)

As an example, if we press DOWN, an object will be created. The x and y of this object will be the snake's head's x and y coordinates, and the type will be "Down". Whenever the position of one of the snake's Elements is the same as this object's, the snakes elements direction will be set to "Down".

Please read the comments next to the variables to understand what the other variables do!


Step 6: Writing the attachElement() Function

The attachElement() function will take four parameters: the new snake element, the x and y coordinates, and the direction of the last part of the snake.

1
2
private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
3
{
4
5
}

Before we put the element on the stage we should position it. But for this we need the direction of the snake's last element, to know whether the new element has to be above, under, or next to this.

After checking the direction and setting the position, we can add it to the stage.

1
2
private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
3
{
4
	if (dirOfLast == "R")
5
	{
6
		who.x = lastXPos - snake_vector[0].width - space_value;
7
		who.y = lastYPos;
8
	}
9
	else if(dirOfLast == "L")
10
	{
11
		who.x = lastXPos + snake_vector[0].width + space_value;
12
		who.y = lastYPos;
13
	}
14
	else if(dirOfLast == "U")
15
	{
16
		who.x = lastXPos;
17
		who.y = lastYPos + snake_vector[0].height + space_value;
18
	}
19
	else if(dirOfLast == "D")
20
	{
21
		who.x = lastXPos;
22
		who.y = lastYPos - snake_vector[0].height - space_value;
23
	}
24
	this.addChild(who);
25
}

Now we can use this function in the init() function:

1
2
for(var i:int=0;i<min_elements;++i)
3
{
4
	snake_vector[i] = new Element(0x00AAFF,1,10,10);
5
	snake_vector[i].direction = "R"; //The starting direction of the snake

6
	if (i == 0)//first snake element

7
	{
8
		//you have to place the first element on a GRID. (now: 0,0) 

9
		//[possible x positions: (snake_vector[0].width+space_value)*<UINT> ]

10
		attachElement(snake_vector[i],0,0,snake_vector[i].direction) 
11
		snake_vector[0].alpha = 0.7;
12
	}
13
	else
14
	{
15
		attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
16
	}
17
}

We create the first 10 Elements, and set the direction of them to 'R' (right). If it is the first element, we call attachElement() and we change its alpha a bit (so the "head" is a slightly lighter color).

If you wish to set the position somewhere else, then please keep the following in mind: the snake has to be placed on a grid, otherwise it would look bad and would not work. If you wish to change the x and y position you can do it the following way:

Setting the x position: (snake_vector[0].width+space_value)*[UINT], where you should replace [UINT] with a positive integer.

Setting the y position: (snake_vector[0].height+space_value)*[UINT], where you should replace [UINT] with a positive integer.

Let's change it to this:

1
2
if (i == 0)//first snake element

3
{
4
	//you have to place the first element on a GRID. (now: 0,0) 

5
	//[possible x positions: (snake_vector[0].width+space_value)*<UINT>]

6
	attachElement(
7
		snake_vector[i],
8
		(snake_vector[0].width+space_value)*20,
9
		(snake_vector[0].height+space_value)*10,
10
		snake_vector[i].direction
11
	);
12
	snake_vector[0].alpha = 0.7;
13
}

And the snake's first element is set onto the 20th space in the x-grid and 10th space in the y-grid.

This is what we've got so far:

1
2
package  
3
{
4
	import flash.display.Sprite;
5
	import flash.text.TextField;
6
	import flash.utils.Timer;
7
	import flash.events.TimerEvent;
8
	import flash.ui.Keyboard;
9
	import flash.events.KeyboardEvent;
10
	import flash.events.MouseEvent;
11
	import flash.events.Event;
12
	
13
	import com.Element;
14
		
15
	public class Main extends Sprite
16
	{
17
		
18
		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function

19
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here

20
		private var markers_vector:Vector.<Object>; //the markers are held in here

21
		private var timer:Timer; 
22
		private var dead:Boolean;
23
		private var min_elements:int; //holds how many parts should the snake have at the beginning

24
		private var apple:Element; //Our apple

25
		private var space_value:Number; //space between the snake parts

26
		private var last_button_down:uint; //the keyCode of the last button pressed

27
		private var flag:Boolean; //is it allowed to change direction?

28
		private var score:Number;
29
		private var score_tf:TextField; //the Textfield showing the score

30
		
31
		public function Main() 
32
		{
33
			if(stage)
34
				addEventListener(Event.ADDED_TO_STAGE, init);
35
			else
36
				init();
37
		}
38
		
39
		private function init(e:Event = null):void
40
		{
41
			snake_vector = new Vector.<Element>;
42
			markers_vector = new Vector.<Object>;
43
			space_value = 2;
44
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!

45
			dead = false;
46
			min_elements = 10; //We will begin with 10 elements.

47
			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;

48
			apple.catchValue = 0; //pretty obvious

49
			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable

50
			score = 0;
51
			score_tf = new TextField(); //this is the TextField which shows our score.

52
			this.addChild(score_tf);
53
			
54
			for(var i:int=0;i<min_elements;++i)
55
			{
56
				snake_vector[i] = new Element(0x00AAFF,1,10,10);
57
				snake_vector[i].direction = "R"; //The starting direction of the snake

58
				if (i == 0)//first snake element

59
				{
60
					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]

61
					attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
62
					snake_vector[0].alpha = 0.7;
63
				}
64
				else
65
				{
66
					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
67
				}
68
			}
69
		}	
70
		
71
		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
72
		{
73
			if (dirOfLast == "R")
74
			{
75
				who.x = lastXPos - snake_vector[0].width - space_value;
76
				who.y = lastYPos;
77
			}
78
			else if(dirOfLast == "L")
79
			{
80
				who.x = lastXPos + snake_vector[0].width + space_value;
81
				who.y = lastYPos;
82
			}
83
			else if(dirOfLast == "U")
84
			{
85
				who.x = lastXPos;
86
				who.y = lastYPos + snake_vector[0].height + space_value;
87
			}
88
			else if(dirOfLast == "D")
89
			{
90
				who.x = lastXPos;
91
				who.y = lastYPos - snake_vector[0].height - space_value;
92
			}
93
			this.addChild(who);
94
		}
95
	}
96
}

Step 7: Writing the placeApple() Function

This function does the following:

  1. It checks whether the apple was caught. For this we will use the caught parameter, and set its default value to true, in case we don't pass any value as parameters in the future. If it was caught, it adds 10 to the apple's score value (so the next apple is worth more).
  2. After this the apple has to be repositioned (we don't create new apples) at a random grid position.
  3. If it is placed on the snake, we should place it somewhere else.
  4. If it is not on the stage yet, we place it there.

1
2
private function placeApple(caught:Boolean = true):void
3
{
4
	if (caught)
5
		apple.catchValue += 10;
6
		
7
	var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
8
	var randomX:Number = Math.floor(Math.random()*boundsX);
9
		
10
	var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
11
	var randomY:Number = Math.floor(Math.random()*boundsY);
12
13
	apple.x = randomX * (apple.width + space_value);
14
	apple.y = randomY * (apple.height + space_value);
15
		
16
	for(var i:uint=0;i<snake_vector.length-1;i++)
17
	{
18
		if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
19
			placeApple(false);
20
	}
21
	if (!apple.stage)
22
		this.addChild(apple);
23
}

There will be some math here, but if you think it through you should understand why it is so. Just draw it out on some paper if necessary.

  • boundsX will hold how many elements could be drawn in one row.
  • randomX takes this boundsX, multiplies it with a Number between zero and one, and floors it. If boundsX is 12 and the random Number is 0.356, then floor(12*0.356) is 4, so the apple will be placed on the 4th spot on the x-grid.
  • boundsY will hold how many elements can be drawn in one column.
  • randomY takes this boundsY, multiplies it with a Number between zero and one, and floors it.
  • Then we set the x and y position to these numbers.

In the for loop, we check whether the apple's new x and y positions are identical to any of the snake_vectors elements. If so, we call the placeApple() function again (recursive function), and set the parameter of it to false. (Meaning that the apple was not caught, we just need to reposition it)

(apple.stage) returns true if the apple is on the stage. we use the '!' operator to invert that value, so if it is NOT on the stage, we place it there.

The last thing we need to do is call the placeApple() function at the end of the init() function.

1
2
private function init(e:Event = null):void
3
{
4
    /*   

5
	.

6
	.

7
	.

8
	*/
9
	
10
	placeApple(false);
11
}

Notice that we pass false as the parameter. It's logical, because we didn't catch the apple in the init() function yet. We will only catch it in the moveIt() function.

Now there are only three more functions to write: the directionChanged(), moveIt() and the gameOver() functions.


Step 8: Starting the moveIt() Function

The moveIt() function is responsible for all of the movement. This function will check the boundaries and check whether there is an object at the x and y position of the snake's head. It will also look for the apple at this position.

For all of this, we will use our timer variable.

Add two more lines in the end of the init() function:

1
2
timer.addEventListener(TimerEvent.TIMER,moveIt);
3
timer.start();

Look at the comments in the sourcecode, to see which block of code does what.

1
2
		private function moveIt(e:TimerEvent):void
3
		{
4
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
5
			{
6
				//This code runs if the snakes heads position and the apples position are the same

7
			}
8
			
9
			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
10
			{
11
				//This block runs if the snakes head is out of the stage (hitting the walls)

12
			}
13
			
14
			
15
			for (var i:int = 0; i < snake_vector.length; i++)
16
			{
17
				/*

18
					START OF FOR BLOCK

19
					This whole 'for' block will run as many times, as many elements the snake has.

20
					If there are four snake parts, this whole for cycle will run four times. 

21
				*/
22
				
23
				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
24
				{
25
					//If the snakes heads position is the same as any of the snake parts, this block will run (Checking the collision with itself).

26
				}
27
			
28
				if (markers_vector.length > 0)
29
				{
30
					//if there are direction markers, this code runs

31
				}
32
			
33
				
34
				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.

35
				switch (DIRECTION)
36
				{
37
					//Sets the new position of the snakes part

38
				}
39
				
40
				/*

41
					END OF FOR BLOCK

42
				*/
43
			}
44
			
45
		}

Now we need to code the movement. For this we jump into the switch block, which will run on every snake part, because of the for loop.

First we need to check the direction of the current element.

1
2
				switch (DIRECTION)
3
				{
4
					case "R" :
5
						//Here we need to set the new x position for the current part

6
						break;
7
					case "L" :
8
						//Here we need to set the new x position for the current part

9
						break;
10
					case "D" :
11
						//Here we need to set the new y position for the current part

12
						break;
13
					case "U" :
14
						//Here we need to set the new y position for the current part

15
						break;
16
				}

When the direction of the part is set to "R", for instance, we need to add something to its current X position (the space_value plus the width of the snake part).

With this in mind, we can fill it out:

1
2
				switch (DIRECTION)
3
				{
4
					case "R" :
5
						snake_vector[i].x += snake_vector[i].width + space_value;
6
						break;
7
					case "L" :
8
						snake_vector[i].x -= snake_vector[i].width + space_value;
9
						break;
10
					case "D" :
11
						snake_vector[i].y += snake_vector[i].height + space_value;
12
						break;
13
					case "U" :
14
						snake_vector[i].y -= snake_vector[i].width + space_value;
15
						break;
16
				}

After testing the code, you should see that the snake is moving, and going off the stage and never stops. (You may need to refresh the page - or just click here to load it in a new window.)

So we need to stop the snake


Step 9: Writing the gameOver() Function

This function is going to be the shortest. We just clear the stage and restart it:

1
2
private function gameOver():void 
3
{
4
	dead = true;
5
	timer.stop();
6
	while (this.numChildren)
7
		this.removeChildAt(0);
8
	timer.removeEventListener(TimerEvent.TIMER,moveIt);
9
	init();
10
}

That's it. We set the dead variable to true, stop the movement with the timer, remove every child of the class and call the init() function, like we just started the game.

Now, let's get back to the moveIt() function.


Step 10: Continuing the moveIt() Function

We will use the gameOver() function in two places. The first is when we check if the head is out of bounds, and the second is when the snake hits itself:

1
2
		private function moveIt(e:TimerEvent):void
3
		{
4
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
5
			{
6
				//This code runs if the snakes heads position and the apples position are the same

7
			}
8
			
9
			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
10
			{
11
				gameOver();
12
			}
13
			
14
			
15
			for (var i:int = 0; i < snake_vector.length; i++)
16
			{
17
				/*

18
					START OF FOR BLOCK

19
					This whole 'for' block will run as many times, as many elements the snake has.

20
					If there are four snake parts, this whole for cycle will run four times. 

21
				*/
22
				
23
				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
24
				{
25
					//If the snakes heads position is the same as any of the snake parts, this block will run

26
					gameOver();
27
				}
28
			
29
				if (markers_vector.length > 0)
30
				{
31
					//if there are direction markers, this code runs

32
				}
33
			
34
				
35
				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.

36
				switch (DIRECTION)
37
				{
38
					case "R" :
39
						snake_vector[i].x += snake_vector[i].width + space_value;
40
						break;
41
					case "L" :
42
						snake_vector[i].x -= snake_vector[i].width + space_value;
43
						break;
44
					case "D" :
45
						snake_vector[i].y += snake_vector[i].height + space_value;
46
						break;
47
					case "U" :
48
						snake_vector[i].y -= snake_vector[i].width + space_value;
49
						break;
50
				}
51
				
52
				/*

53
					END OF FOR BLOCK

54
				*/
55
			}
56
			
57
		}

This is the code we have now:

1
2
package  
3
{
4
	import flash.display.Sprite;
5
	import flash.text.TextField;
6
	import flash.utils.Timer;
7
	import flash.events.TimerEvent;
8
	import flash.ui.Keyboard;
9
	import flash.events.KeyboardEvent;
10
	import flash.events.MouseEvent;
11
	import flash.events.Event;
12
	
13
	import com.Element;
14
		
15
	public class Main extends Sprite
16
	{
17
		
18
		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function

19
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here

20
		private var markers_vector:Vector.<Object>; //the markers are held in here

21
		private var timer:Timer; 
22
		private var dead:Boolean;
23
		private var min_elements:int; //holds how many parts should the snake have at the beginning

24
		private var apple:Element; //Our apple

25
		private var space_value:Number; //space between the snake parts

26
		private var last_button_down:uint; //the keyCode of the last button pressed

27
		private var flag:Boolean; //is it allowed to change direction?

28
		private var score:Number;
29
		private var score_tf:TextField; //the Textfield showing the score

30
		
31
		public function Main() 
32
		{
33
			if(stage)
34
				addEventListener(Event.ADDED_TO_STAGE, init);
35
			else
36
				init();
37
		}
38
		
39
		private function init(e:Event = null):void
40
		{
41
			snake_vector = new Vector.<Element>;
42
			markers_vector = new Vector.<Object>;
43
			space_value = 2;
44
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!

45
			dead = false;
46
			min_elements = 10; //We will begin with 10 elements.

47
			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;

48
			apple.catchValue = 0; //pretty obvious

49
			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable

50
			score = 0;
51
			score_tf = new TextField(); //this is the TextField which shows our score.

52
			this.addChild(score_tf);
53
			
54
			for(var i:int=0;i<min_elements;++i)
55
			{
56
				snake_vector[i] = new Element(0x00AAFF,1,10,10);
57
				snake_vector[i].direction = "R"; //The starting direction of the snake

58
				if (i == 0)//first snake element

59
				{
60
					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]

61
					attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
62
					snake_vector[0].alpha = 0.7;
63
				}
64
				else
65
				{
66
					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
67
				}
68
			}
69
			
70
			placeApple(false);
71
			timer.addEventListener(TimerEvent.TIMER, moveIt);
72
			timer.start();
73
		}	
74
		
75
		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
76
		{
77
			if (dirOfLast == "R")
78
			{
79
				who.x = lastXPos - snake_vector[0].width - space_value;
80
				who.y = lastYPos;
81
			}
82
			else if(dirOfLast == "L")
83
			{
84
				who.x = lastXPos + snake_vector[0].width + space_value;
85
				who.y = lastYPos;
86
			}
87
			else if(dirOfLast == "U")
88
			{
89
				who.x = lastXPos;
90
				who.y = lastYPos + snake_vector[0].height + space_value;
91
			}
92
			else if(dirOfLast == "D")
93
			{
94
				who.x = lastXPos;
95
				who.y = lastYPos - snake_vector[0].height - space_value;
96
			}
97
			this.addChild(who);
98
		}
99
		
100
		private function placeApple(caught:Boolean = true):void
101
		{
102
			if (caught)
103
				apple.catchValue += 10;
104
			
105
			var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
106
			var randomX:Number = Math.floor(Math.random()*boundsX);
107
			
108
			var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
109
			var randomY:Number = Math.floor(Math.random()*boundsY);
110
111
			apple.x = randomX * (apple.width + space_value);
112
			apple.y = randomY * (apple.height + space_value);
113
			
114
			for(var i:uint=0;i<snake_vector.length-1;i++)
115
			{
116
				if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
117
					placeApple(false);
118
			}
119
			if (!apple.stage)
120
				this.addChild(apple);
121
		}		
122
		
123
		private function moveIt(e:TimerEvent):void
124
		{
125
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
126
			{
127
				//This code runs if the snakes heads position and the apples position are the same

128
			}
129
			
130
			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
131
			{
132
				gameOver();
133
			}
134
			
135
			
136
			for (var i:int = 0; i < snake_vector.length; i++)
137
			{
138
				/*

139
					START OF FOR BLOCK

140
					This whole 'for' block will run as many times, as many elements the snake has.

141
					If there are four snake parts, this whole for cycle will run four times. 

142
				*/
143
				
144
				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
145
				{
146
					//If the snakes heads position is the same as any of the snake parts, this block will run

147
					gameOver();
148
				}
149
			
150
				if (markers_vector.length > 0)
151
				{
152
					//if there are direction markers, this code runs

153
				}
154
			
155
				
156
				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.

157
				switch (DIRECTION)
158
				{
159
					case "R" :
160
						snake_vector[i].x += snake_vector[i].width + space_value;
161
						break;
162
					case "L" :
163
						snake_vector[i].x -= snake_vector[i].width + space_value;
164
						break;
165
					case "D" :
166
						snake_vector[i].y += snake_vector[i].height + space_value;
167
						break;
168
					case "U" :
169
						snake_vector[i].y -= snake_vector[i].width + space_value;
170
						break;
171
				}
172
				
173
				/*

174
					END OF FOR BLOCK

175
				*/
176
			}
177
			
178
		}
179
		
180
		private function gameOver():void 
181
		{
182
			dead = true;
183
			timer.stop();
184
			while (this.numChildren)
185
				this.removeChildAt(0);
186
			timer.removeEventListener(TimerEvent.TIMER,moveIt);
187
			//stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);

188
			init();
189
		}
190
		
191
	}
192
}

Step 11: The directionChanged() Function

We want to listen to the keyboard, so we can actually control the snake. For this we need to put some code into the init() function and the gameOver() function.

Put this at the end of the init() function (setting up the listener function):

1
2
stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);

And this at the end of the gameOver() function:

1
2
stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);

Now create a new function:

1
2
private function directionChanged(e:KeyboardEvent):void 
3
{
4
	var m:Object = new Object(); //MARKER OBJECT 

5
	//this will be added to the markers_vector, and have the properties x,y, and type 

6
	//the type property will show us the direction. if it is set to right, whenever a snake's part hits it, 

7
	//the direction of that snake's part will be set to right also

8
	
9
	if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
10
	{
11
		//If we pressed the LEFT arrow, 

12
		//and it was not the last key we pressed, 

13
		//and the last key pressed was not the RIGHT arrow either...

14
		//Then this block of code will run

15
	}
16
	markers_vector.push(m); //we push the object into a vector, so we can acces to it later (in the moveIt() function)

17
}

What goes into the if block?

  • The direction of the head should be rewritten.
  • The marker object has to be set correctly.
  • The last_button variable should be set to the last button pressed.

1
2
if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
3
{
4
	snake_vector[0].direction = "L";
5
	m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
6
	last_button_down = Keyboard.LEFT;
7
}

Repeat this three more times, and we will have this:

1
2
private function directionChanged(e:KeyboardEvent):void 
3
		{
4
			var m:Object = new Object(); //MARKER OBJECT

5
6
			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
7
			{
8
				snake_vector[0].direction = "L";
9
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
10
				last_button_down = Keyboard.LEFT;
11
			}
12
			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT)
13
			{
14
				snake_vector[0].direction = "R";
15
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
16
				last_button_down = Keyboard.RIGHT;
17
			}
18
			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN)
19
			{
20
				snake_vector[0].direction = "U";
21
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
22
				last_button_down = Keyboard.UP;
23
			}
24
			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP)
25
			{
26
				snake_vector[0].direction = "D";
27
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
28
				last_button_down = Keyboard.DOWN;
29
			}
30
			markers_vector.push(m);
31
		}

We need one more thing to test it. In the moveIt() function we have something like this:

1
2
				if (markers_vector.length > 0)
3
				{
4
					//if there are direction markers, this code runs

5
				}

Here we need another for loop, to check every snake's part against every marker on the stage, and check whether they collide. If they do, we need to set the snake's part's direction to the marker's type. If it's the last snake part which collides with the marker, we need to remove the marker from the markers_vector, too, so the snake parts don't collide with it any more.

1
2
				if (markers_vector.length > 0)
3
				{
4
					for(var j:uint=0;j < markers_vector.length;j++)
5
					{
6
						if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
7
						{
8
							//setting the direction

9
							snake_vector[i].direction = markers_vector[j].type;
10
							if(i == snake_vector.length-1)
11
							{
12
								//if its the last snake_part

13
								markers_vector.splice(j, 1);
14
							}
15
						}
16
					}
17
				}

Now if you play with it it looks okay, but there is a bug in there. Remember what i said at the beginning of the tutorial?

For instance, if the snake is going to the right and you press the down-left combo very fast, it will hit itself and restart the game.

How do we correct this? Well it's easy. We have our flag variable, and we will use that for this. We will only be able to change the directions of the snake when this is set to true (Default is false, check the init() function for that).

So we need to change the directionChanged() function a little. The if blocks' heads should be changed: add a && flag clause at the end of every 'if'.

1
2
		private function directionChanged(e:KeyboardEvent):void 
3
		{
4
			var m:Object = new Object(); //MARKER OBJECT

5
6
			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
7
			{
8
				snake_vector[0].direction = "L";
9
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
10
				last_button_down = Keyboard.LEFT;
11
				flag = false;
12
			}
13
			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
14
			{
15
				snake_vector[0].direction = "R";
16
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
17
				last_button_down = Keyboard.RIGHT;
18
				flag = false;
19
			}
20
			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
21
			{
22
				snake_vector[0].direction = "U";
23
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
24
				last_button_down = Keyboard.UP;
25
				flag = false;
26
			}
27
			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
28
			{
29
				snake_vector[0].direction = "D";
30
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
31
				last_button_down = Keyboard.DOWN;
32
				flag = false;
33
			}
34
			markers_vector.push(m);
35
		}

If you test it now, it won't work because the flag is always false.

When do we need to set it to true then?

After a move/tick we can allow the users to change directions, we just don't want to change it twice in one tick. So put this at the very end of the moveIt() function:

1
2
flag = true;

Now test it, and there is no bug any more.


Step 12: Finishing the Game

Now the only thing we need to do is the 'apple-check'

Remember this at the very beginning of the moveIt() function?

1
2
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
3
			{
4
				//This code runs if the snake's head's position and the apple's position are the same

5
			}

This is what we need to do in there:

  • Call the placeApple() function. (We don't set the parameter to false; we leave it as it is. The default is true.)
  • Show the current score
  • Attach a new element to the snake's last part.

1
2
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
3
			{
4
				//calling the placeApple() function

5
				placeApple();
6
				//show the current Score

7
				score += apple.catchValue;
8
				score_tf.text = "Score:" + String(score);
9
				//Attach a new snake Element

10
				snake_vector.push(new Element(0x00AAFF,1,10,10));
11
				snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction; //lastOneRichtung

12
				//attachElement(who,lastXPos,lastYPos,lastDirection)

13
				attachElement(snake_vector[snake_vector.length-1],
14
									  (snake_vector[snake_vector.length-2].x),
15
									  snake_vector[snake_vector.length-2].y,
16
									  snake_vector[snake_vector.length-2].direction);
17
			}

Now everything should work fine. Try it out:

Here is the whole Main class again:

1
2
			package  
3
{
4
	import flash.display.Sprite;
5
	import flash.text.TextField;
6
	import flash.utils.Timer;
7
	import flash.events.TimerEvent;
8
	import flash.ui.Keyboard;
9
	import flash.events.KeyboardEvent;
10
	import flash.events.MouseEvent;
11
	import flash.events.Event;
12
	
13
	import com.Element;
14
		
15
	public class Main extends Sprite
16
	{
17
		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function

18
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here

19
		private var markers_vector:Vector.<Object>; //the markers are held in here

20
		private var timer:Timer; 
21
		private var dead:Boolean;
22
		private var min_elements:int; //holds how many parts should the snake have at the beginning

23
		private var apple:Element; //Our apple

24
		private var space_value:Number; //space between the snake parts

25
		private var last_button_down:uint; //the keyCode of the last button pressed

26
		private var flag:Boolean; //is it allowed to change direction?

27
		private var score:Number;
28
		private var score_tf:TextField; //the Textfield showing the score

29
		
30
		public function Main() 
31
		{
32
			if(stage)
33
				addEventListener(Event.ADDED_TO_STAGE, init);
34
			else
35
				init();
36
		}
37
		
38
		private function init(e:Event = null):void
39
		{
40
			snake_vector = new Vector.<Element>;
41
			markers_vector = new Vector.<Object>;
42
			space_value = 2;
43
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!

44
			dead = false;
45
			min_elements = 1;
46
			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;

47
			apple.catchValue = 0; //pretty obvious

48
			last_button_down = Keyboard.RIGHT; //The starting direction of the snake (only change it if you change the 'for cycle' too.)

49
			score = 0;
50
			score_tf = new TextField();
51
			this.addChild(score_tf);
52
53
			//Create the first <min_elements> Snake parts

54
			for(var i:int=0;i<min_elements;++i)
55
			{
56
				snake_vector[i] = new Element(0x00AAFF,1,10,10);
57
				snake_vector[i].direction = "R"; //The starting direction of the snake

58
				if (i == 0)
59
				{
60
					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]

61
					attachElement(snake_vector[i],0,0,snake_vector[i].direction) 
62
					snake_vector[0].alpha = 0.7;
63
				}
64
				else
65
				{
66
					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
67
				}
68
			}
69
			
70
			placeApple(false);
71
			timer.addEventListener(TimerEvent.TIMER,moveIt);
72
			stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
73
			timer.start();
74
		}
75
		
76
		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
77
		{
78
			if (dirOfLast == "R")
79
			{
80
				who.x = lastXPos - snake_vector[0].width - space_value;
81
				who.y = lastYPos;
82
			}
83
			else if(dirOfLast == "L")
84
			{
85
				who.x = lastXPos + snake_vector[0].width + space_value;
86
				who.y = lastYPos;
87
			}
88
			else if(dirOfLast == "U")
89
			{
90
				who.x = lastXPos;
91
				who.y = lastYPos + snake_vector[0].height + space_value;
92
			}
93
			else if(dirOfLast == "D")
94
			{
95
				who.x = lastXPos;
96
				who.y = lastYPos - snake_vector[0].height - space_value;
97
			}
98
			this.addChild(who);
99
		}
100
		
101
		private function placeApple(caught:Boolean = true):void
102
		{
103
			if (caught)
104
				apple.catchValue += 10;
105
			
106
			var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
107
			var randomX:Number = Math.floor(Math.random()*boundsX);
108
			
109
			var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
110
			var randomY:Number = Math.floor(Math.random()*boundsY);
111
112
			apple.x = randomX * (apple.width + space_value);
113
			apple.y = randomY * (apple.height + space_value);
114
			
115
			for(var i:uint=0;i<snake_vector.length-1;i++)
116
			{
117
				if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
118
					placeApple(false);
119
			}
120
			if (!apple.stage)
121
				this.addChild(apple);
122
		}
123
		
124
		private function moveIt(e:TimerEvent):void
125
		{
126
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
127
			{
128
				placeApple();
129
				//show the current Score

130
				score += apple.catchValue;
131
				score_tf.text = "Score:" + String(score);
132
				//Attach a new snake Element

133
				snake_vector.push(new Element(0x00AAFF,1,10,10));
134
				snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction; //lastOneRichtung

135
				attachElement(snake_vector[snake_vector.length-1],
136
									  (snake_vector[snake_vector.length-2].x),
137
									  snake_vector[snake_vector.length-2].y,
138
									  snake_vector[snake_vector.length-2].direction);
139
			}
140
			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
141
			{
142
				gameOver();
143
			}
144
			
145
			for (var i:int = 0; i < snake_vector.length; i++)
146
			{
147
				if (markers_vector.length > 0)
148
				{
149
					for(var j:uint=0;j < markers_vector.length;j++)
150
					{
151
						if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
152
						{
153
							snake_vector[i].direction = markers_vector[j].type;
154
							if(i == snake_vector.length-1)
155
							{
156
								markers_vector.splice(j, 1);
157
							}
158
						}
159
					}
160
				}
161
				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
162
				{
163
					gameOver();
164
				}
165
			
166
				//Move the boy

167
				var DIRECTION:String = snake_vector[i].direction;
168
				switch (DIRECTION)
169
				{
170
					case "R" :
171
						snake_vector[i].x += snake_vector[i].width + space_value;
172
						break;
173
					case "L" :
174
						snake_vector[i].x -= snake_vector[i].width + space_value;
175
						break;
176
					case "D" :
177
						snake_vector[i].y += snake_vector[i].height + space_value;
178
						break;
179
					case "U" :
180
						snake_vector[i].y -= snake_vector[i].width + space_value;
181
						break;
182
				}
183
				
184
			}
185
						
186
			flag = true;
187
		}
188
		
189
		private function gameOver():void 
190
		{
191
			dead = true;
192
			timer.stop();
193
			while (this.numChildren)
194
				this.removeChildAt(0);
195
			timer.removeEventListener(TimerEvent.TIMER,moveIt);
196
			stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
197
			init();
198
		}
199
	
200
		private function directionChanged(e:KeyboardEvent):void 
201
		{
202
			var m:Object = new Object(); //MARKER OBJECT

203
204
			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
205
			{
206
				snake_vector[0].direction = "L";
207
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
208
				last_button_down = Keyboard.LEFT;
209
				flag = false;
210
			}
211
			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
212
			{
213
				snake_vector[0].direction = "R";
214
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
215
				last_button_down = Keyboard.RIGHT;
216
				flag = false;
217
			}
218
			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
219
			{
220
				snake_vector[0].direction = "U";
221
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
222
				last_button_down = Keyboard.UP;
223
				flag = false;
224
			}
225
			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
226
			{
227
				snake_vector[0].direction = "D";
228
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
229
				last_button_down = Keyboard.DOWN;
230
				flag = false;
231
			}
232
			markers_vector.push(m);
233
		}
234
		
235
	}
236
237
}

Step 13: Summing It All Up

Congratulations! You have just created a nice game. Now you can develop it further, and create a super apple or something. For that I recommend using another function called placeSuperApple() and a new class named SuperApple. Whenever you catch a super apple, the snakes parts could lengthen by three elements, perhaps. This could be set with setters/getters in the SuperApple class.

If you wish to do this, and you get stuck somewhere, just leave me a comment here.

Thank you for your time!

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.