By paddloPayday loans

Home > Getters and Setters, Mobile, State > The Grid State: Movement in Mobile

The Grid State: Movement in Mobile

GridStateRelMatrix Work

As our desktop screens soar to new heights in size, we find ourselves coping with smaller platforms for mobile viewing. To address this problem, developers simply move from one cell, grid or section to another. Each new cell becomes the full screen. For example, a 3 x 3 matrix has 9 cells (grid sections or just ‘grids’). The matrix can be seen as a “game universe” and each cell is the current “stage” for interaction. Some of you old-timers may remember the Star Trek games from the early days of desktop computing. Your starship would be an asterisk and you’d go from grid cell to grid cell chasing or chased by Romulans or Klingons, and when you came to the edge of a section you’d just change sections and with it came a new “stage.”

I have no idea of the programming behind those games, but the concept is perfectly clear. As you change sections, you get a new ‘map’ of that section. To get a sense of what’s going on, click the Play button to view the desktop version of the game, and then the Download button to get all of the code and objects in FLA and SWC files:
play buttondownload this sucker

Movement Rules

To get started building this game, I wanted to begin with simple movement rules. So I decided that movement would only be up, down, left, right. No diagonal movement allowed. For example, if an object were in Grid 7, it could go either to Grid 4 or Grid 8. No movement to Grid 5 or off the grid allowed. The opponent character is randomly placed into one of the grids, and the player moves from grid to grid until he finds the opponent. Figure 1 shows an example:

two sams

Figure 1: The player's samurai and opponent meet in a grid cell

The four Japanese characters are buttons for movement direction—North (up), South (down), West (left) and East (right). The buttons are simple ones and use a TouchEvent to slip in images behind the button set and characters. A single “Attack” button does nothing more than animate the characters [for now]. Eventually, I want the movement of the individual samurai objects to move across each grid and then jump to the next grid once he reaches a border. I also want the character to move faster along roads than non-road terrain. For example, in Figure 2, the character would be able to move speedily along roads to the next grid cell moving north, south or east, but moving west (left) movement would be slower because the lack of a road. Also, he would be able to move at road speed across the bridge (bottom right), but he would move across the river with no bridge at a much slower rate if at all.

Figure 2: Movement options (iPhone screen shot)

At this juncture, all I have is the movement through grids and not within a grid. In order to facilitate movement, I’ve employed a State Design Pattern (See Class and File Diagrams in earlier posts). Each state class shows the options. For example, State 5 is right in the middle of the grid (see grid map at the beginning of this post.)

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package 
{
	public class Grid5State implements IGrid
	{
		private var context:Context;
 
		public function Grid5State(context:Context)
		{
			this.context = context;
		}
 
		public function goWest():uint
		{
			context.setState(context.getGrid4State());
			return 4;
		}
		public function goEast():uint
		{
			context.setState(context.getGrid6State());
			return 6;
 
		}
		public function goNorth():uint
		{
			context.setState(context.getGrid2State());
			return 2;
		}
		public function goSouth():uint
		{
			context.setState(context.getGrid8State());
			return 8;
		}
	}
}

As you can see, all four directions are available to a player in Grid 5. However, if we look at Grid 9, the player can only go north or west as shown in the following class:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package 
{
	public class Grid9State implements IGrid
	{
		private var context:Context;
 
		public function Grid9State(context:Context)
		{
			this.context = context;
		}
 
		public function goWest():uint
		{
			context.setState(context.getGrid8State());
			return 8;
		}
		public function goEast():uint
		{
			return 9;
		}
		public function goNorth():uint
		{
			context.setState(context.getGrid6State());
			return 6;
		}
		public function goSouth():uint
		{
			return 9;
		}
	}
}

If the player tries to go south or east, the class simply returns to value of the current grid and the player goes nowhere. Likewise, no context is changed. A choice of north or west results in the appropriate change in context and the next grid cell is slipped in.

The Client, Context and State Interface

Like all State Design Patterns, the Client makes its calls through the context. In this case the Client is pretty big because it is also setting the state with buttons and whatnot. However, once in place, the navigation and attack buttons no longer need to be called because the backgrounds are slipped beneath them. You can see how this works in Client code:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package 
{
	import flash.display.Sprite;
	import flash.display.MovieClip;
	import fl.controls.Button;
	import flash.display.SimpleButton;
	import flash.events.TouchEvent;
	import flash.ui.Multitouch;
    import flash.ui.MultitouchInputMode;
 
	public class Client extends Sprite
	{
		private static var sam1:MovieClip=new Sam1();
		private static var sam2:MovieClip=new Sam2();
		private static var context:Context=new Context();
		private var grid:Sprite=new Sprite();
 
		private var foeGrid:uint = uint(Math.random() * (9 - 1) + 1);
		private var gridNow:uint;
 
		private static var attackBtn:Button=new Button();
 
		private static var westBtn:SimpleButton=new West();
		private static var eastBtn:SimpleButton=new East();
		private static var northBtn:SimpleButton=new North();
		private static var southBtn:SimpleButton=new South();
 
		private var grid1:Sprite=new Grid1();
		private var grid2:Sprite=new Grid2();
		private var grid3:Sprite=new Grid3();
		private var grid4:Sprite=new Grid4();
		private var grid5:Sprite=new Grid5();
		private var grid6:Sprite=new Grid6();
		private var grid7:Sprite=new Grid7();
		private var grid8:Sprite=new Grid8();
		private var grid9:Sprite=new Grid9();
 
		public function Client()
		{
			grid = this["grid" + context.randomStart()];
			addChildAt(grid,0);
			sam1.x = 350,sam1.y = 220;
			addChild(sam1);
			sam1.gotoAndStop(1);
			sam2.x = 400,sam2.y = 100;
			sam2.gotoAndStop(1);
			setControls();
		}
 
		private final function setControls():void
		{
			Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
			northBtn.x = 67,northBtn.y = 5;
			northBtn.addEventListener(TouchEvent.TOUCH_TAP,northNow);
			addChild(northBtn);
 
			westBtn.x = 5,westBtn.y = 80;
			westBtn.addEventListener(TouchEvent.TOUCH_TAP,westNow);
			addChild(westBtn);
 
			eastBtn.x = 150,eastBtn.y = 80;
			eastBtn.addEventListener(TouchEvent.TOUCH_TAP,eastNow);
			addChild(eastBtn);
 
			southBtn.x = 67,southBtn.y = 160;
			southBtn.addEventListener(TouchEvent.TOUCH_TAP,southNow);
			addChild(southBtn);
 
			//Animate
			attackBtn.width = 50,attackBtn.label = "Attack!";
			attackBtn.x = 15,attackBtn.y = 360;
			attackBtn.addEventListener(TouchEvent.TOUCH_TAP,attackNow);
			addChild(attackBtn);
		}
 
		private final function northNow(e:TouchEvent):void
		{
			if(gridNow==foeGrid)
			{
				removeChild(sam2);
			}
			removeChild(grid);
			gridNow = context.doNorth();
			if (gridNow==foeGrid)
			{
				addChild(sam2);
			}
			grid = this["grid" + gridNow];
			addChildAt(grid,0);
		}
 
		private final function westNow(e:TouchEvent):void
		{
			if(gridNow==foeGrid)
			{
				removeChild(sam2);
			}
			removeChild(grid);
			gridNow = context.doWest();
			if (gridNow==foeGrid)
			{
				addChild(sam2);
			}
			grid = this["grid" + gridNow];
			addChildAt(grid,0);
		}
 
		private final function eastNow(e:TouchEvent):void
		{
			if(gridNow==foeGrid)
			{
				removeChild(sam2);
			}
			removeChild(grid);
			gridNow = context.doEast();
			if (gridNow==foeGrid)
			{
				addChild(sam2);
			}
			grid = this["grid" + gridNow];
			addChildAt(grid,0);
		}
 
		private final function southNow(e:TouchEvent):void
		{
			if(gridNow==foeGrid)
			{
				removeChild(sam2);
			}
			removeChild(grid);
			gridNow = context.doSouth();
			if (gridNow==foeGrid)
			{
				addChild(sam2);
			}
			grid = this["grid" + gridNow];
			addChildAt(grid,0);
		}
 
		//Toggle button
		private static function attackNow(e:TouchEvent):void
		{
			if (e.target.label == "Attack!")
			{
				sam1.gotoAndPlay(1);
				sam2.gotoAndPlay(1);
				attackBtn.label = "Cease!";
			}
			else
			{
				sam1.gotoAndStop(1);
				sam2.gotoAndStop(1);
				attackBtn.label = "Attack!";
			}
		}
	}
}

One of the features in the Client class that I don’t like is the use of conditional statements in the calls to the different directions. I found that the “Foe Samurai” was a bit of a problem. He’s only allowed in the random cell that has been selected, and if I didn’t remove the sam2 object, he’d get over stacked whenever the player object (sam1) entered the cell. There’s probably some more elegant way to deal with that issue, but I didn’t see it.

I decided to use grid numbers—unsigned integers—in the hope of making the code more efficient. The grid number is knitted to a “grid” from a Sprite class stored in the Library. All grids are instantiated and assigned values at the outset and so they’re not re-instantiated; just added and removed from the stage–sort of like the magic trick where the table cloth is pulled quickly from a table and all the dishes and silverware remain on top.

The IGrid interface and Context class are pretty simple. The interface has methods for the four directions:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
package 
{
	//Interface
 
	public interface IGrid
	{
		function goWest():uint;
		function goEast():uint;
		function goNorth():uint;
		function goSouth():uint;
	}
}

Given the polymorphism that goes into each of these methods with the nine concrete states, it’s really nice to have this kind of interface. It’s a mix of flexibility, simplicity and possibilities.

The Context is also fairly simple, but it is at the heart of the State pattern because it keeps track of the current state. By doing so, no matter what state (grid cell) the samurai is in, not only does it know what cell it in, the connected concrete State class provides all the options available—without a single conditional statement.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package 
{
	//Context class
 
	class Context
	{
		private var grid1State:IGrid;
		private var grid2State:IGrid;
		private var grid3State:IGrid;
		private var grid4State:IGrid;
		private var grid5State:IGrid;
		private var grid6State:IGrid;
		private var grid7State:IGrid;
		private var grid8State:IGrid;
		private var grid9State:IGrid;
		private var stateNow:IGrid;
		private var rndNow:uint;
 
		public final function Context()
		{
			trace("States Set");
			grid1State = new Grid1State(this);
			grid2State = new Grid2State(this);
			grid3State = new Grid3State(this);
			grid4State = new Grid4State(this);
			grid5State = new Grid5State(this);
			grid6State = new Grid6State(this);
			grid7State = new Grid7State(this);
			grid8State = new Grid8State(this);
			grid9State = new Grid9State(this);
 
			rndNow=uint(Math.random() * (9-1) +1);
			stateNow = this["grid"+rndNow+"State"];
		}
 
		public final function randomStart():uint
		{
			return rndNow;
		}
		public final function doWest():uint
		{
			return stateNow.goWest();
		}
 
		public final function doEast():uint
		{
			return stateNow.goEast();
		}
 
		public final function doNorth():uint
		{
			return stateNow.goNorth();
		}
 
		public final function doSouth():uint
		{
			return stateNow.goSouth();
		}
 
		public final function setState(stateNow:IGrid):void
		{
			this.stateNow = stateNow;
		}
 
		public final function getGrid1State():IGrid
		{
			return this.grid1State;
		}
		public final function getGrid2State():IGrid
		{
			return this.grid2State;
		}
		public final function getGrid3State():IGrid
		{
			return this.grid3State;
		}
		public final function getGrid4State():IGrid
		{
			return this.grid4State;
		}
		public final function getGrid5State():IGrid
		{
			return this.grid5State;
		}
		public final function getGrid6State():IGrid
		{
			return this.grid6State;
		}
		public final function getGrid7State():IGrid
		{
			return this.grid7State;
		}
		public final function getGrid8State():IGrid
		{
			return this.grid8State;
		}
		public final function getGrid9State():IGrid
		{
			return this.grid9State;
		}
	}
}

Note that all of the grid state properties are typed as IGrid types—that’s Programming to the interface instead of the implementation. This provides the properties with far more flexibility and reduces binding. Likewise, the return types on the getter functions were all typed to the IGrid interface, and so changes are easy because objects are not bound to the implementation.

Some (More) Mobile Tweaks

In the original version of this app, I used the SimpleButton timeline with different states for Up/Over/Down/Hit. I noticed that the Over state did not work but the buttons did—sort of. However, the movement was gummy and I often had to tap the button several times. I removed all of the states except for Up state and the movement event handling went much smoother. I could probably just as well use Sprite objects for buttons and I may change that.

Another feature that I want to work on is a New Game button of some sort. Given the sparse real estate on a mobile device I may have to have some kind of popup menu with different options for a new game, attacking and defending. I’d like to clear out memory and re-set where the “opponent” samurai goes—fresh start the whole thing.

Finally, I’m going to have to integrate intra-cell and inter-cell movement. All movement within a cell will uses the current State pattern so that when the samurai reaches the border it will jump to the next cell. This kind of movement may require an additional set of state classes, but right now I’m not sure. Likewise, I want to set it up so that simultaneous movement of the player samurai and the robot samurai can attack/defend. The Symmetric Proxy Pattern (Chapter 13) can possibly be employed so that each combat movement is judged simultaneously.

Share
  1. archont
    February 13, 2012 at 12:39 pm | #1

    Hey,

    While I use design patterns of all sorts in desktop and web applications I’m afraid that the role of design patterns is limited in game development. That applies double so for mobile games.

    In game development the best cycle I’ve found is to first create a quick and dirty prototype – and by dirty I mean GOTO dirty, if that speeds up the workflow. Once the prototype is fun and playable comes refactoring. But since game logic is pretty convoluted and messy you never know which way you’ll want to expand the code. Maybe you’ll want to completely rewrite arrows from a vector of data objects (the C equivalent of struct) to classes that extend PhysicsTask. Sometimes proper coding practices get in the way of optimization – function calls are costly in AS3.

    The important thing – you need to make it clear that what you’re doing doesn’t apply to the Real World. This kind of overengineering at the prototype level gets in the way of creating the actual fun in the game. It’s a good way to show specific design patterns on an interactive example that looks interesting but this is certainly not the way to create games, ever.

    Cheers

    • William B. Sanders
      February 13, 2012 at 2:13 pm | #2

      Hi Archont,

      First of all, it does not matter whether the device is mobile or desktop as far as Design Patterns are concerned. Good or bad algorithms can go into good or bad structures.(See http://www.as3dp.com/2011/03/beginners-oop-design-patterns-in-actionscript-3-0-post-1-rolls-royces-in-the-junk-yard/) There’s no over-engineering going on, and if you can show me a better way to create a design for self-aware state, I’d like to see it.

      Second, there’s no problem starting with a Q&D prototype. I usually do as well, and then I look to see a way that I could do it so that there’s loose binding and easy to update. Like you, I’ve pulled out Occam’s Razor when it comes to writing code for mobile.

      I suppose it depends on which Real World you live in. Design Patterns were developed for Programming in the Read World where the size of the programs are big and changeable. None of us think that the limitations on these early mobile devices will be restrictive forever.

      Let’s see a better way to create inter-cell movement in a grid.

      Kindest regards,
      Bill

  2. archont
    February 13, 2012 at 2:50 pm | #3

    >First of all, it does not matter whether the device is mobile or desktop as far as Design Patterns are concerned. Good or bad algorithms can go into good or bad structures.

    Algorithms are separate from design patterns, are they not? Games require highly optimized and often very special-purpose algorithms. But design patterns, in my understanding, are more about creating higher levels of abstraction to better conceptualize the relationships in the program in order to facilitate quicker coding.

    However the downside of design patterns is that they lock down the code (meaning by setting out how objects interact using a specific pattern) it’s hard to quickly adapt the objects to use a different relationship without doing a full nuke&pave. But that’s not the real problem, a lot of the code can be taken for granted and won’t be changed, so patterns are appropriate there.

    The real problem comes with efficiency. Sure, some patterns like the object pool, vectors, maps, signals and so on are great tools but high-level patterns need to be used with caution. Using design patterns for rarely executed code is fine. A game menu or inventory screen is a great place to use patterns. The main meat of the engine, the rendering code and the frame loop are not.

    As for creating better code – sure, a few examples of what I’d do:

    1) remove the repetition in westnow, eastnow, northnow and southnow. I don’t like pasting code. The map changing logic would be moved to another function, moveAvatar(deltaX:int, deltaY:int=0):void

    The event handlers would just call a variation of moveAvatar(-1) or moveAvatar(0,1);

    2) go further with no. 1 and remove all game logic from event handler functions. Imagine I have to implement a cutscene where the PC attacks an NPC. Would I have to do an attackNow(theEventIJustCreated); ? It’s my own coding practice to have event handlers call functions that do the real action. Since the event handling model in Flash is glacially slow anyway I might as well afford the luxury of decoupling. In time-critical situations I use nativesignals.

    3) Use an Array or Vector for storing grids. Support for various map sizes out of the box.

    I might actually go and redo this code if I have a few minutes today.

    • William B. Sanders
      February 13, 2012 at 5:14 pm | #4

      Hi again Archont,

      Thanks for your reply. You made several good points. Let me clarify a couple of things. First, the “attack” button and actions are just “place holders” until I decide what I want to do with the combat portion. In fact the entire Client class is just a platform for making requests. Second, changing anything in the Client for calls to the State machine does not change the State pattern, and all of your points on event handlers are quite helpful.

      Where we may disagree is the role of the structure and highly specialized algorithms (or not-so-specialized algorithms). The idea of programming to the interface instead of the implementation implies flexibility in methods. So, I would think that highly specialized algorithms that implement an interface would both maintain the structure without crashing the other parts of the program. The alternative would be a delicately balanced set of algorithms strung together by conditional statements, which have their own set of issues. Changing an algorithm in a conditional based system would seem to be far more tricky to maintain and/or extend than one in a State Pattern where there are no conditional statements (outside of the Client). You could use array or vector objects for storage, but I’m not sure why. I’ve used both in State Patterns, but I did not see a use for them here.

      As for a better event handler, I’m all ears. I had to cut back on anything that moved in the SimpleButton objects to improve Touch responses, but like you said, slugs move faster than AS event handlers.

      So if you have the few minutes, I’d like to see your redo.

      Kindest regards,
      Bill

  1. No trackbacks yet.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>