Abstracting the World of Mr Nom Model View Controller

If you are a long-time coder, you've probably heard about design patterns. They are more or less strategies to design your code given a scenario. Some of them are academic, and some have uses in the real world. For game development we can borrow some ideas from the Model-View-Controller (MVC) design pattern. It's often used by the database and web community to separate the data model from the presentation layer and the data manipulation layer. We won't strictly follow this design pattern, but rather adapt it in a simpler form.

So what does this mean for Mr. Nom? First of all we need an abstract representation of our world that is independent of any bitmaps, sounds, framebuffers, or input events. Instead we'll model Mr. Nom's world with a few simple classes in an object-oriented manner. We'll have a class for the stains in the world and a class for Mr. Nom himself. Mr. Nom is composed of a head and tail parts, which we'll also represent by a separate class. To tie everything together, we'll have an all-knowing class representing the complete world of Mr. Nom, including the stains and Mr. Nom himself. All this represents the model part of MVC.

The view in MVC will be the code that is responsible for rendering the world of Mr. Nom. We'll have a class or a method that takes the class for the world, reads its current state, and renders it to the screen. How it is rendered does not concern the model classes, though, and this is the most important lesson to take away from MVC. The model classes are independent of everything, but the view classes and methods depend on the model classes.

Finally we have the controller in MVC. It tells the model classes to change their state based on things like user input or the time ticking away. The model classes provide methods to the controller (e.g., with instructions like "Turn Mr. Nom to the left."), which the controller can then use to modify the state of the model. We don't have any code in the model classes that directly accesses things like the touchscreen or the accelerometer. This way we can keep the model classes clear of any external dependencies.

This may sound complicated, and you may be wondering why we do things this way. However, there are a lot of benefits to this approach. We can implement all our game logic without having to know about graphics, audio, or input devices. We can modify the rendering of the game world without having to change the model classes themselves. We could even go so far as to exchange a 2D world renderer with a 3D world renderer. We can easily add support for new input devices by using a controller. All it does is translate input events to method calls of the model classes. Want to turn Mr. Nom via the accelerometer? No problem — read the accelerometer values in the controller and translate them to a "turn Mr. Nom left" or "turn Mr. Nom right" method call on the model of Mr. Nom. Want to add support for the Zeemote? No problem, just do the same as in the case of the accelerometer! The best thing about using controllers is that we don't have to touch a single line of Mr. Nom's code to make all this happen.

Let's start by defining Mr. Nom's world. To do this we'll break away from the strict MVC pattern a little and use our graphical assets to illustrate the basic ideas. This will also

Figure 6-5 shows the game screen with the world of Mr. Nom superimposed on it, in the form of a grid.

help us to implement the view component later on (rendering Mr. Nom's abstract world in pixels).

Figure 6-5 shows the game screen with the world of Mr. Nom superimposed on it, in the form of a grid.

[0

?

\

/

(S

12)

©

©

Figure 6-5. Mr. Nom's world superimposed onto our game screen

Figure 6-5. Mr. Nom's world superimposed onto our game screen

Notice that Mr. Nom's world is confined to a grid of 10! 13 cells. We address cells in a coordinate system with the origin in the upper-left corner at (0,0) spanning to the bottom-right corner at (9,12). Any part of Mr. Nom must be in one of these cells, and thus must have integer x- and y-coordinates within this world. The same is true for the stains in this world. Each part of Mr. Nom fits into exactly one cell of 1! 1 units. Note that the type of units doesn't matter—this is our own fantasy world free from the shackles of the SI system or pixels!

Mr. Nom can't travel outside this small world. If he passes an edge he'll just come out the other end, and all his parts will follow. (We have the same problem here on earth by the way—go in any direction for long enough and you'll come back to your starting point). Mr. Nom can also only advance cell by cell. All his parts will always be at integer coordinates. He'll never, for example, occupy two and a half cells.

NOTE: As stated earlier, what we use here is not a strict MVC pattern. If you are interested in the real definition of an MVC pattern, I suggest taking reading Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamm, Richard Helm, Ralph Johnson, and John M. Vlissides (aka the Gang of Four) (Addison-Wesley, 1994). In their book, the MVC pattern is knows as the Observer pattern.

The Stain Class

The simplest object in Mr. Nom's world is a stain. It just sits in a cell of the world, waiting to be eaten. When we designed Mr. Nom, we created three different visual representations of a stain. The type of a stain does not make a difference in Mr. Nom's world, but we'll include it in our Stain class anyway. Listing 6-8 shows the Stain class.

Listing 6-8. Stain.java package com.badlogic.androidgames.mrnom;

public class Stain {

public static final int TYPE_1 = 0; public static final int TYPE_2 = l; public static final int TYPE_3 = 2; public int x, y; public int type;

public Stain(int x, int y, int type) { this.x = x; this.y = y; this.type = type;

The Stain class defines three public static constants that encode the type of a stain. Each Stain has three members, x- and y-coordinates in Mr. Nom's world, and a type, which is one of the constants defined earlier. To make our code simple, we don't include getters and setters, as is common practice. We finish the class off with a nice constructor that allows us to instantiate a Stain instance easily.

One thing to notice is the lack of any connection to graphics, sound, or other classes. The Stain class stands on its own, proudly encoding the attributes of a stain in Mr. Nom's world.

The Snake and SnakePart Classes

Mr. Nom is like a moving chain, composed of interconnected parts that will move along when we pick one part and drag it somewhere. Each part occupies a single cell in Mr. Nom's world, much like a stain. In our model, we do not distinguish between the head and tail parts, so we can have a single class that represents both types of parts of Mr.

Nom. Listing 6-9 shows called SnakePart class, which is used to define both parts of Mr. Nom.

Nom. Listing 6-9 shows called SnakePart class, which is used to define both parts of Mr. Nom.

Listing 6-9. SnakePart.java package com.badlogic.androidgames.mrnom;

public class SnakePart { public int x, y;

public SnakePart(int x, int y) { this.x = x; this.y = y;

This is essentially the same as the Stain class—we just removed the type member. The first really interesting class of our model of Mr. Nom's world is the Snake class. Let's think about what it has to be able to do:

It must store the head and tail parts.

It must know which way Mr. Nom is currently heading.

It must be able to grow a new tail part when Mr. Nom eats a stain.

It must be able to move by one cell in the current direction.

The first and second items are easy. We just need a list of SnakePart instances—the first part in that list being the head and the other parts making up the tail. Mr. Nom can move up, down, left, and right. We can encode that with some constants and store his current direction in a member of the Snake class.

The third item isn't all that complicated either. We just add another SnakePart to the list of parts we already have. The question is, at what position we should add that part? It may sound surprising, but we give it the same position as the last part in the list. The reason for this becomes clearer when we look at how we can implement the last item on the preceding list: moving Mr. Nom.

Figure 6-6 shows Mr. Nom in his initial configuration. He is composed of three parts, the head, at (5,6), and two tail parts, at (5,7) and (5,8).

Figure 6-6. Mr. Nom in his initial configuration

The parts in the list are ordered beginning with the head and ending at the last tail part. When Mr. Nom advances by one cell, all the parts behind his head have to follow. However, Mr. Nom's parts might not be laid out in a straight line, as in Figure 6-6, so simply shifting all the parts in the direction Mr. Nom advances is not enough. We have to do something a little more sophisticated.

We need to start at the last part in the list, as counterintuitive as that may sound. We move it to the position of the part before it, and we repeat this for all other parts in the list except for the head, as there's no part before it. In the case of the head, we check which direction Mr. Nom is currently heading and modify the head's position accordingly. Figure 6-7 illustrates this with a bit more complicated configuration of Mr. Nom.

Figure 6-7. Mr. Nom advancing and taking his tail with him

This movement strategy works well with our eating strategy. When we add a new part to Mr. Nom, it will stay at the same position as the part before it the next time Mr. Nom moves. Also note that this will allow us to easily implement wrapping Mr. Nom to the other side of the world if he passes one of the edges. We just set the head's position accordingly, and the rest is done automatically.

With all this information we can now implement the Snake class representing Mr. Nom. Listing 6-10 shows the code.

Listing 6-10. Snake.java; Mr. Nom in Code package com.badlogic.androidgames.mrnom;

import java.util.ArrayList; import java.util.List;

public class Snake {

public static final int UP = 0; public static final int LEFT = l; public static final int DOWN = 2; public static final int RIGHT = 3;

public List<SnakePart> parts = new ArrayList<SnakePart>(); public int direction;

We start off by defining a couple of constants that encode the direction of Mr. Nom. Remember that Mr. Nom can only turn left and right, so the way we define the constants' values is critical. It will later allow us to easily rotate the direction by plus and minus 90 degrees by just incrementing and decrementing the current direction constant by one.

Next we define a list called parts that holds all the parts of Mr. Nom. The first item in that list is the head, and the other items are the tail parts. The second member of the Snake class holds the direction Mr. Nom is currently heading in.

direction = UP;

parts.add(new SnakePart(5, 6)); parts.add(new SnakePart(5, 7)); parts.add(new SnakePart(5, 8));

In the constructor, we set up Mr. Nom to be composed of his head and two additional tail parts, positioned more or less in the middle of the world, as shown previously in Figure 6-6. We also set the direction to Snake.UP so that Mr. Nom will advance upward by one cell the next time he's asked to advance.

public void turnLeft() { direction += 1; if(direction > RIGHT) direction = UP;

public void turnRight() {

direction -= 1; if(direction < UP) direction = RIGHT;

The methods turnLeft() and turnRight() just modify the direction member of the Snake class. For a turn left we increment it by one, and for a turn right we decrement it. We also have to make sure that we wrap around if the direction value gets outside the range of the constants we defined earlier.

SnakePart end = parts.get(parts.size()-1); parts.add(new SnakePart(end.x, end.y));

Next up is the eat() method. All it does is add a new SnakePart to the end of the list; this new part will have the same position as the current end part. Next time Mr. Nom advances, those to overlapping parts will move apart, as discussed earlier.

public void advance() {

SnakePart head = parts.get(0);

int len = parts.size() - 1; for(int i = len; i > 0; i--) {

SnakePart before = parts.get(i-1); SnakePart part = parts.get(i); part.x = before.x; part.y = before.y;

if(head.x < 0) head.x = 9; if(head.x > 9) head.x = 0; if(head.y < 0)

The next method, advance(),implements the logic illustrated in Figure 6-7. First we move each part to the position of the part before it, starting with the last part. We exclude the head from this mechanism. Then we move the head according to Mr. Nom's current direction. Finally we perform some checks to make sure Mr. Nom doesn't go outside his world. If that's the case we just wrap him around so that he comes out at the other side of the world.

public boolean checkBitten() { int len = parts.size(); SnakePart head = parts.get(o); for(int i = 1; i < len; i++) { SnakePart part = parts.get(i); if(part.x == head.x && part.y == head.y) return true;

return false;

The final method, checkBitten(), is a little helper method that checks if Mr. Nom has bitten his tail. All it does is check that no tail part is at the same position as the head. If that's the case, Mr. Nom will die and the game will end.

The World Class

The last of our model classes is called World. The World class has a couple of tasks to fulfill:

Keeping track of Mr. Nom (in the form of a Snake instance) as well as the Stain that dropped on the World. There will only ever be a single stain in our world.

Providing a method that will update Mr. Nom in a time-based manner (e.g., he should advance by one cell every 0.5 seconds). This method will also check if Mr. Nom has eaten a stain or has bitten himself.

Keeping track of the score, which is basically just the number of stains eaten so far times 10.

Increasing the speed of Mr. Nom after every ten stains he's eaten. That will make the game a little bit more challenging.

Keeping track of whether Mr. Nom is still alive. We'll use this to determine whether the game is over later on.

Creating a new stain after Mr. Nom eats the current one (a subtle but important and surprisingly complex task).

There are only two items on this task list that we haven't discussed yet: updating the world in a time-based manner and placing a new stain.

Time-Based Movement of Mr. Nom

In Chapter 3 we talked about time-based movement. This basically means that we define velocities of all our game objects, measure the time that has passed since the last update (aka the delta time), and advance the objects by multiplying their velocity by the delta time. In the example given in Chapter 3, we used floating-point values to achieve this. Mr. Nom's parts have integer positions, though, so we need to figure out how to advance the objects in this scenario.

Let's first define the velocity of Mr. Nom. The world of Mr. Nom has time, and we measure it in seconds. Initially Mr. Nom should advance by one cell every 0.5 seconds. All we need to do is keep track of how much time has passed since we last advanced Mr. Nom. If that accumulated time goes over our 0.5-second threshold, we call the Snake.advance() method and reset our time accumulator. Where do we get those delta times from? Remember the Screen.update() method. It gets the frame delta time. We just pass that on to the update method of our World class, which will do the accumulation. To make the game more challenging, we will decrease that threshold by 0.05 seconds each time Mr. Nom eats another ten stains. We have to make sure, of course, that we don't reach a threshold of 0, or else Mr. Nom would travel at infinite speed — something Einstein wouldn't take to kindly.

Placing Stains

The second issue we have to solve is how to place a new stain when Mr. Nom has eaten the current one. It should appear in a random cell of the world. So, we could just instantiate a new Stain with a random position, right? Sadly it's not that easy.

Imagine Mr. Nom taking up a considerable number of cells. The probability that the stain would be placed in a cell that's already occupied by Mr. Nom will increase the bigger Mr. Nom gets. We thus have to find a cell that is currently not occupied by Mr. Nom. Easy again, right? Just iterate over all cells and use the first one that is not occupied by Mr. Nom.

Well, again, that's a little suboptimal. If we started our search at the same position, the stain wouldn't be placed randomly. Instead we'll start at a random position in the world, scan all cells until we reach the end of the world, and then scan all cells above the start position if we haven't found a free cell yet.

How do we check whether a cell is free? The naïve solution would be to go over all cells, take each cell's x- and y-coordinates, and check all the parts of Mr. Nom against those coordinates. We have 10 ! 13 = 130 cells, and Mr. Nom can take up 55 cells. That would be 130 ! 55 = 7,150 checks! Granted, most devices could handle that, but we can do better.

We'll create a two-dimensional array of booleans where each array element represents a cell in the world. When we have to place a new stain, we first go through all parts of Mr. Nom and set those elements that are occupied by a part in the array to true. We then simply choose a random position from which we start scanning until we find a free cell that we can place the new stain in. With Mr. Nom being composed of 55 parts, that would take 130 + 55 = 185 checks. That's a lot better!

Determining When the Game Is Over

There's one last thing we have to think of: what if all cells are taken up by Mr. Nom? In that case, the game would be over, as Mr. Nom would officially become the whole world. Given that we add 10 to the score each time Mr. Nom eats a stain, the maximally achievable score is (10 ! 13 - 3) ! 10 = 1,000 points (remember, Mr. Nom starts off with three parts already).

Implementing the World Class

Phew, we have a lot of stuff to implement, so let's get going. Listing 6-11 shows the code of the World class.

Listing 6-11. World.java package com.badlogic.androidgames.mrnom;

import java.util.Random;

public class World {

static final int WORLD_WIDTH = l0; static final int WORLD_HEIGHT = l3; static final int SCORE_INCREMENT = l0; static final float TICK_INITIAL = 0.5f; static final float TICK_DECREMENT = 0.05f;

public Snake snake; public Stain stain; public boolean gameOver = false;; public int score = 0;

boolean fields[][] = new boolean[WORLD_WIDTH][WORLD_HEIGHT];

Random random = new Random();

float tickTime = 0;

static float tick = TICK_INITIAL;

As always, we start off by defining a couple of constants—in this case, the world's width and height in cells, the value we increment the score with each time Mr. Nom eats a stain, the initial time interval used to advance Mr. Nom (called a tick), and the value we decrement the tick each time Mr. Nom has eaten ten stains in order to speed up things a little.

Next we have some public members that hold a Snake instance, a Stain instance, a boolean that stores whether the game is over, and the current score.

We define another four package private members: the 2D array we'll use to place a new stain; an instance of the Random class, through which we'll produce random numbers to place the stain and generate its type; the time accumulator variable, tickTime, to which we'll add the frame delta time; and the current duration of a tick, which defines how often we advance Mr. Nom.

In the constructor we create an instance of the Snake class, which will have the initial configuration shown in Figure 6-6. We also place the first random stain via the placeStain() method.

private void placeStain() {

for (int y = 0; y < WORLDJHEIGHT; y++) { fields[x][y] = false;

int len = snake.parts.size(); for (int i = 0; i < len; i++) {

SnakePart part = snake.parts.get(i); fields[part.x][part.y] = true;

int stainX = random.nextInt(WORLD_WIDTH); int stainY = random.nextInt(wORLD_HEIGHT); while (true) {

if (stainX >= WORLD_WIDTH) { stainX = 0; stainY += 1;

stain

new Stain(stainX, stainY, random.nextInt(3));

The placeStain() method implements the placement strategy discussed earlier. We start off by clearing the cell array. Next we set all the cells occupied by parts of the snake to true. Finally we scan the array for a free cell starting at a random position. Once we have found a free cell, we create a Stain with a random type. Note that if all cells are occupied by Mr. Nom, then the loop will never terminate. We'll make sure that will never happen in the next method.

public void update(float deltaTime) { if (gameOver) return;

tickTime += deltaTime;

while (tickTime > tick) { tickTime -= tick; snake.advance(); if (snake.checkBitten()) { gameOver = true; return;

SnakePart head = snake.parts.get(o); if (head.x == stain.x && head.y == stain.y) { score += SCORE_INCREMENT; snake.eat();

if (snake.parts.size() == WORLD_WIDTH * WORLD_HEIGHT) { gameOver = true; return; } else {

placeStain();

if (score % 100 == 0 && tick - TICK_DECREMENT > 0) { tick -= TICK_DECREMENT;

The update() method is responsible for updating the World and all the objects in it based on the delta time we pass to it. This method will be called each frame in the game screen so that the World is updated constantly. We start off by checking whether the game is over. If that's the case, then we don't need to update anything, of course. Next we add the delta time to our accumulator. The while loop will use up as many ticks as have been accumulated (e.g., when tickTime is 1.2 and one tick should take 0.5 seconds, we can update the world twice, leaving 0.2 seconds in the accumulator). This is called a fixed-time-step simulation.

In each iteration we first subtract the tick interval from the accumulator. Next we tell Mr. Nom to advance. We check if he has bitten himself afterward, and set the game-over flag if that's the case. Finally we check whether Mr. Nom's head is in the same cell as the stain. If that's the case we increment the score and tell Mr. Nom to grow. Next we check whether Mr. Nom is composed of as many parts as there are cells in the world. If that's the case, the game is over and we return from the function. Otherwise we place a new stain with the placeStain() method. The last thing we do is check whether Mr. Nom has just eaten ten more stains. If that's the case and our threshold is above zero, we decrease it by 0.05 seconds. The tick will be shorter and thus make Mr. Nom move faster.

This completes our set of model classes. The last thing we need to implement is the game screen!

0 0

Post a comment