Category Archives: iOS

Tigers & Goats: A Strategy Game

Board games have a rich tradition across various countries and cultures – games such as Chess and Go are hugely popular and are probably what you think of when you hear “strategy based board games”. But in ancient times, there were other games that kept people entertained for hours. In Egypt, one of the most popular 2-player board games was Senet – and it was played not just by Pharoahs and royalty but also by ordinary Egyptians.

In this post, we will explore a lesser known but immensely entertaining and simple board game called Tigers & Goats. This game has many incarnations and the original version comes to us from Nepal, where it is referred to as Bagh Chal. However, the variant I learned is from India and is commonly known by its Marathi name – Wagh-Shelya. I was introduced to Tigers & Goats by my grandmother – who taught me to play this on the chess board. Now, we have a modern version – an app – that we can play on an iPhone, iPad, Mac or Apple TV.

Simulator Screen Shot - iPad Pro (12.9-inch) (6th generation) - 2023-02-12 at 07.58.01

The rules are really simple – you start with an 8 x 8 board and arrange 28 goats around the perimeter. The four tigers are placed at the center of the board.

Simulator Screen Shot - iPad Pro (12.9-inch) (6th generation) - 2023-02-12 at 08.01.22

Goats always begin the game. They can move to an adjacent square in any direction as long as that square is empty. But be warned – the tigers can “eat” goats if they are not careful. In order for a tiger to be able to eat a goat, the goat should have an empty square behind it as shown below – either horizontally or vertically.

Simulator Screen Shot - iPad Pro (12.9-inch) (6th generation) - 2023-02-12 at 08.06.25

Tigers move in a fashion similar to how an elephant or rook moves in Chess – any number of squares horizontally or vertically. In addition, a tiger can “jump” over a goat and eat it. It is not mandatory for a tiger to kill a goat and it may choose not to depending on the situation in the game. The goats have to work together as a team to advance without being killed and eventually trap all 4 tigers so they cannot move at all. If this happens, the goats win.

The latest version of this game (version 2.7) can be played in 3 modes:

  • Practice
  • 1-Player
  • 2-Player

Practice mode lets you play both tigers and goats and helps you learn the game. One level up is 1-Player mode, where you take charge of goats and match wits against the computer, which plays tigers. Finally, 2-Player mode allows you to play with friends you invite using Apple’s Game Center.

The screenshot below shows the initial stage of the game when both goats and tigers have made a few moves. It is now the tigers move (indicated by a orange circle around them) and as a player who plays tigers, your job is to occupy strategic locations on the board such that goats will find it really difficult to advance any further.

Simulator Screen Shot - iPad Pro (12.9-inch) (6th generation) - 2023-02-12 at 08.14.39

If you are playing goats, you will now need to think of clever ways to advance without getting killed. This may seem hard but is not impossible. In fact, most of the fun while playing this game comes from exploring various strategies to advance (from the goats perspective) and of making this impossible (from the tigers perspective). The simple rules make this very easy to understand and play even for kids. And the endless possibilities can keep us adults (and the little kid inside each and every one of us) entertained for hours.

In the app, if a goat is in danger of being eaten because a tiger moved in for the kill, the square on which it is located gets highlighted in red. You should move this goat to safety either by retreating or moving another goat behind it so the tiger can’t kill it. For example, the tiger pointed out in the situation below has moved in for the kill, but we can move the goat indicated by the other finger to the left, thereby making the goat safe from the tiger.

Screenshot 2023-02-12 at 8.25.18 AM

If the goats move carelessly and get eaten, it will get harder and harder for them to trap tigers. If 4 goats are eaten it is next to impossible for goats to trap tigers and so in this situation, tigers win. On the other hand, if goats manage to keep advancing and trap all tigers, goats win! Here is an example of how all tigers are now in big trouble and likely to get trapped by goats soon.

Simulator Screen Shot - iPad Pro (12.9-inch) (6th generation) - 2023-02-12 at 08.34.47

To play this game with your friends, simply follow the steps below to invite them via Apple’s GameKit UI. Note that you will need to be logged in to Game Center to invite friends and will also need to add them as a friend before you can invite them. All of this is managed by Game Center. In the steps below, Player 1 initiates the invite and this player plays goats. The other person who gets invited plays tigers. You can then interchange the process to switch roles.

As soon as Player 1 moves a goat piece, the invite gets sent to Player 2 and the game begins! GameKit also sends notifications when a move is made by your opponent so you can get back to the game if you are doing something else on your device.

Communication between the two devices happens via GameKit and is handled by Apple. The video below shows the first several moves as Player 1 (on an iPhone) and Player 2 (on a Mac) take turns in moving the pieces. The game continues until one of the player wins or forfeits the game. You can have multiple games with different opponents going on at the same time. GameKit keeps track of the status of each game – and you can also stop and pick up your game at a later time.

The game is best played on large screens – such as iPads, Macs and Apple TV but also works on iPhones. On the more recent iPhones, it supports a new feature called Live Activities. This allows you to see at a glance whose turn it is to play and how long it has been since the turn started. You will need to be on iOS 16.1 or higher in order to see Live Activities.

On newer phone models such as the iPhone 14 Pro and the iPhone 14 Pro Max, you will also see Live Activity information in Apple’s beautifully designed Dynamic Island – a shape-shifting elongated capsule that lives near the front camera. Tapping on this Live Activity immediately takes you to your game – from whatever app you happen to be in.

Screenshot 2023-02-12 at 9.53.42 AM

Board games have changed and evolved over the ages and we have learned to use modern technology to play them. The internet now allows players who are separated by thousands of miles to play them as if they are sitting in the same room. And with rapid advancements and improvements in AI, machines will soon come up with interesting strategies that we may not think of right now – and be unbeatable at these games.

But at the core, games such as these retain their charm and simplicity and will continue to exist in some form or another and entertain us for generations to come.

APP STORE LINK:

https://apps.apple.com/us/app/tigers-goats/id1599385271

DISCORD LINK:

To chat with players who play this game using your iPhone or iPad, download the discord app and sign up for a free account. Then, tap on the link below to automatically receive an invite to join the chat channel. See you there!

https://discord.com/invite/zXr27KWYwG

iOS – A simple introduction to Swift

I don’t know who once said these words, but they apply without question to this blog post.

Make everything as simple as possible – but not simpler.

Anyways, if you have nothing better to do with your day, you might as well learn how to program using Apple’s shiny new language called Swift. Granted it is not so shiny because it has been a while since it was born. But it is still somewhat of a toddler and lots of fun to play with – although this toddler is a lot smarter than what you might think.

Ok – so this post is not about to teach you how to program – or about how to write apps for iOS. It is more of a visual inspiration to start coding in Swift for those still reluctant to make the leap from good old Objective-C. Yes, I am one of those who fell in love with and still love Objective-C. I was particularly shocked when I just found out what Swift is going to do to the increment operator ( i ++ ). But you can read about that elsewhere if you don’t already know.

Enough chit chat – let’s get to the point. Here we go:

Screen Shot 2016-06-20 at 11.00.58 PM

The project here has a single button and a label. The label begins life with the number 1. Pressing the button doubles the value of the label and this keeps happening forever. I have not checked what happens if you keep doing this for a long time. Your finger may not like it much.

1…2…4…8…16…32…you get the idea.

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var label: UILabel!
    
    var i = 1
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func buttonPressed(sender: AnyObject) {
        i += i
        label.text = String(i)
    }
}

Granted, this is a really simple app – but hey, we got to start somewhere and this is as good a place as any.

Happy Coding!

iOS – Using MKLocalSearchCompleter for Map Search

Google has always been somewhat ahead of the game in map search – for example in suggesting “search completion” strings when we start typing in places, points of interests or partial addresses in Maps. But Apple is catching up – and catching up fast.

In iOS 9.3, Apple introduced a very useful class for developers called MKLocalSearchCompleter – one that is sure to swing several iOS dev teams to lean towards using Apple Maps in lieu of Google Maps for implementing the search completion functionality in their apps.

The basic idea of search completion is to be able to type in a partial string in a search box and get several suggestions in real time as we are typing in. The user can then tap on one of the suggested options – and be taken to that location on the map view.

Here is an example of what is now possible using the MKLocalSearchCompleter class :

Screen Shot 2016-04-03 at 10.37.24 AM

To implement this functionality in your apps, just follow these simple steps:

1.

Add the delegate to your interface.

@interface YourMapVC : UIViewController < .. ,MKLocalSearchCompleterDelegate> {

}

2.

Add a couple of new properties:

.
.
@property (strong, nonatomic) MKLocalSearchCompleter *completer;
@property(nonatomic, readonly, strong) NSArray <MKLocalSearchCompletion *> *results;
.
.

3.

Initialize the class somewhere in your implementation:

.
.
completer = [[MKLocalSearchCompleter alloc] init];
completer.delegate = self;
completer.filterType = MKSearchCompletionFilterTypeLocationsAndQueries;
.
.

4.

Add these delegate methods and use the results returned in your app:

- (void) completerDidUpdateResults:(MKLocalSearchCompleter *)completer {
    for (MKLocalSearchCompletion *completion in completer.results) {
        NSLog(@"------ %@",completion.description);
    }
}

- (void) completer:(MKLocalSearchCompleter *)completer didFailWithError:(NSError *)error {
    NSLog(@"Completer failed with error: %@",error.description);

}

In the above code, I am printing out the results returned in the array – we can use these to update a table below the search box in real time.

I was quite impressed with how responsive and relevant this search functionality is in a real app. For additional details, check out the official Apple Docs.

Nice work, Apple!

iOS – Unit Testing

xcode2

This post will talk briefly about setting up a bare-bones iOS app to illustrate what unit testing is all about and how to utilize the XCTest Framework for writing your own tests.

Imagine that you are writing a method to validate US and Canadian ZIP codes (or postal codes, as they’re called in Canada). In the US, ZIP codes are 5-digits long. Things like 77335, 06226, 29631 and so on.

In Canada, postal codes are typically written using 6 characters, with a space after the first three characters, like so:

LNL NLN

In the above code, L is a letter from the alphabet and N is a number or digit from 0-9. So Canadian postal codes can look like A2K 4L8, K7G 5T9, M2N 6P8 and so on. To make things more interesting, there are some additional rules about what letters are allowed (or rather not allowed) at various positions:

  • The first letter cannot be W or Z.
  • The letters D F I O Q U are not allowed at any location.

There might be more rules, but we’ll stop here. You get the idea where this is going. We’ll need to write a method that receives a user input string and then parses the string to make sure all these rules are met (for both US and Canada). Let’s assume you cook up the following class method somewhere in your model:

+ (BOOL) validateZip:(NSString *)userZip
{
    .
    .
    .    
}

If the user enters the correct ZIP code, this method should return YES, otherwise it should return NO.

Once you finish developing this method, you simply go to the test section of your project and start adding in various test cases to check your method. There are some who maintain that these test cases should be written even before you create the above method – we will not argue about what approach is best – do whatever works for you.

For simplicity, this post only talks about the following testing methods, which suffice for our purpose here:

  • XCTAssertTrue( something_that_should_be_true, @”optional message displayed when that something is false” )
  • XCTAssertFalse (something_that_should_be_false, @”optional message displayed when that something is true” )

This is how you use them in the code (all you need is to include the header file of your model class in the test case file):

#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "ZipCodeValidation.h"

@interface ZipCheckTests : XCTestCase

@end

@implementation ZipCheckTests

- (void)setUp {
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

// Invalid ZIP or Canadian Postal codes

- (void)test_empty_zip_not_allowed {
    XCTAssertFalse([ZipCodeValidation validateZip:@""],
                   @"blank entry is invalid");
}

- (void)test_just_spaces_not_allowed {
    XCTAssertFalse([ZipCodeValidation validateZip:@"      "],
                   @"lots of spaces is not a valid entry");
}

- (void)test_random_names_not_allowed {
    XCTAssertFalse([ZipCodeValidation validateZip:@"Ludwig Van Beethoven"],
                   @"random names should fail");
}

- (void)test_strange_characters_not_allowed {
    XCTAssertFalse([ZipCodeValidation validateZip:@"$[]&@!"],
                   @"special characters are not allowed");
}

// USA

- (void)test_USA_01 {
    XCTAssertTrue([ZipCodeValidation validateZip:@"23324"],
              @"23324 should be valid");
}

- (void)test_USA_02 {
    XCTAssertTrue([ZipCodeValidation validateZip:@"  06 226   "],
              @"spaces should not matter");
}

- (void)test_USA_03 {
    XCTAssertTrue([ZipCodeValidation validateZip:@"12345"],
              @"any 5-digit code is valid for the US");
}

- (void)test_USA_04 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"1234"],
                   @"4 digits not allowed");
}

- (void)test_USA_05 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"123456"],
                   @"6 digits not allowed");
}

- (void)test_USA_06 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"12w45"],
                   @"letters not allowed for the US");
}

// Canada

- (void)test_CANADA_01 {
    XCTAssertTrue([ZipCodeValidation validateZip:@"a2a 2a2"],
              @"a2a 2a2 should be fine");
}

- (void)test_CANADA_02 {
    XCTAssertTrue([ZipCodeValidation validateZip:@"a2a2a2"],
              @"a2a2a2 should also work");
}

- (void)test_CANADA_03 {
    XCTAssertTrue([ZipCodeValidation validateZip:@"A2A 2A2"],
              @"A2A 2A2 should be valid");
}

- (void)test_CANADA_04 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"W2A 2A2"],
                   @"W cannot be the first letter");
}

- (void)test_CANADA_05 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"Z2A 2A2"],
                   @"Z cannot be the first letter");
}

- (void)test_CANADA_06 {
    XCTAssertTrue([ZipCodeValidation validateZip:@"S2W 2Z2"],
              @"W and Z elsewhere is fine");
}

- (void)test_CANADA_07 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"S2D 2Z2"],
                   @"D is not allowed");
}

- (void)test_CANADA_08 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"S2F 2Z2"],
                   @"F is not allowed");
}

- (void)test_CANADA_09 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"I2A 2Z2"],
                   @"I is not allowed");
}

- (void)test_CANADA_10 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"B2A 2O2"],
                   @"O is not allowed");
}

- (void)test_CANADA_11 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"B2Q 2A2"],
                   @"Q is not allowed");
}

- (void)test_CANADA_12 {
    XCTAssertFalse([ZipCodeValidation validateZip:@"U2A 2A5"],
                   @"U is not allowed");
}

@end

You can add as many tests you like in this file. To run these tests, use

Product —> Test (or the keyboard shortcut  Command + U)

Xcode will run all your tests and place cute little check signs next to each test to let you know which tests pass and which tests fail.

That’s it! Isn’t unit testing cool? Now that I’ve wrapped my head around the basics, I will start adding unit tests to several of my iOS projects.

iOS – Mandelbrot

INTRODUCTION

Is it possible to fall in love with a mathematical object? If the object in question is the Mandelbrot set, then the answer is a definite yes. This post talks about an iPad app that helps us explore the strange, hypnotic and never-ending beauty of this well-known fractal.

Screen Shot 2014-05-29 at 12.30.42 AM

The image above shows a zoomed-in view of a certain part of the Mandelbrot set. The points in black are inside the set and those in blue are outside the set. Much of the artistic appeal of the set depends on the color scheme you design to mark the points at the borderline. I used the exact same color scheme from my C++/OpenGL tutorial and a lot of code (in the Model) was directly ported from C++ to Objective-C.

The displayed image is divided into 4 quadrants by the white lines. You select where you want to zoom in by tapping the appropriate block with your finger. For example, here is a sequence of touch events from the very beginning of the set:

Screen Shot 2014-05-28 at 10.28.47 PM

Naming the blocks using 1 (top-left), 2 (top-right), 3 (bottom-left) and 4 (bottom-right), the journey above may be abbreviated thus: 1-4-3-1. The large image at the beginning of this article was obtained using the following sequence: 1-4-4-1-3-3. The possible journeys are infinite and one can probably spend several lifetimes exploring every nook and cranny.

UNDER THE HOOD

The overall design of this app follows the usual Model-View-Controller strategy. The model consists of the minimum and maximum (x, y) coordinates of the window in the complex plane, the number of divisions along x and y (resolution), a 2D array that holds the parameter value used to color the set and a method to determine whether a point belongs in the set or not. Here is the implementation of the Model class:

#import "Model.h"

@implementation Model
@synthesize xmin, xmax, ymin, ymax;
@synthesize nx, ny;
@synthesize MAX_ITER;
@synthesize iters;

// override superclass implementation of init
-(id) init
{
    self = [super init];
    if (self) {
        nx = 500;
        ny = 500;
        xmin = -2.0;
        xmax = 1.0;
        ymin = -1.5;
        ymax = 1.5;
        MAX_ITER = 200;
        
        // allocate 2D array
        iters = [[NSMutableArray alloc] initWithCapacity:nx*ny];
        
        // initialize the 2D "iters" array, which
        // represents the number of iterations it takes for the
        // point to escape from the set
        
        for(int i = 0; i < nx; i++) {
            for(int j = 0; j < ny; j++) {
                [iters addObject:@(0)];
            }
        }
    }
    
    return self;
}

// this function checks if a point (x,y) is a member of the Mandelbrot set
// it returns the number of iterations it takes for this point to escape from the set
// if (x,y) is inside the set, it will not escape even after the maximum number of iterations
// and this function will take a long time to compute this and return the maximum iterations

- (int) Mandelbrot_Member_x:(double) x and_y: (double) y
{
    double cx = x, cy = y;
    double zx = 0.0, zy = 0.0;
    
    int iter = 0;
    
    while(iter < MAX_ITER)
    {
        iter++;
        double real = zx*zx - zy*zy + cx;
        double imag = 2.0*zx*zy + cy;
        double mag = sqrt(real*real + imag*imag);
        if (mag > 2.0) break;   // (x,y) is outside the set, quick exit from this loop
        zx = real;
        zy = imag;
    }
    return iter;
}

// update the 2D array which stores "iterations to escape" based on the
// current window
- (void) updateMandelbrotData
{
    double dx = (xmax - xmin)/nx; // grid spacing along X
    double dy = (ymax - ymin)/ny; // grid spacing along Y
    
    // update the 2D "iters" array, which
    // represents the number of iterations it takes for the
    // point to escape from the set
    
    for(int i = 0; i < nx; i++) {
        for(int j = 0; j < ny; j++) {
            
            double x = xmin + dx/2 + i*dx;   // actual x coordinate
            double y = ymin + dy/2 + j*dy;   // actual y coordinate
            
            // calculate iterations to escape
            int iterationsToEscape = [self Mandelbrot_Member_x: x and_y: y];
            
            // natural index
            int N = i + nx*j;
            
            // add this entry to the 2D "iters" array
            [iters removeObjectAtIndex:N];
            [iters insertObject:@(iterationsToEscape) atIndex:N];
        }
    }
}

@end

There are two views, both subclasses of UIView: one displays the set itself and the other draws the horizontal and vertical centerlines. The implementation of the Class that draws the set is provided below for reference:

#import "DrawMandelbrot.h"

@implementation DrawMandelbrot

@synthesize nx, ny;
@synthesize MAX_ITER;
@synthesize data;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

- (void) initData
{
    data = [[NSMutableArray alloc] initWithCapacity:nx*ny];
    
    for(int i = 0; i < nx; i++) {
        for(int j = 0; j < ny; j++) {
            [data addObject:@(0)];
        }
    }
}

- (void)drawRect:(CGRect)rect
{
    // get the current context
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // context size in pixels
    size_t WIDTH = CGBitmapContextGetWidth(context);
    size_t HEIGHT = CGBitmapContextGetHeight(context);
    
    // for retina display, 1 point = 2 pixels
    // context size in screen points
    float width = WIDTH/2.0;
    float height = HEIGHT/2.0;
    
    float dx = width/nx;
    float dy = height/ny;
    
    // assign color based on the number of iterations - Red Green Blue (RGB)`
    
    for(int i = 0; i < nx; i++) {
        for(int j = 0; j < ny; j++) {
            
            int N = i + nx*j;
            int VAL = [[data objectAtIndex:N] intValue];
                        
            UIColor* fillColor;
            
            if(VAL==MAX_ITER)
            {
                fillColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:1.0];   // black
            }
            else
            {
                // ratio of iterations required to escape
                // the higher this value, the closer the point is to the set
                float frac = (float) VAL / MAX_ITER;
                
                if(frac <= 0.5)
                {
                    // yellow to blue transition
                    fillColor = [UIColor colorWithRed:2*frac green:2*frac blue:1.0 - 2*frac alpha:1.0];
                    //glColor3f(2*frac,2*frac,1-2*frac);
                }
                else
                {
                    // red to yellow transition
                    fillColor = [UIColor colorWithRed:1.0 green:2.0-2.0*frac blue:0.0 alpha:1.0];
                    //glColor3f(1,2-2*frac,0);
                }
            }

            // draw colored rectangle
            double x = i*dx;          // screen x coordinate
            double y = (ny-j-1)*dy;   // screen y coordinate
            CGRect rect = CGRectMake(x, y, dx, dy);
            [fillColor setFill];
            CGContextFillRect(context, rect);
        }
    }
}

@end

Note that we are using the Core Graphics API to render the 2D color data. This is not necessarily the best approach, but I believe it is the simplest to understand and implement. In the future, I plan to use OpenGL-ES and accelerate the calculation and graphics using GPU computing (CUDA/OpenCL).

Finally, the controller interprets the model for the views and updates the model data based on user touch-events. Essentially, we figure out which quadrant the user touched and update the minimum and maximum x and y coordinates appropriately, ask the Model to re-calculate the 2D array in the new window and send the updated data to the View for rendering. Here is the Controller implementation:

#import "ViewController.h"
#import "CrossHair.h"
#import "Model.h"
#import "DrawMandelbrot.h"

@interface ViewController ()
@property (strong, nonatomic) IBOutlet UIView *blackBox;
@property float width, height;
@property (strong, nonatomic) CrossHair* cross;
@property (strong, nonatomic) Model* model;
@property (strong, nonatomic) DrawMandelbrot* drawSet;
- (IBAction)backToSquareOne:(id)sender;
@end

@implementation ViewController
@synthesize blackBox;
@synthesize width, height;
@synthesize cross;
@synthesize model;
@synthesize drawSet;

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    
    // black box view dimensions
    width = blackBox.frame.size.width;
    height = blackBox.frame.size.height;
    
    // initialize the model
    model = [[Model alloc] init];

    // get window size
    CGRect viewRect = CGRectMake(0, 0, width , height);

    // initialize the Mandelbrot view
    drawSet = [[DrawMandelbrot alloc] initWithFrame:viewRect];
    drawSet.nx = model.nx;
    drawSet.ny = model.ny;
    drawSet.MAX_ITER = model.MAX_ITER;
    [drawSet initData];
    
    // initialize cross view
    cross = [[CrossHair alloc] initWithFrame:viewRect];
    [cross setBackgroundColor:[UIColor clearColor]];
    
    // add subviews
    [blackBox addSubview:drawSet];
    [blackBox addSubview:cross];
    
    // draw the Mandelbrot set
    [self drawMandelbrotSet];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


// touch events determine where we need to zoom in
//   +------+------+
//   |      |      |
//   |  1   |   2  |
//   |      |      |
//   +------+------+
//   |      |      |
//   |  3   |   4  |
//   |      |      |
//   +------+------+

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch* t in touches) {
        CGPoint touchLocation;
        touchLocation = [t locationInView:blackBox];
        float x = touchLocation.x;
        float y = touchLocation.y;
        
        float dx = width / 2;
        float dy = height / 2;
        
        // find out array location where finger touches the screen
        int xIndex = x/dx;
        int yIndex = y/dy;
        int N = (xIndex + 2*yIndex) + 1;
        
        NSLog(@"You selected block %d", N);
        
        switch (N) {
            case 1:
                model.xmax = model.xmin + (model.xmax - model.xmin)/2;
                model.ymin = model.ymin + (model.ymax - model.ymin)/2;
                break;
                
            case 2:
                model.xmin = model.xmin + (model.xmax - model.xmin)/2;
                model.ymin = model.ymin + (model.ymax - model.ymin)/2;
                break;
            
            case 3:
                model.xmax = model.xmin + (model.xmax - model.xmin)/2;
                model.ymax = model.ymin + (model.ymax - model.ymin)/2;
                break;
                
            case 4:
                model.xmin = model.xmin + (model.xmax - model.xmin)/2;
                model.ymax = model.ymin + (model.ymax - model.ymin)/2;
                break;
                
            default:
                break;
        }
        
        [self drawMandelbrotSet];
    }
}

- (void) drawMandelbrotSet
{
    [model updateMandelbrotData];
    drawSet.data = model.iters;
    [drawSet setNeedsDisplay];
}

- (IBAction)backToSquareOne:(id)sender {
    model.xmin = -2.0;
    model.xmax = 1.0;
    model.ymin = -1.5;
    model.ymax = 1.5;

    [self drawMandelbrotSet];
}

@end

Notice we have a RESET button to go back to the original window. This merely resets the minimum and maximum x and y limits to their original values.

The entire source code can be cloned from

https://github.com/jabhiji/ios-mandelbrot.git

Happy Xcoding!

iOS – Maze

My latest app for the iPhone is about creating a maze pattern using the touchscreen and then guiding a ball through the maze by tilting the phone in the appropriate direction. You create the maze in a block-by-block fashion by tapping the screen with your finger. One tap puts a solid block. Another tap at the same location removes the solid block. You can create any maze pattern, for example the one shown below.

Screen Shot 2014-05-23 at 11.28.46 PM

Hitting the CLEAR button removes all solid blocks. The ball bounces off the edges of the domain and also bounces off solid blocks. Implementing the model logic where the ball bounces off the domain edges is quite straightforward. Extending the model to include the solid blocks required some additional thought and this was perhaps the main take away from this project for me.

The entire source code can be downloaded from:

https://github.com/jabhiji/ios-maze.git

The Model class contains all the necessary properties and methods to specify the structure of the maze and to specify the ball location and update the ball location with time. The maze is simply a 2D integer array (LGEO), where the array element is 1 if the location is a solid block and 0 otherwise. Here is the Model class interface:

#import <Foundation/Foundation.h>

@interface Model : NSObject

// LGEO array
@property int nx, ny;
@property NSMutableArray* LGEO;

// domain size
@property float width, height;

// ball
@property float x, y, R, ux, uy, ax, ay;
- (void) updateBallPosition;

// coefficient of restitution
@property float COR;

@end

The name “LGEO” stands for “Logical GEOmetry array” and is a terminology rollover from some of my old CFD research codes, where a similar 3D array is used to describe a porous material. The number of array elements along X and Y is nx and ny respectively and the size of the LGEO array is thus nx*ny. The width and height are obtained based on the size of the UIView and used to calculate the grid size along X and Y. The remaining parameters specify the ball size, location, velocity and acceleration. The implementation file shows the details about how the model handles collisions:

#import "Model.h"

@implementation Model

@synthesize nx, ny;
@synthesize LGEO;
@synthesize width, height;
@synthesize x,y,R,ux,uy,ax,ay;
@synthesize COR;

// override superclass implementation of init
-(id) init
{
    self = [super init];
    if (self) {
        nx = 12;
        ny = 12;
        LGEO = [[NSMutableArray alloc] initWithCapacity:nx*ny];
        for (int i = 0; i < nx*ny; i++) {
            int value = 0;
            [LGEO addObject:@(value)];
        }
        COR = 0.5;
    }
    
    return self;
}

- (void) updateBallPosition
{
    // kinematics
    x += 0.5*ux;
    y += -0.5*uy;
    
    // check for collisions with walls
    if (x > width - R) {
        x = width - R;
        ux = -fabsf(ux)*COR;
    }
    if (y > height - R) {
        y = height - R;
        uy = fabsf(uy)*COR;
    }
    if (x < R) {
        x = R;
        ux = fabsf(ux)*COR;
    }
    if (y < R) {
        y = R;
        uy = -fabsf(uy)*COR;
    }
    
    // check for collision with maze blocks
    float dx = width / nx;
    float dy = height / ny;
    
    // find out array location where the ball center is located
    int xIndex = x/dx;
    int yIndex = y/dy;
    
    // loop over all neighboring squares
    NSMutableArray* xCenter, *yCenter, *distanceFromCenter;
    NSMutableArray *Nindex, *xIndexNbr, *yIndexNbr;
    xCenter = [[NSMutableArray alloc] initWithCapacity:9];
    yCenter = [[NSMutableArray alloc] initWithCapacity:9];
    distanceFromCenter = [[NSMutableArray alloc] initWithCapacity:9];
    Nindex  = [[NSMutableArray alloc] initWithCapacity:9];
    xIndexNbr = [[NSMutableArray alloc] initWithCapacity:9];
    yIndexNbr = [[NSMutableArray alloc] initWithCapacity:9];
    
    [xCenter insertObject:@(dx/2 + (xIndex-1)*dx) atIndex:0];
    [xCenter insertObject:@(dx/2 + (xIndex)  *dx) atIndex:1];
    [xCenter insertObject:@(dx/2 + (xIndex+1)*dx) atIndex:2];
    [xCenter insertObject:@(dx/2 + (xIndex-1)*dx) atIndex:3];
    [xCenter insertObject:@(dx/2 + (xIndex)  *dx) atIndex:4];
    [xCenter insertObject:@(dx/2 + (xIndex+1)*dx) atIndex:5];
    [xCenter insertObject:@(dx/2 + (xIndex-1)*dx) atIndex:6];
    [xCenter insertObject:@(dx/2 + (xIndex)  *dx) atIndex:7];
    [xCenter insertObject:@(dx/2 + (xIndex+1)*dx) atIndex:8];

    [yCenter insertObject:@(dy/2 + (yIndex-1)*dx) atIndex:0];
    [yCenter insertObject:@(dy/2 + (yIndex-1)*dx) atIndex:1];
    [yCenter insertObject:@(dy/2 + (yIndex-1)*dx) atIndex:2];
    [yCenter insertObject:@(dy/2 + (yIndex)  *dx) atIndex:3];
    [yCenter insertObject:@(dy/2 + (yIndex)  *dx) atIndex:4];
    [yCenter insertObject:@(dy/2 + (yIndex)  *dx) atIndex:5];
    [yCenter insertObject:@(dy/2 + (yIndex+1)*dx) atIndex:6];
    [yCenter insertObject:@(dy/2 + (yIndex+1)*dx) atIndex:7];
    [yCenter insertObject:@(dy/2 + (yIndex+1)*dx) atIndex:8];

    [Nindex insertObject:@((xIndex-1) + nx*(yIndex-1)) atIndex:0];
    [Nindex insertObject:@((xIndex  ) + nx*(yIndex-1)) atIndex:1];
    [Nindex insertObject:@((xIndex+1) + nx*(yIndex-1)) atIndex:2];
    [Nindex insertObject:@((xIndex-1) + nx*(yIndex  )) atIndex:3];
    [Nindex insertObject:@((xIndex  ) + nx*(yIndex  )) atIndex:4];
    [Nindex insertObject:@((xIndex+1) + nx*(yIndex  )) atIndex:5];
    [Nindex insertObject:@((xIndex-1) + nx*(yIndex+1)) atIndex:6];
    [Nindex insertObject:@((xIndex  ) + nx*(yIndex+1)) atIndex:7];
    [Nindex insertObject:@((xIndex+1) + nx*(yIndex+1)) atIndex:8];

    [xIndexNbr insertObject:@(xIndex-1) atIndex:0];
    [xIndexNbr insertObject:@(xIndex  ) atIndex:1];
    [xIndexNbr insertObject:@(xIndex+1) atIndex:2];
    [xIndexNbr insertObject:@(xIndex-1) atIndex:3];
    [xIndexNbr insertObject:@(xIndex  ) atIndex:4];
    [xIndexNbr insertObject:@(xIndex+1) atIndex:5];
    [xIndexNbr insertObject:@(xIndex-1) atIndex:6];
    [xIndexNbr insertObject:@(xIndex  ) atIndex:7];
    [xIndexNbr insertObject:@(xIndex+1) atIndex:8];
    
    // check for possible collision with all applicable neighbors
    for (int index = 0; index < 9; index++) {
        int Nval = [[Nindex objectAtIndex:index] intValue];
        int xNbr = [[xIndexNbr objectAtIndex:index] intValue];
        float xCen = [[xCenter objectAtIndex:index] floatValue];
        float yCen = [[yCenter objectAtIndex:index] floatValue];

        if (xNbr >= 0 && xNbr < nx
              && index != 4
            && fabsf(x-xCen) < (dx/2 + R)
            && fabsf(y-yCen) < (dy/2 + R)
            && Nval >= 0 && Nval < nx*ny) {

            // ball coordinates relative to the square center
            float xRel = x - xCen;
            float yRel = y - yCen;

            int val = [[LGEO objectAtIndex:Nval] intValue];
            
            if (val == 1) {
                
            // hit a solid block
            if (fabsf(xRel) > fabsf(yRel)) {
                ux = -COR*ux;
                x += ux;
            } else {
                uy = -COR*uy;
                y += -uy;
            }
                
            }

        }
    }

    // dynamics
    ux += 0.2*ax;
    uy += 0.2*ay;
}

@end

The basic idea of the ball-maze collision logic is this: At each time instant, we find out the location of the ball within the 2D (LGEO) array and find out the status (1 or 0) of all 8 neighboring cells. If the ball hits a neighboring solid block, we reverse the appropriate velocity component depending upon the location of the impact. The picture below shows a schematic of the idea, where I show balls impacting a solid square block from various directions.

Screen Shot 2014-05-24 at 5.46.30 AM

For the balls on the left and right of the block, the x-component of the ball center (relative to the square center) is more than the y-component. In such a case, we reverse the x-component of the velocity. For the balls to the top and bottom, we reverse the y-component of the velocity.

The values in the LGEO array can be changed in real-time by the user. These touch-based events are handled within the Controller. The method used to implement this logic is copied below for reference.

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch* t in touches) {
        CGPoint touchLocation;
        touchLocation = [t locationInView:blackBox];
        float x = touchLocation.x;
        float y = touchLocation.y;

        float width = blackBox.frame.size.width;
        float height = blackBox.frame.size.height;
        
        float dx = width / model.nx;
        float dy = height / model.ny;
        
        // find out array location where finger touches the screen
        int xIndex = x/dx;
        int yIndex = y/dy;
        int N = xIndex + model.nx*yIndex;
        
        // if touch is within array bounds, toggle LGEO value
        if (xIndex >=0 && xIndex < model.nx && yIndex >=0 && yIndex < model.ny) {
            int val = [[model.LGEO objectAtIndex:N] intValue];
            
            // update entry at that location in the model
            [model.LGEO removeObjectAtIndex:N];
            [model.LGEO insertObject:@(1-val) atIndex:N];

            [maze.LGEO removeObjectAtIndex:N];
            [maze.LGEO insertObject:@(1-val) atIndex:N];
        }
    }
}

Notice that we have a bounds check to make sure we don’t specify memory locations outside the specified array limits. Each touch in the UIView window toggles the LGEO array value at that location.

A side note: For those of us coming from C/C++, it may seem a little strange to not have to constantly clean up after our array declarations on the heap. Objective-C implements this clean up automagically via automatic reference counting.

Now that you understand the basic idea of how to specify a maze and implement collision detection, it is just a matter of detail to extend this project to create a game with various difficulty levels and keep track of the score and things of that sort. Perhaps I will return some day to make a game of this sort.

Bis dahin, Happy Xcoding!

iOS – Embedding Web Pages

This short post is a tutorial on how to embed a specified webpage on the screen using the UIWebView class. This class includes several methods to load web content and several properties to control how the web page is displayed inside the window.

The screenshot shows a simple example where I embed the exact same web page (http://www.joshiscorner.com) in two different UIWebView objects:

Screen Shot 2014-05-19 at 11.43.17 AM

STEP 1: Set up the storyboard

Drag two UIWebView objects to the storyboard and size both views to be 280 by 250.

STEP 2: Make connections

Make IBOutlet connections by ctrl-clicking from the UIWebViews on the storyboard to the ViewController interface.

STEP 3: Specify the URL and display it inside the window

The ViewController implementation is copied below.

#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) IBOutlet UIWebView *webView1;
@property (strong, nonatomic) IBOutlet UIWebView *webView2;
@end

@implementation ViewController
@synthesize webView1;
@synthesize webView2;

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    NSString *string = @"http://www.joshiscorner.com";
    NSURL *url = [NSURL URLWithString:string];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    [webView1 loadRequest:request];

    [webView2 loadRequest:request];
    webView2.scalesPageToFit = YES;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

As usual, we synthesize the two UIWebView properties and then use the NSURL class to specify the URL we wish to load. In this case, we load the same URL in both the windows. But in the top window, we load the page “as is”, that is using the width specified in the HTML/CSS on the remote server. In this case, the width of the page is set to 970px, while the width of the iPhone screen is 320px. So when we display the page “as is”, only a fraction of the content is seen inside the window.

In the second view, we use the “scalesPageToFit” property of the UIWebView object and set it to YES. This forces the view to display the entire width of the web page.

You can learn more about the UIWebView class on the Apple Developer website.

iOS – Paintbrush App for the iPhone

How do I draw free-form shapes on the iPhone screen with my finger? This post is an introduction to a simple paintbrush style app that does exactly that. Actually, this type of app is much more suitable for an iPad, but we’ll stick to the iPhone for now.

Screen Shot 2014-05-15 at 5.04.44 PM

In the above app, you simply place your finger inside the black UIView window and start drawing whatever you want. The default drawing color is white. To change to a different color, just click on the appropriate color buttons. To keep things simple, there are only 5 color options.

How it works under the hood

The heart of this app is a set of pre-built methods that can sense touch events and return the coordinates (relative to the view) where your finger touches and interacts with the screen. There are three primary touch events:

  • Your finger touches the screen for the first time (touch begins)
  • Your finger moves from point A to point B on the screen
  • Your finger leaves the screen (touch ends)

This is how these methods appear in actual code:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

The methods are inserted in the View Controller implementation, and the usual practice is to override the default implementations (which do nothing) with our own custom implementation.

Like the other apps we developed so far, the ViewController interface contains IBOutlets, IBAction items from the storyboard and other properties used in the implementation. Here is the ViewController interface for this app:

#import 
#import "Circle.h"

@interface ViewController : UIViewController

@property (strong, nonatomic) IBOutlet UIView *blackBox;
@property (strong, nonatomic) NSMutableArray* circleArray;
@property int i;
@property UIColor* currentColor;

- (IBAction)redColor:(id)sender;
- (IBAction)blueColor:(id)sender;
- (IBAction)yellowColor:(id)sender;
- (IBAction)greenColor:(id)sender;
- (IBAction)whiteColor:(id)sender;
- (IBAction)clearScreen:(id)sender;

@end

The implementation file contains the meat of the app and I encourage you to uncomment some of the NSLog calls to get a feel of the numbers (x and y coordinates) returned by the three touch methods.

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize blackBox;
@synthesize circleArray;
@synthesize i;
@synthesize currentColor;

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    blackBox.clipsToBounds = YES;
    circleArray = [[NSMutableArray alloc] initWithCapacity:1000];
    i = 0;
    currentColor = [UIColor whiteColor];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch* t in touches) {
        CGPoint touchLocation;
        touchLocation = [t locationInView:blackBox];
        //NSLog(@"touchedBegan at %f %f", touchLocation.x, touchLocation.y);
    }
}

- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch* t in touches) {
        CGPoint touchLocation;
        touchLocation = [t locationInView:blackBox];
        //NSLog(@"touchesMoved at %f %f", touchLocation.x, touchLocation.y);
        float x = touchLocation.x;
        float y = touchLocation.y;
        float R = 10.0;
        CGRect circleFrame = CGRectMake(x-R, y-R, 2*R, 2*R);
        Circle* circle = [[Circle alloc] initWithFrame:circleFrame];
        [circle setBackgroundColor:[UIColor clearColor]];
        circle.circleColor = currentColor;
        [circleArray addObject:circle];
        [blackBox addSubview:circleArray[i]];
        i++;
    }
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch* t in touches) {
        CGPoint touchLocation;
        touchLocation = [t locationInView:blackBox];
        //NSLog(@"touchesEnded at %f %f", touchLocation.x, touchLocation.y);
    }
}

- (IBAction)clearScreen:(id)sender {
    for (int kount=0; kount<i; kount++) {
        [circleArray[kount] removeFromSuperview];
    }
    [circleArray removeAllObjects];
    circleArray = nil;
    circleArray = [[NSMutableArray alloc] initWithCapacity:1000];
    i = 0;
}

- (IBAction)redColor:(id)sender {
    currentColor = [UIColor redColor];
}

- (IBAction)blueColor:(id)sender {
    currentColor = [UIColor blueColor];
}

- (IBAction)yellowColor:(id)sender {
    currentColor = [UIColor yellowColor];
}

- (IBAction)greenColor:(id)sender {
    currentColor = [UIColor greenColor];
}

- (IBAction)whiteColor:(id)sender {
    currentColor = [UIColor whiteColor];
}


@end

Once the finger coordinates (x, y) are obtained using these methods, we allocate a Circle object (subclass of UIView) that draws a filled circle of a certain size at that (x, y) location. As your finger moves, the coordinates keep changing and we scramble more and more Circle objects to draw additional circles along the path. If you move your finger too quickly, you will actually see the individual circles. Go slow if you want to create the illusion of drawing a continuous thick line.

Notice that we use a NSMutableArray to keep track of all the Circle objects because later on, we want to clear the entire screen by removing all those views from the superview. The NSMutableArray is initialized with a maximum capacity to hold 1000 objects. Obviously, as we draw more and more circles, the size of the NSMutableArray and the memory needed by the app increases. Because of this reason, you will notice some sluggishness in the drawing when you draw far too many shapes in the view. All this memory is released when we hit the CLEAR button (triggering the clearScreen method above).

Finally, here is the interface and implementation file for the Circle class:

#import <UIKit/UIKit.h>

@interface Circle : UIView
@property UIColor* circleColor;
@end

Clicking the color buttons in the UI triggers methods that change the circleColor property. Background images of the appropriate color were used for all the color buttons.

#import "Circle.h"

@implementation Circle

@synthesize circleColor;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

// Custom drawing: circle filled with the specified color
- (void)drawRect:(CGRect)rect
{
    // get the current context
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // context size in pixels
    size_t WIDTH = CGBitmapContextGetWidth(context);
    size_t HEIGHT = CGBitmapContextGetHeight(context);
    
    // for retina display, 1 point = 2 pixels
    // context size in screen points
    float width = WIDTH/2.0;
    float height = HEIGHT/2.0;
    
    // center coordinates
    float xCen = width/2.0;
    float yCen = height/2.0;
    float maxR = width/2.0;     // WIDTH = HEIGHT in this case

    // circle of specified color
    CGContextBeginPath(context);
    CGContextAddArc(context, xCen, yCen, maxR, 0, 2*M_PI, YES);
    [circleColor setFill];
    CGContextDrawPath(context, kCGPathFill);
}

@end

If you wish to reduce the thickness of the lines in your drawing, simply reduce the frame size when you draw the Circle object. The size I am presently using is 20 points (circle radius = 10).

In summary, this post was a quick introduction to the world of sensing touch events in iOS and using this information to create a simple paintbrush-style app. Want some suggestions for using this app? How about teaching toddlers how to draw numbers and letters in English or in your native alphabet?

As always, you can download the entire source code for this project from my GitHub link:

https://github.com/jabhiji/ios-touch-paintbrush.git

Happy Xcoding!

iOS – Return to HOLES

In a previous post, I talked about my first iOS game using PhoneGap, which I called HOLES. At that time, I did not know as much about Xcode and Objective-C as I do now. Because of this reason, I wrote that game using HTML5/JavaScript and used PhoneGap to port the game to iOS.

Now that Objective-C is becoming more and more easy to use, I decided to revisit the game and write a completely native version. Here are some screenshots from version 1.0:

Screen Shot 2014-05-13 at 7.24.02 AM

I admit that it took me a lot more time to write this native iOS version compared to the HTML5 version, primarily because I am relatively new to Objective-C and to object oriented programming (OOP) in general. But it was well worth the effort because I learned several new things while working on this project and I believe the end result is an app that is more polished compared to the HTML5 version.

The entire project can be downloaded from my GitHub page using the following link:

https://github.com/jabhiji/ios-holes.git

What follows is a brief summary of the design – and some nuggets from Objective-C  – that I hope will be helpful to the new game developer.

The Model

Think about what parameters we might need in the abstract model of our game. To begin with, we are using the accelerometer to control the movement of a ball on screen. So we need to know the ball radius and the location (x, y) on screen. We also need the acceleration and velocity components and need some logic to (1) update the ball position (2) check for collisions with the domain walls (3) rotate the hole pattern (4) check if the ball falls inside a hole (5) check if the ball reaches the flag and (6) update the score and number of balls remaining and (7) detect when the game ends and reinitialize all parameters when the user presses the RESTART button.

All this and more is done using the GameModel class and the interface for this class is copied below.

#import <Foundation/Foundation.h>

@interface GameModel : NSObject

// center of the ball
@property float x, y, R;

// ball velocity
@property float ux, uy;

// coefficient of restitution
@property float COR;

// ball acceleration
@property float ax, ay;

// view size in points
@property float width;
@property float height;

// game score
@property int score;

// balls remaining
@property int ballsLeft;

// holes
@property int numberOfHoles;
@property NSMutableArray* xBH;
@property NSMutableArray* yBH;
@property NSMutableArray* radiusBH;
@property int ballInsideHole;
@property float dtheta;

// methods
- (void) setInitialBallPosition;
- (void) updateBallPosition;
- (void) resetHoles;
- (void) updateHoles;
- (void) checkHoleCapture;

@end

As usual, the idea is to instantiate an object of this class in ViewController and use the above properties and methods to help the controller send the appropriate data to the view for displaying the ball and hole pattern on screen and figuring out how the scene changes with time.

This app makes heavy use of an Objective-C class called NSMutableArray, which is a convenient way to deal with an array of objects. The Apple Developer website sums this up perfectly:

The NSMutableArray class declares the programmatic interface to objects that manage a modifiable array of objects. This class adds insertion and deletion operations to the basic array-handling behavior inherited from NSArray.

In the GameModel class, we use NSMutableArray to create an array of x-coordinates, y-coordinates and radii of the black holes. This is how you allocate and initialize the array:

        
        numberOfHoles = 5;

        xBH = [[NSMutableArray alloc] initWithCapacity:numberOfHoles];
        yBH = [[NSMutableArray alloc] initWithCapacity:numberOfHoles];
        radiusBH = [[NSMutableArray alloc] initWithCapacity:numberOfHoles];
        
        [xBH addObject:@(0.2*width)];
        [yBH addObject:@(0.2*height)];
        [radiusBH addObject:@(25.0)];
        
        [xBH addObject:@(0.8*width)];
        [yBH addObject:@(0.2*height)];
        [radiusBH addObject:@(25.0)];
        
        [xBH addObject:@(0.5*width)];
        [yBH addObject:@(0.5*height)];
        [radiusBH addObject:@(25.0)];
        
        [xBH addObject:@(0.2*width)];
        [yBH addObject:@(0.8*height)];
        [radiusBH addObject:@(25.0)];
        
        [xBH addObject:@(0.8*width)];
        [yBH addObject:@(0.8*height)];
        [radiusBH addObject:@(25.0)];

If you wish to access the data (floating-point numbers) stored in this array, you can use the following:

        
        float xH = [[xBH objectAtIndex:i] floatValue];
        float yH = [[yBH objectAtIndex:i] floatValue];
        float rH = [[radiusBH objectAtIndex:i] floatValue];

In this case, we are using simple floating point numbers as the “objects” stored in this array, but the same idea works for storing an array of objects of any class. This is what makes NSMutableArray a powerful and useful tool.

Like “addObject”, another useful method in NSMutableArray is “insertObject:(object) atIndex:(integer)”. We use this to reset the hole coordinates in one of the our GameModel class methods:

- (void) resetHoles
{
    float xH, yH;
    
    xH = 0.2*width;
    yH = 0.2*height;
    [xBH insertObject:@(xH) atIndex:0];
    [yBH insertObject:@(yH) atIndex:0];
    
    xH = 0.8*width;
    yH = 0.2*height;
    [xBH insertObject:@(xH) atIndex:1];
    [yBH insertObject:@(yH) atIndex:1];
    
    xH = 0.5*width;
    yH = 0.5*height;
    [xBH insertObject:@(xH) atIndex:2];
    [yBH insertObject:@(yH) atIndex:2];
    
    xH = 0.2*width;
    yH = 0.8*height;
    [xBH insertObject:@(xH) atIndex:3];
    [yBH insertObject:@(yH) atIndex:3];
    
    xH = 0.8*width;
    yH = 0.8*height;
    [xBH insertObject:@(xH) atIndex:4];
    [yBH insertObject:@(yH) atIndex:4];
}

We use NSMutableArray again in the Controller to manage the task of displaying all 5 holes.

The View(s)

The main items we need to display on screen are: (1) the yellow ball (2) the flag and (3) the black holes. Because all these shapes are easy to construct from geometric primitives, I decided to use custom UIView-based drawings. I called these classes Ball, Flag and Holes and they all inherit from the same UIView class.

The yellow ball:

// Custom drawing: yellow ball
- (void)drawRect:(CGRect)rect
{
    // get the current context
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // context size in pixels
    size_t WIDTH = CGBitmapContextGetWidth(context);
    size_t HEIGHT = CGBitmapContextGetHeight(context);
    
    // for retina display, 1 point = 2 pixels
    // context size in screen points
    float width = WIDTH/2.0;
    float height = HEIGHT/2.0;
    
    // center coordinates
    float xCen = width/2.0;
    float yCen = height/2.0;
    float maxR = width/2.0;     // WIDTH = HEIGHT in this case
    
    // yellow circle
    CGContextBeginPath(context);
    CGContextAddArc(context, xCen, yCen, maxR, 0, 2*M_PI, YES);
    [[UIColor yellowColor] setFill];
    CGContextDrawPath(context, kCGPathFill);
}

The black hole:

// custom drawing of a black hole
- (void)drawRect:(CGRect)rect
{
    // get the current context
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // context size in pixels
    size_t WIDTH = CGBitmapContextGetWidth(context);
    size_t HEIGHT = CGBitmapContextGetHeight(context);
    
    // for retina display, 1 point = 2 pixels
    // context size in screen points
    float width = WIDTH/2.0;
    float height = HEIGHT/2.0;
    
    // center coordinates
    float xCen = width/2.0;
    float yCen = height/2.0;
    float maxR = width/2.0;     // WIDTH = HEIGHT in this case
    
    // black circle
    CGContextBeginPath(context);
    CGContextAddArc(context, xCen, yCen, maxR, 0, 2*M_PI, YES);
    [[UIColor blackColor] setFill];
    CGContextDrawPath(context, kCGPathFill);
}

The flag:

// custom drawing for the flag
- (void)drawRect:(CGRect)rect
{
    // get the current context
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // context size in pixels
    size_t WIDTH = CGBitmapContextGetWidth(context);
    size_t HEIGHT = CGBitmapContextGetHeight(context);
    
    // for retina display, 1 point = 2 pixels
    // context size in screen points
    float width = WIDTH/2.0;
    float height = HEIGHT/2.0;
    
    // flag
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 0.2*width, 0.9*height);
    CGContextAddLineToPoint(context, 0.2*width, 0.1*height);
    CGContextAddLineToPoint(context, 0.8*width, 0.3*height);
    CGContextAddLineToPoint(context, 0.2*width, 0.5*height);
    CGContextClosePath(context);
    [[UIColor whiteColor] setFill];
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathFillStroke);
}

In the ViewController, we can instantiate objects of class Ball, Holes and Flag and specify where we want to display them in the superview.

The Controller

Because we need the accelerometer, we must add the CoreMotion framework to our project and include the corresponding header file in our interface. Here is the complete controller interface, with connections to the main storyboard:

controller interface

In the implementation file (ViewController.m), we talk with the model and views to get the game going. The first step is to synthesize the properties we defined in the interface so Objective-C can provide us with the corresponding setter and getter methods for using these objects.

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize model;
@synthesize motionManager;
@synthesize greenTable;
@synthesize ball;
@synthesize flag;
@synthesize holeArray;
@synthesize ballCount, showScore;
@synthesize timer;
@synthesize gameOverMessage;

// flag to check whether ball reached the flag
int reachedFlag = 0;

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    
    // hide "GAME OVER" label
    gameOverMessage.hidden = YES;
    
    // initialize flag view
    CGRect flagRect = CGRectMake(0, 0, 30, 30);
    flag = [[Flag alloc] initWithFrame:flagRect];
    [flag setBackgroundColor:[UIColor clearColor]];
    [greenTable addSubview:flag];
    
    // clip things outside the view
    greenTable.clipsToBounds = YES;
    
    // initialize Ball object
    CGRect ballRect = CGRectMake(100, 100, 40, 40);
    ball = [[Ball alloc] initWithFrame:ballRect];
    [ball setBackgroundColor:[UIColor clearColor]];
    
    // initialize model
    model = [[GameModel alloc] init];
    model.width  = greenTable.frame.size.width;
    model.height = greenTable.frame.size.height;
    [model setInitialBallPosition];
    [model resetHoles];
    
    // initial score is zero
    showScore.text = [NSString stringWithFormat:@"%i",0];
    ballCount.text = [NSString stringWithFormat:@"%i",model.ballsLeft];

    // init holeArray
    holeArray = [[NSMutableArray alloc] initWithCapacity:model.numberOfHoles];

    // draw holes
    for (int i=0; i<model.numberOfHoles; i++) {
        float xH = [[model.xBH objectAtIndex:i] floatValue];
        float yH = [[model.yBH objectAtIndex:i] floatValue];
        float rH = [[model.radiusBH objectAtIndex:i] floatValue];
        CGRect holeRect = CGRectMake(xH-rH, yH-rH, 2*rH, 2*rH);
        Holes* holeView = [[Holes alloc] initWithFrame:holeRect];
        [holeArray addObject:holeView];
        [holeArray[i] setBackgroundColor:[UIColor clearColor]];
        [greenTable addSubview:holeArray[i]];
    }
    
    // initialize motion manager
    motionManager = [[CMMotionManager alloc] init];
    motionManager.accelerometerUpdateInterval = 1.0/60.0;
    
    if ([motionManager isAccelerometerAvailable]) {
        
        [self startGameLoop];
        
    } else {
        
        NSLog(@"No accelerometer! You may be running on the iOS simulator...");
    }

}

Some things you may want to pay close attention to in the above code are:

  • Making the background transparent for UIView objects.
  • Clipping things outside the view
  • Creating an array of 5 objects of class “Holes” using NSMutableArray and displaying these objects.
  • Starting the accelerometer and the NSTimer based game animation.

The rest of the ViewController code deals with the main game loop, where we update the model and draw the ball and holes at their updated locations and keep track of the score and how many balls we have left.

I used a simple UILabel for displaying the “GAME OVER” message when we no longer have any balls left. This label is hidden from view for most of the game with

    // hide "GAME OVER" label
    gameOverMessage.hidden = YES;

When all balls are gone, we simply un-hide this label and bring it to the “front” using

            // show "GAME OVER" label
            [greenTable bringSubviewToFront:gameOverMessage];
            gameOverMessage.hidden = NO;

If you’ve read this far, you now have a pretty good idea of the thought process and coding decisions that went into making this game work. I think I am enjoying Objective-C more and more with each new app I write.

Happy Gaming!

iOS – Accelerometer App

A few weeks ago, I wrote about how to access the iPhone accelerometer using HTML5 and the PhoneGap API. In this post, I return to this topic and discuss a native Accelerometer app. Here is a screenshot of version 1.0:Screen Shot 2014-05-09 at 4.34.39 AM

In the above picture, I’ve kept the iPhone on a table, with the x and y axes in the plane of the table. The z-axis points up towards you, normal to the iPhone screen. The acceleration due to gravity (in our non-inertial frame of reference on the earths surface) is negative one (in units of g = 9.8 [m/s2]), which means it is in the direction of the negative z-axis. The numbers show the acceleration magnitude and the {x,y,z} components, with the minimum and maximum values. Pressing the reset button erases the minimum and maximum values.

The polar grid is for displaying the instantaneous acceleration in the x-y plane. Because the acceleration is along z in this picture, the yellow ball is at the origin (zero x and y components). The circles correspond to different g values. If we place the iPhone on its edge in a vertical position on the table, we get the following result:

Screen Shot 2014-05-09 at 4.47.58 AM

As you expected, the x-component is now 1.0 and the y and z components are zero. You can play with this app by downloading the source code from

https://github.com/jabhiji/ios-accelerometer.git

If you’ve been following my journey in iOS land, there were primarily two new things I learned while making this app:

  • Using the CoreMotion Framework – this is where all the “motion sensing” classes live. You need to add this framework to your project and instantiate a CMMotionManager object to talk to the accelerometer.
#import <UIKit/UIKit.h>
#import <CoreMotion/CoreMotion.h>
#import "Model.h"
#import "Circles.h"

@interface ViewController : UIViewController

// labels
@property (strong, nonatomic) IBOutlet UILabel *minMag;
@property (strong, nonatomic) IBOutlet UILabel *magnitude;
@property (strong, nonatomic) IBOutlet UILabel *maxMag;
@property (strong, nonatomic) IBOutlet UILabel *accX;
@property (strong, nonatomic) IBOutlet UILabel *accY;
@property (strong, nonatomic) IBOutlet UILabel *accZ;
@property (strong, nonatomic) IBOutlet UILabel *minX;
@property (strong, nonatomic) IBOutlet UILabel *minY;
@property (strong, nonatomic) IBOutlet UILabel *minZ;
@property (strong, nonatomic) IBOutlet UILabel *maxX;
@property (strong, nonatomic) IBOutlet UILabel *maxY;
@property (strong, nonatomic) IBOutlet UILabel *maxZ;

// motion manager
@property (strong, nonatomic) CMMotionManager *motionManager;

// rolling ball model
@property (strong, nonatomic) Model *model;

// the view
@property (strong, nonatomic) IBOutlet Circles *myView;

// the ball images
@property (nonatomic, strong) UIImage* yellowball;
@property (nonatomic, strong) UIImageView* ballView;
- (IBAction)resetValues:(id)sender;

@end

The implementation file shows how to initialize this new object and use it to talk to the accelerometer:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize minMag, magnitude, maxMag;
@synthesize minX, minY, minZ;
@synthesize accX, accY, accZ;
@synthesize maxX, maxY, maxZ;
@synthesize model;
@synthesize myView;
@synthesize motionManager;
@synthesize yellowball;
@synthesize ballView;

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    
    // ball image
    yellowball = [UIImage imageNamed:@"yellowBall.png"];
    
    // draw custom drawing inside the view (as background)
    [myView setNeedsDisplay];
    
    // crop anything that is outside the view
    myView.clipsToBounds = YES;
    
    // initialize model
    model = [[Model alloc] init];
    model.width  = self.myView.frame.size.width;
    model.height = self.myView.frame.size.height;
    [model setInitialBallPosition];
    
    // initialize UIIMageView object
    ballView = [[UIImageView alloc] init];
    ballView.image = yellowball;
    
    // initialize motion manager
    motionManager = [[CMMotionManager alloc] init];
    motionManager.accelerometerUpdateInterval = 1.0/60.0;

    if ([motionManager isAccelerometerAvailable]) {
        
        [self startGameLoop];
        
    } else {
        
        NSLog(@"No accelerometer! You may be running on the iOS simulator...");
    }

}

// get acceleration data and animate ball motion based on current acceleration
- (void) startGameLoop
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
          
            // acceleration components (X, Y and Z)
            model.accelerationX = accelerometerData.acceleration.x;
            model.accelerationY = accelerometerData.acceleration.y;
            model.accelerationZ = accelerometerData.acceleration.z;
.
.
.
.

@end
  • Using the CoreGraphics API for custom drawing inside a UI View, starting with getting the current graphics context and learning the difference between points and pixels. This stuff is found in Circles.m, the implementation file for the Circles class.
#import "Circles.h"

@implementation Circles

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    [self drawCircles];
}

// polar grid on which the instantaneous acceleration is displayed in real time
- (void) drawCircles
{
    // get the current context
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // context size in pixels
    size_t WIDTH = CGBitmapContextGetWidth(context);
    size_t HEIGHT = CGBitmapContextGetHeight(context);
    
    // for retina display, 1 point = 2 pixels
    // context size in screen points
    float width = WIDTH/2.0;
    float height = HEIGHT/2.0;
    
    // center coordinates
    float xCen = width/2.0;
    float yCen = height/2.0;
    float maxR = width/2.0;     // WIDTH = HEIGHT in this app
    float ONEGEE = 0.666*maxR;
    
    // big circle to fill corners
    CGContextBeginPath(context);
    CGContextAddArc(context, xCen, yCen, 3*ONEGEE, 0, 2*M_PI, YES);
    [[UIColor blueColor] setFill];
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathFillStroke);
    
    // outermost red circle (2g)
    CGContextBeginPath(context);
    CGContextAddArc(context, xCen, yCen, 2*ONEGEE, 0, 2*M_PI, YES);
    [[UIColor blueColor] setFill];
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathFillStroke);
    
    // blue circle (1g)
    CGContextBeginPath(context);
    CGContextAddArc(context, xCen, yCen, ONEGEE, 0, 2*M_PI, YES);
    [[UIColor blueColor] setFill];
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathFillStroke);
    
    // unfilled circles (0.5g)
    CGContextBeginPath(context);
    CGContextAddArc(context, xCen, yCen, 0.5*ONEGEE, 0, 2*M_PI, YES);
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathStroke);
    
    // unfilled circles (1.5g)
    CGContextBeginPath(context);
    CGContextAddArc(context, xCen, yCen, 1.5*ONEGEE, 0, 2*M_PI, YES);
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathStroke);
    
    // horizontal centerline
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 0, height/2);
    CGContextAddLineToPoint(context, width, height/2);
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathStroke);
    
    // vertical centerline
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, width/2, 0);
    CGContextAddLineToPoint(context, width/2, height);
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathStroke);
    
    // diagonal 1
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 0, 0);
    CGContextAddLineToPoint(context, width, height);
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathStroke);

    // diagonal 2
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, width, 0);
    CGContextAddLineToPoint(context, 0, height);
    [[UIColor blackColor] setStroke];
    CGContextDrawPath(context, kCGPathStroke);
}

@end

—-

Once your device has this app, there are some interesting experiments you can do to test how many g’s your body experiences while walking, running, walking up and down the stairs and so on and so forth. Here is a quick summary of the acceleration magnitude range in some random experiments:

  • Standing still – 1g
  • Normal walking – 0.7g to 1.8g
  • Walking up the stairs – 0.4g to 2.0g
  • Going downstairs – 0.1g to 3.3g
  • Jumping up and down – 0g to 2.5g
  • Vigorously shaking the phone – 0g to 3.5g

Finally, if you throw your iPhone in the air, it will temporarily experience “weightless” conditions (just like it does when you jump up and down) and the app will record the minimum acceleration magnitude to be zero. But the impulse when you throw it and catch it again could be up to 3.5g. The above set of results come with some caveats that you should keep in mind: (1) The data is obtained at 60 Hz. Thus, impulses imparted in a time interval less than 1/60th of a second may not be recorded accurately. (2) I was not able to get a reading more than 3.5g for the maximum acceleration. This might be the maximum limit of the hardware.

In summary, we learned how to write a native iOS app that queries the built-in accelerometer and displays the values on screen in real-time. In the next version of this app, I plan to add in another screen where we can plot the different accelerations as a single graph or multiple graphs, with time on the x-axis.