Geisha Studios

iTouchBalls

Revision History
Revision 34b7522b4f97 2011-03-28 jfita
atangle is now using a new style for directives which don't collide with XML tags. I had to update all games and programs as well in order to use the new directive syntax.
Revision 6cc909c0b61d 2011-03-07 jfita
Added the comments section.
Revision 63a438521347 2011-03-07 jfita
Added a missing snippet name.
Revision 158b4beced10 2011-03-07 jfita
Reworded some of the paragraphs to easer understanding (I hope)
Revision e73be3b3a6c9 2011-03-07 jfita
Fixed a couple of source tags.
Revision 882f083478ee 2011-03-06 jfita
Added the status text at the bottom.
Revision 796441ded6d8 2011-03-06 jfita
Added the drawing of the next level status.
Revision 13761768abb1 2011-03-06 jfita
Added the check whether the level is finished.
Revision 307a1a94ef1f 2011-03-05 jfita
Using 15FPS instead of 30.
Revision dd168af98daf 2011-03-04 jfita
Added the explosion on tap.
Revision 1e548eb394bb 2011-03-04 jfita
Added the collision function between balls and explosions.
Revision 130cee844f04 2011-03-03 jfita
Added the Explosion class.
Revision 1a569a15a4ab 2011-02-26 jfita
Added the creation of balls on tap.
Revision 4f9c425a068e 2011-02-21 jfita
Added the Level class and the initialization of the levels NSArray.
Revision df31631cf0a4 2011-02-21 jfita
Added the drawing of balls.
Revision a54aa5df6113 2011-02-19 jfita
The array of balls is now NSMutableArray instead.
Revision b6e068d41ab4 2011-02-19 jfita
Added the balls array and removed the starting variable.
Revision 4788a8fb383c 2011-02-19 jfita
Added the Ball class.
Revision f856f7fbcbbd 2011-02-19 jfita
Added the 'started' property to BallsView.
Revision 7a82ef52fc05 2011-02-19 jfita
Added the main loop.
Revision 25bee09c8a1f 2011-02-19 jfita
Fixed the section level for 'plist file'.
Revision da426a4818c0 2011-02-19 jfita
Fixed the section level of 'Main' section.
Revision 7477ba944a7f 2011-02-19 jfita
Added the empty BallsView. Changed the window's frame to use bounds instead.
Revision 02627d79c046 2011-02-19 jfita
Added the ITouchBall class inside the 'main' section, as it is really "an extension" of it.
Revision c6601b32b5c1 2011-02-19 jfita
Fixed mistakes in plist file for iTouchBalls.
Revision 08a069444f82 2011-02-19 jfita
Fixed a typo in iTouchBalls' main.m
Revision 79730c7a3282 2011-02-19 jfita
Added the main source code to iTouchBalls.
Revision d5378c1f4ba5 2011-02-19 jfita
Added the prefix header to iTouchBalls.
Revision 25ec21dcf298 2011-02-19 jfita
Added the license text to iTouchBalls.
Revision e29bfff26ecb 2011-02-18 jfita
Added the initial iTouchBall projects and the plist file.

Table of Contents

Introduction
Download
Code
The View
Main Loop
Status Text
Levels
Balls
Explosions
Tap, Tap, Tapping
Comments

Introduction

iTouchBalls is an iOS port of Balls!, a very simple puzzle game in which the player must remove an specific number of bouncing balls from the screen by starting a chain reaction of explosions. In this port, instead of using the mouse, the player must tap on the screen to start the first explosion and trigger the chain reaction.

Being an iOS application, iTouchBalls is written in Objective-C.

Download

There is no precompiled version of iTouchBalls available because I highly doubt that Apple would allow this game in his App Store. However, the source code, extracted with atangle, and an Xcode project file are available at the following URL:

http://www.geishastudios.com/download/balls-ios.zip

Also, for those interesed in this port’s original AsciiDoc document, the latest version is always available at:

http://dev.geishastudios.com/literate/src/tip/balls-ios/

Code

The View

Even tough Apple recommends to separate the application in three different responsibilities using a Model-View-Controller (MVC) pattern, I’ve decided to put everything in a single UIView derived class which I named BallsView.

            
<<BallsView.h>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // #import <UIKit/UIKit.h> @interface BallsView: UIView { <<BallsView interface>> } @end
            
<<BallsView.m>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // <<BallsView includes>> #import "BallsView.h" #import "Ball.h" #import "Explosion.h" #import "Level.h" @implementation BallsView <<view constructor>> <<update function>> <<draw function>> <<tap function>> - (void)dealloc { <<release main loop>> <<release BallsView interface>> [super dealloc]; } @end

This class must be initialized by calling the UIView inherited initWithFrame function and passing the frame with the view’s size.

            
<<create view>>= BallsView *view = [[BallsView alloc] initWithFrame:frame];

This method, besides calling its parent’s method and initialize the object’s interface, sets the view’s background color as well as the application’s main loop which will update the game’s logic and force a redraw of the view.

            
<<view constructor>>= - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { <<init BallsView interface>> self.backgroundColor = [UIColor colorWithRed:42.0f / 255.0f green:84.0f / 255.0f blue:128.0f / 255.0f alpha:1.0f]; <<set up main loop>> } return self; }

Once the view is created, I can set it to the application’s window to make the view visible. The function that I am using to set the view, setContentView, is not documented and, in fact, XCode will complain that UIWindow might not respond to this message. The method exists and I use it because I remember using it from regular OS X development. Unfortunately, I wasn’t able to set the view using any other method.

            
<<set view in window>>= [window setContentView:view];

Main Loop

Differing from traditional imperative applications, an iOS application does not have the common even loop in which the application can check for incoming events and act upon them. This is all handled by the UIApplicationMain function, among other initialization and set up routines. Unfortunately, for games, this means that I no longer have the option to add the calls to the methods that update the game’s internal logic each frame in that loop. Instead, I need to find a way to call my update function periodically. This is where NSTimer comes to help me.

With NSTimer I can schedule a call to a function of my choice at some point in the future and this call will be repeated until the timer gets invalidated. Given that I want to update the game’s logic at an speed of 15 frames per second, I need to call the update function every 66 milliseconds (1000 / 15.)

            
<<set up main loop>>= mainLoop = [NSTimer scheduledTimerWithTimeInterval:0.066f target:self selector:@selector(update) userInfo:nil repeats:YES];

The mainLoop variable is defined in the BallsView class and released when the class is deallocated.

            
<<BallsView interface>>= NSTimer *mainLoop;
            
<<release main loop>>= [mainLoop release];

The function referenced in the selector parameter is the function that will be called repeatedly every 0.066 seconds. This function’s responsibility is to update the balls and the explosions in the game as well as to check whether the game is done. Once these are updated, I trigger a redraw by telling iOS that the view needs to display again.

            
<<update function>>= - (void)update { <<update balls>> <<check if there is any explosion>> <<update explosions>> <<check whether level is over>> [self setNeedsDisplay]; }

The drawing is performed in drawRect:. Here I need to get the current graphic’s context and then simply draw the balls, the explosions, and the status text onto this context, if there is any ball on the level. If there are no balls yet, I need to tell the player to tap the screen to start the level.

            
<<draw function>>= - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); if ([balls count] == 0) { <<draw next level label>> } else { <<draw balls>> <<draw explosions>> <<draw status>> } CGContextStrokePath(context); }

To draw the label, I am going to use ‘NSString` drawAtPoint:withFont which prints the text at the given point on the current context — the one I’ve got when called UIGraphisGetCurrentContext — with the specified font. As I want this text to be drawn at the view’s center, fist I need to compute the position where to draw the text. Fortunately, NSString also has the sizeWithFont: function that returns the rendered string’s size if it were drawn using the passed font. Knowing the text’s size as well as the view’s size, the position on there to start drawing is half the view’ size less half the text size, in both vertical and horizontal.

            
<<draw next level label>>= CGRect viewRect = [self bounds]; UIFont *font = [UIFont boldSystemFontOfSize:20.0]; CGSize size = [nextLevelLabel sizeWithFont:font]; CGPoint pos = CGPointMake(viewRect.origin.x + (viewRect.size.width - size.width) / 2, viewRect.origin.y + (viewRect.size.height - size.height) / 2); [[UIColor whiteColor] set]; [nextLevelLabel drawAtPoint:pos withFont:font];

The next level label is just an string which prompts the user to tap on the screen to start the level.

            
<<BallsView interface>>= NSString *nextLevelLabel;
            
<<init BallsView interface>>= nextLevelLabel = @"Tap to Start Next Level";

Status Text

The status text is an string with the number of balls removed from the level and the target balls to remove. I need to get the current level and the remaining balls on the screen to know the number of removed balls, that I can get with a simple subtraction.

            
<<draw status>>= Level *level = [levels objectAtIndex:currentLevel]; int removedBalls = level.balls - [balls count]; NSString *status = [NSString stringWithFormat:@"%d/%d", removedBalls, level.target];

This string needs to be drawn at the screen’s bottom right corner. Again, I am using NSString sizeWithFont: to know the area used by the text with the intended font but this time I have to subtract the whole text’s size from the view’s size to render the text at the bottom’s right corner.

            
<<draw status>>= CGRect viewRect = [self bounds]; UIFont *font = [UIFont systemFontOfSize:15.0]; CGSize size = [status sizeWithFont:font]; CGPoint pos = CGPointMake(viewRect.origin.x + viewRect.size.width - size.width, viewRect.origin.y + viewRect.size.height - size.height); [[UIColor whiteColor] set]; [status drawAtPoint:pos withFont:font];

Levels

The levels only requires two pieces of information: the number of balls bouncing on the screen and the minimum number of balls to remove, known as target. These two never change during the lifetime of a level and thus can be set read-only.

            
<<Level.h>>= // // iTouchBalls - A simple game with balls // Copyright (c) 2011 Jordi Fita // <<license>> // #import <Foundation/Foundation.h> @interface Level: NSObject { unsigned int balls; unsigned int target; } @property (readonly) unsigned int balls; @property (readonly) unsigned int target;

These two values are initialized in the Level’s initWithBalls:target: method.

            
<<Level.h>>= - (id)initWithBalls:(unsigned int)balls target:(unsigned int)target;
            
<<Level.m>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita // <<license>> // #import "Level.h" @implementation Level @synthesize balls; @synthesize target; - (id)initWithBalls:(unsigned int)balls_ target:(unsigned int)target_ { self = [super init]; if (self) { balls = balls_; target = target_; } return self; }

The levels are initialized by the BallsView when created and they are stored in an NSArray object. I also keep a variable to know in which level the game is currently. This variable is initially set to the first level, which is 0.

            
<<BallsView interface>>= NSArray *levels; unsigned int currentLevel;

For convenience, I am going to add a class method to Level that creates the level and calls autorelease on it, thus freeing me to have to keep the references and releasing them when adding them to the array.

            
<<Level.h>>= + (id)levelWithBalls:(unsigned int)balls target:(unsigned int)target; @end
            
<<Level.m>>= + (id)levelWithBalls:(unsigned int)balls target:(unsigned int)target { Level *level = [[Level alloc] initWithBalls:balls target:target]; return [level autorelease]; } @end

With this method in place, now I can create the levels thus:

            
<<init BallsView interface>>= levels = [[NSArray alloc] initWithObjects: [Level levelWithBalls:4 target:1], [Level levelWithBalls:4 target:2], [Level levelWithBalls:8 target:4], [Level levelWithBalls:15 target:7], [Level levelWithBalls:25 target:12], [Level levelWithBalls:30 target:17], [Level levelWithBalls:30 target:20], [Level levelWithBalls:30 target:25], [Level levelWithBalls:30 target:28], [Level levelWithBalls:30 target:30], nil]; currentLevel = 0;

The array must be released when the BallsView is done.

            
<<release BallsViews interface>>= [levels release];

Balls

The balls in this game are just circles that bounce around the screen. Thus, they need a position, a direction, a radius and a color. But, to make it easier to draw onto a Quartz context, I am going to store the balls as the rectangle that encloses the circle, the direction it is moving, and its color.

            
<<Ball.h>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // #import <UIKit/UIKit.h> @interface Ball: NSObject { UIColor *color; CGPoint dir; CGRect rect; } <<Ball constructor declaration>> <<Ball draw declaration>> <<Ball update declaration>> @property (nonatomic, retain) UIColor *color; @property (nonatomic) CGPoint dir; @property (nonatomic) CGRect rect; <<Ball radius property declaration>> <<Ball position property declaration>> @end
            
<<Ball.m>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // #import "Ball.h" @implementation Ball @synthesize color; @synthesize dir; @synthesize rect; <<Ball constructor>> <<Ball draw>> <<Ball update>> <<Ball radius property>> <<Ball position property>> - (void)dealloc { [color release]; [super dealloc]; } @end

Ball’s constructor expects a point, a radius and a color but then constructs the rectangle centered at the given point. The initial direction is towards the screen’s top-left corner.

            
<<Ball constructor declaration>>= - (id)initWithPosition:(CGPoint)pos radius:(CGFloat)radius color:(UIColor *)color;
            
<<Ball constructor>>= - (id)initWithPosition:(CGPoint)pos radius:(CGFloat)radius color:(UIColor *)ballColor { self = [super init]; if (self) { [ballColor retain]; self.color = ballColor; self.dir = CGPointMake(-1.0f, -1.0f); self.rect = CGRectMake(pos.x - radius, pos.y - radius, radius * 2, radius * 2); } return self; }

I do this because then drawing the ball to a context is just a matter to pass the appropriate attributes to the functions.

            
<<Ball draw declaration>>= - (void)drawToContext:(CGContextRef)context;
            
<<Ball draw>>= - (void)drawToContext:(CGContextRef)context { CGContextSetFillColorWithColor(context, self.color.CGColor); CGContextFillEllipseInRect(context, self.rect); }

Thus drawing all the balls is a matter of calling this function with the context to draw on.

            
<<draw balls>>= for (Ball *ball in balls) { [ball drawToContext:context]; }

Although this approach makes updating the balls a little more complicated because now, besides updating the current position according to the direction, I have to find the rectangle’s center position in order to know whether to change the direction when the ball must bounce around the screen’s limits, which are passed as a parameter when updating.

Given that I’ll use the balls position later to know whether the balls collides with an explosion, I think it is better to put this center position in a position property of Ball. This property returns the Ball’s enclosing rectangle’s origin point but with an offset equal to the ball’s radius.

            
<<Ball position property declaration>>= @property (nonatomic, readonly) CGPoint position;
            
<<Ball position property>>= - (CGPoint)position { return CGPointMake(self.rect.origin.x + self.radius, self.rect.origin.y + self.radius); }

The ball’s radius is also a property, for the same reason as the position, and can also be extracted from the ball’s rectangle, knowing that both the rectangle’s width and height — they are the same value — are the ball’s diameter, the radius is half either of these values.

            
<<Explosion radius property declaration>>= @property (nonatomic, readonly) CGFloat radius;
            
<<Explosion radius property>>= - (CGFloat)radius { return self.rect.size.width / 2; }

With these two properties, it is now easy to write the function that updates the ball’s position.

            
<<Ball update declaration>>= - (void)updateWithBounds:(CGRect)bounds;
            
<<Ball update>>= - (void)updateWithBounds:(CGRect)bounds { self.rect = CGRectMake( self.rect.origin.x + self.dir.x, self.rect.origin.y + self.dir.y, self.rect.size.width, self.rect.size.height); CGPoint center = self.position; if (center.x < bounds.origin.x) { self.dir = CGPointMake(1.0f, self.dir.y); } else if (center.x >= bounds.origin.x + bounds.size.width) { self.dir = CGPointMake(-1.0f, self.dir.y); } if (center.y < bounds.origin.y) { self.dir = CGPointMake(self.dir.x, 1.0f); } else if (center.y >= bounds.origin.y + bounds.size.height) { self.dir = CGPointMake(self.dir.x, -1.0f); } }

Calling the update function is as easy as drawing, but instead of passing the context, I need to pass the view’s bounds rectangle to keep the balls within the view.

            
<<update balls>>= CGRect bounds = [self bounds]; for (Ball *ball in balls) { [ball updateWithBounds:bounds]; }

balls, both when drawing and when updating, is an object of type NSMutableArray owned by BallsViews and thus is created when initializing BallsView.

            
<<BallsView interface>>= NSMutableArray *balls;
            
<<init BallsView interface>>= balls = [[NSMutableArray alloc] init];

Of course, since BallsView has ownership of this object, it is also responsible of releasing it.

            
<<release BallsView interface>>= [balls release];

Explosions

Like the balls, the explosions in iTouchBalls are simple circles. But, unlike the balls, instead of moving around the screen they change their radius in such a way to mimic an explosion’s blast. Like I did for the balls, I am going to store the explosions as the rectangle that encloses the explosion and its color. The explosion has also a growth rate which is the speed in which the explosion grows or shrinks.

            
<<Explosion.h>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // #import <UIKit/UIKit.h> @class Ball; @interface Explosion: NSObject { UIColor *color; CGFloat growthRate; CGRect rect; }; <<Explosion constructor declaration>> <<Explosion draw declaration>> <<Explosion update declaration>> <<Explosion collides declaration>> @property (nonatomic, retain) UIColor *color; @property (nonatomic) CGFloat growthRate; @property (nonatomic) CGRect rect; <<Explosion radius property declaration>> @end
            
<<Explosion.m>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // #import "Explosion.h" #import "Ball.h" @implementation Explosion @synthesize color; @synthesize growthRate; @synthesize rect; <<Explosion constructor>> <<Explosion draw>> <<Explosion update>> <<Explosion collides>> <<Explosion radius property>> - (void)dealloc { [color release]; [super dealloc]; } @end

Explosion’s constructor expects a position, a radius, and a color but then constructs the rectangle centered at the specified point. The initial growth ratio is set to 1 pixel per each update.

            
<<Explosion constructor declaration>>= - (id)initWithPosition:(CGPoint)pos radius:(CGFloat)radius color:(UIColor *)color;
            
<<Explosion constructor>>= - (id)initWithPosition:(CGPoint)pos radius:(CGFloat)radius color:(UIColor *)explosionColor { self = [super init]; if (self) { [explosionColor retain]; self.color = explosionColor; self.growthRate = 1.0f; self.rect = CGRectMake(pos.x - radius, pos.y - radius, radius * 2, radius * 2); } return self; }

This rectangle centered around the explosion renders the drawing of said explosion dead simple, just like I did with the balls.

            
<<Explosion draw declaration>>= - (void)drawToContext:(CGContextRef)context;
            
<<Explosion draw>>= - (void)drawToContext:(CGContextRef)context { CGContextSetFillColorWithColor(context, self.color.CGColor); CGContextFillEllipseInRect(context, self.rect); }

Calling this function for all explosions and passing the view’s context draws them all on the screen.

            
<<draw explosions>>= for (Explosion *explosion in explosions) { [explosion drawToContext:context]; }

But updating the rectangle is now a little more involved than just updating the radius property. Now I have to grow or shrink the explosion’s rectangle around the center. Given that the growth rate is exactly how many pixels the radius grow each update, I only have to move the rectangle’s position away from the center by that amount while, at the same time, I make the rectangle bigger by double that amount, because the rectangle’s size represents the diameter, not the radius. Of course, if the growth is negative (i.e., shrinking) then the operation is reversed, but this happens automatically because the growth rate’s variable is negative.

Another thing to take into account when updating the explosions is that they grow until a certain limit. This limit is passed as a parameter to the update function, but this limit is the maximum radius, which is half the rectangle’s width or height. When this limit is reached, then the explosion starts to shrink until the ball’s diagonal is 0.

            
<<Explosion update declaration>>= - (void)updateWithMaxRadius:(CGFloat)radius;
            
<<Explosion update>>= - (void)updateWithMaxRadius:(CGFloat)maxRadius { if (self.radius > 0) { self.rect = CGRectMake( self.rect.origin.x - self.growthRate, self.rect.origin.y - self.growthRate, self.rect.size.width + self.growthRate * 2, self.rect.size.height + self.growthRate * 2); if (self.radius / 2 > maxRadius) { self.growthRate = -1; } } }

To make the code a little easier to understand, I’ve added a new property to Explosion which hides the details of how I need to extract the radius from the explosion’s rectangle.

            
<<Ball radius property declaration>>= @property (nonatomic, readonly) CGFloat radius;
            
<<Ball radius property>>= - (CGFloat)radius { return self.rect.size.width / 2; }

Although the idea behind Explosion’s updateWithMaxRadius: is the same as the ball’s updateWithBounds:, traversing and updating the explosions is not as straightforward as with the balls. The main difference is that besides calling the update function, I have to check whether the explosions collide with a ball or not. I also have to make sure that explosions no longer valid (i.e., its radius reached 0) get removed from the game.

            
<<update explosions>>= for (int explosionIndex = 0 ; explosionIndex < [explosions count] ; ++explosionIndex) { Explosion *explosion = [explosions objectAtIndex:explosionIndex]; [explosion updateWithMaxRadius:30.0f]; <<check whether to remove explosion>> <<otherwise check whether explosion collides with balls>> }

The check whether the explosion must be removed, I need to know its radius. If the radius reached 0, then the explosion is no longer valid and must be removed from the array. I also need to subtract one from the explosion’s index or I would skip the next explosion, because removing the explosion moves all posterior explosions one position to the beginning of the array.

            
<<check whether to remove explosion>>= if (explosion.radius <= 0) { [explosions removeObjectAtIndex:explosionIndex]; --explosionIndex; }

If the explosion is still active (i.e., its radius is greater than 0) then I need to check whether collides with any of the balls on the screen. If the explosion is colliding with a ball, that ball is replaced with a new explosion at the same position and with the same color as the colliding ball. The new explosion is added at the array’s beginning in order to be draw under older explosions. This is because I am drawing the explosions from the first index to the last. If the drawing for-loop was done in reverse, then putting the explosion at the array’s end would be enough, and it could even improve the application’s performance.

            
<<otherwise check whether explosion collides with balls>>= else { for (int ballIndex = 0 ; ballIndex < [balls count] ; ++ballIndex) { Ball *ball = [balls objectAtIndex:ballIndex]; if ([explosion collidesWithBall:ball]) { Explosion *newExplosion = [[Explosion alloc] initWithPosition:ball.position radius:ball.radius color:ball.color]; [explosions insertObject:newExplosion atIndex:0]; [newExplosion release]; [balls removeObjectAtIndex:ballIndex]; --ballIndex; } } }

Knowing that both the explosion and the ball are circles, I can compute the euclidean distance between the two to check whether they are colliding or not. If the distance is less than the sum of their radius, then the explosion and the ball are colliding. To avoid the square root in computing the euclidean distance, instead of the actual distance, which I do not require, I am going to check whether the squared distance is less than the squared sum of their radii.

            
<<Explosion collides declaration>>= - (BOOL)collidesWithBall:(Ball *)ball;
            
<<Explosion collides>>= - (BOOL)collidesWithBall:(Ball *)ball { CGFloat maxDistance = (self.radius + ball.radius) * (self.radius + ball.radius); CGPoint center = CGPointMake(self.rect.origin.x + self.radius, self.rect.origin.y + self.radius); CGPoint ballCenter = ball.position; return ((center.x - ballCenter.x) * (center.x - ballCenter.x) + (center.y - ballCenter.y) * (center.y - ballCenter.y)) < maxDistance; }

The only last thing to check for is whether the level is done or not. The level is done when the last explosion is fired and destroyed. This happens when the there was any explosion before the last update and then there is not more explosion left. Since the player can only start a single explosion per level, if there is no longer an explosion available, then I can assume there is nothing else to do.

            
<<check if there is any explosion>>= BOOL anyExplosionLeft = [explosions count] > 0;
            
<<check whether level is over>>= if (anyExplosionLeft && [explosions count] == 0) {

To end the level, the only thing I have to do is remove all the balls from their array. But before that, I need to check if we reached the level’s target amount of balls to remove. With the number of balls still left and the number of balls that there were initially, I can readily find out whether the level is completed or not. If it is, then move to the next level, if there are more levels.

            
<<check whether level is over>>= Level *level = [levels objectAtIndex:currentLevel]; if (level.balls - [balls count] >= level.target) { nextLevelLabel = @"Tap to Start Next Level"; if (currentLevel < [levels count] - 1) { ++currentLevel; } } else { nextLevelLabel = @"Oops. Try Again!"; } [balls removeAllObjects]; }

explosions, like balls above, is an object of class NSMutableArray owned by BallsView and created with it.

            
<<BallsView interface>>= NSMutableArray *explosions;
            
<<init BallsView interface>>= explosions = [[NSMutableArray alloc] init];

As BallsView has ownership of this object, it is also responsible to release them.

            
<<release BallsView interface>>= [explosions release];

Tap, Tap, Tapping

Now that I have most of the pieces in place, I need to be able to start the game by tapping on the screen.

When starting, the game waits for the player to tap the screen before creating the balls for the current level. I know when the player tapped the screen because BallsView receives the touchesEnded:withEvent message from the underlaying system. I’ve chosen touchesEnded:withEvent instead of touchesBegan:withEvent because I am going to use the same message to create the explosions and I want a way to rectify, as it were, the explosion’s initial position if I did choose the wrong spot.

            
<<tap function>>= - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { <<create balls if level has not already started>> <<otherwise create explosion if no explosion exists>> }

In this message I thus need to know whether the level has already started. I can easily know that by looking at the number of elements inside the balls array. If this array is empty, the level hasn’t started yet and I must start the game by creating as many balls as dictated by the current level.

            
<<create balls if level has not already started>>= if ([balls count] == 0) { <<get current level>> <<create levels balls>> }

Given that the view’s currentLevel property is always within the array’s bounds, the current level object can be retrieved from the array using this property as the index.

            
<<get current level>>= Level *level = [levels objectAtIndex:currentLevel];

Using this object, I can get the number of balls to create for the current level using a for loop. The balls are created at a random position inside the view’s bounds and each has a random color.

            
<<create levels balls>>= NSArray *colors = [[NSArray alloc] initWithObjects: [UIColor redColor], [UIColor greenColor], [UIColor blueColor], [UIColor cyanColor], [UIColor yellowColor], [UIColor magentaColor], nil]; CGRect bounds = [self bounds]; for (unsigned int currentBall = 0 ; currentBall < level.balls ; ++currentBall) { CGPoint position = CGPointMake(bounds.origin.x + arc4random() % (int)bounds.size.width, bounds.origin.y + arc4random() % (int)bounds.size.height); UIColor *color = [colors objectAtIndex:(arc4random() % [colors count])]; Ball *ball = [[Ball alloc] initWithPosition:position radius:4.0f color:color]; [balls addObject:ball]; [ball release]; } [colors release];

The arc4random() is a function that returns a pseudo-random number between 0 and 232-1 and doesn’t require a seed value, unlike rand(). Thus, it is more convenient in this case, but I need to include the stdlib header where it is declared.

            
<<BallsView includes>>= #include <stdlib.h>

The explosion is easier to create than the balls. The player can only create a single explosion, hence I need to check whether there is already an explosion in the level or not. If there is none, then I create a 1 pixel explosion at the position where the player tapped the screen. I can’t create a 0 pixel explosion because when an explosion reaches 0 it gets removed from the level.

            
<<otherwise create explosion if no explosion exists>>= else if ([explosions count] == 0) { UITouch *touch = [touches anyObject]; Explosion *newExplosion = [[Explosion alloc] initWithPosition:[touch locationInView:self] radius: 1.0f color: [UIColor redColor]]; [explosions addObject:newExplosion]; [newExplosion release]; }

Appendix A. Main

The main file for an iOS application is really simple. I only have to call UIApplicationMain passing the arguments from the command line and telling which is the application class. As with most Objective-C applications, the main function is wrapped inside an NSAutoreleasePool that manages the application’s memory allocation.

            
<<main.m>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // #import <UIKit/UIKit.h> int main(int argc, char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, @"ITouchBalls", @"ITouchBalls"); [pool release]; return retVal; }

The ITouchBalls class referenced in the call to UIApplicationMain is an UIApplication class that is responsible to create and manage the application’s window.

            
<<ITouchBalls.h>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // #import <UIKit/UIKit.h> @interface ITouchBalls: UIApplication { UIWindow *window; } @end
            
<<ITouchBalls.m>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // #import "ITouchBalls.h" #import "BallsView.h" @implementation ITouchBalls <<create and set view>> - (void)dealloc { [window dealloc]; [super dealloc]; } @end

The ITouchBalls class is not only responsible to allocate the window but also its view, which is the class the actually manages the whole game’s logic. This must be done inside the applicationDidFinishLaunching: function called by UIApplicationMain when the application has done with the initialization process. This could be considered the “real” application’s main.

            
<<create and set view>>= - (void)applicationDidFinishLaunching:(UIApplication *)application {

The first thing I want to do in this function is to retrieve the device’s screen area because I am going to need the area later to create the window and its view.

The screen area can be retrieved either using the function bounds or applicationFrame of UIScreen applied to the mainScreen. The difference between the two functions is that bounds will give the total device’s area while applicationFrame returns the device’s area minus the space occupied by the status bar, if shown. In this case, I want to make a full screen window, thus I’m going to use bounds.

            
<<create and set view>>= CGRect frame = [[UIScreen mainScreen] bounds];

Now I can make the window and its view, assign the view to the window, mark this window as the main window, and show it.

            
<<create and set view>>= <<create view>> window = [[UIWindow alloc] initWithFrame:frame]; <<set view in window>> [window makeKeyAndVisible]; [view release]; }

Appendix B. Prefix Header

A prefix header is a header that the compiler automatically includes at the top of each source file. This header file is pre-compiled in order to speed up the building time. Thus, in this header I import all the other headers used by an iOS application, but only if the source being compiled is actually an Objective-C source file.

            
<<iTouchBalls_Prefix.pch>>= // // iTouchBalls - A simple game with balls. // Copyright (c) 2011 Jordi Fita <jfita@geishastudios.com> // <<license>> // // Prefix header for all source files. // #ifdef __OBJC_ #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #endif

Appendix C. Plist File

The plist file is an XML file placed inside the application’s bundle that has the essential information about the program.

            
<<iTouchBalls-Info.plist>>= <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <<Bundle Settings>> </dict> </plist>

This file is used by iOS to retrieve information about the application. In this case it know that that application was written in an English locale.

            
<<Bundle Settings>>= <key>CFBundleDevelopmentRegion</key> <string>English</string>

iOS needs to know which kind of binary this bundle is holding: an application, a framework, a plug-in, etc. This game is an application, thus I set its type accordingly.

            
<<Bundle Settings>>= <key>CFBundlePackageType</key> <string>APPL</string>

iOS also looks in this file the name of the bundle and the string to show to the user. In both cases, I use the same string set by Xcode when building the application.

            
<<Bundle Settings>>= <key>CFBundleName</key> <string>${PRODUCT_NAME}</string> <key>CFBundleDisplayName</key> <string>${PRODUCT_NAME}</string>

Along with the bundle’s name, iOS also shows an icon. In this case, though, I’ll use an empty icon’s filename because I don’t have any icon for this game.

            
<<Bundle Settings>>= <key>CFBundleIconFile</key> <string></string>

The bundle needs to have an identifier that can be used by iOS to associate file types to the application, or store the application’s settings in the appropriate place, among other things. This identifier is a reverse DNS style string where, generally, the first part is a developer’s prefix — usually the developer’s domain — followed by the application’s name. The application name must be RFC-1034 compliant.

            
<<Bundle Settings>>= <key>CFBundleIdentifier</key> <string>com.geishastudios.${PRODUCT_NAME:rfc1034identifier}</string>

In order to start the application, iOS must know the name of the executable to run. The executable can also be written by Xcode when building.

            
<<Bundle Settings>>= <key>CFBundleExecutable</key> <string>${EXECUTABLE_NAME}</string>

As a bundle can host an application for iOS or for regular Mac OS X, I need to specify that this application requires iOS (formerly called IPhoneOS) to be run.

            
<<Bundle Settings>>= <key>LSRequiresIPhoneOS</key> <true/>

Finally, the bundle’s plist needs some extra information that I don’t have any idea whatsoever what they mean. I just keep the values I’ve found to every other plist file.

            
<<Bundle Settings>>= <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string>1.0</string>

Appendix D. License

This program is distributed under the terms of the GNU General Public License (GPL) version 2.0 as follows:

            
<<license>>= // This program is free software; you can redistribute is and/or modify // it under the terms of the GNU General Public License version 2.0 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 50 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Comments

Bookmark and Share