Updated Version: The Last Rock Curling v1.5

The latest update went live on the App Store this week! New features and fixes based on all of your valuable feedback. Most requested feature: 10 End Full Game is now here!

   

Here is the full list of changes:

+ Bonus 10 new Challenge levels, based on best shots in curling.
+ Full game is here: play a 10 end game with Friends or against Computer.
+ Added an 8-end, 6-rock Mixed Doubles mode.
+ Easy/Hard option for Computer opponent.
+ Fixed hog-line collisions to keep rock in play.
+ Minor tweaks to the physics and AI.
+ New Leaderboard for Ends Won.
+ Bug fix for achievements against computer.

Hope you enjoy the update!

Posted in Home, Product | Tagged , , , , , , , , | Leave a comment

Sneak Peak: new features for The Last Rock Curling

Just a quick preview of upcoming features in The Last Rock Curling.
Easy/Hard mode for Computer AI.

10 End Game.
Also Mixed Doubles. Just because.

Coding phase is wrapping up; hope to finish testing and bug fixing soon and submit to App Store.

Posted in Home, Product | Tagged , , , , , | 2 Comments

Tap to Curl: tip for Last Rock Curling

Here’s a little know tip for playing The Last Rock Curling: you can make the rock curl by tapping the curl indicator.

See the red circle at the top for the left-curl button. Just tap it before your rock crosses the hog line!

This may be useful for anyone playing on an iPhone3, as it does not have a gyroscope (that was added to iPhone4) — without gyroscope, rotation detection is not as accurate and you might see the rock curl the wrong way, depending on how you hold it.

Posted in Home, Product | Tagged , , , , | Leave a comment

How to Pause NSTimer in Objective-C

In my game, The Last Rock Curling, I rely on the convenience of NSTimer for certain triggers around animation and game play. Implementation of the computer opponent, in particular, makes heavy use of timers to delay the actions of moving the skip, throwing the rock and sweeping. Computations are quite fast, but delays help the player see what is going on and lend an impression that the opponent is “thinking”. NSTimer on iOS is an easy way to schedule an action to occur in the near future.

Unfortunately, there is no pause/resume ability built into NSTimer. And pause/resume is a necessary feature for any interactive real-time game, so it’s too bad this feature is not built-in to iOS.

There is a way to manually implement pause and resume functionality, by capturing the time remaining on a timer and then recreating it for the resume. Even so, it’s still a little tedious to manage your timers every place that you need them so I went a step further and implemented a utility class handle multiple timers at once with pause/resume functional. Scheduling a new timer is nearly identical to scheduling with NSTimer, so it’s easy to adapt or extend.

Here’s the code. This is released under the CC0 public domain license, so it’s free to use.

Example usage:

// Computer Opponent (delaying UI actions)
  [GameTimers scheduledTimerWithTimeInterval:1.4 target:self selector:@selector(moveSkip)];
...
  [GameTimers scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(throwRock)];
...
// Main UI Controller
- (void) pause {
    [GameTimers pauseAll];
}
- (void) resume {
    [GameTimers resumeAll];
}

Header:

@interface GameTimers : NSObject {
@private
  NSMutableArray* timers;
  NSMutableArray* pausedTimeRemaining;
  NSMutableArray* pausedActions;
}

+ (NSTimer*) scheduledTimerWithTimeInterval:(NSTimeInterval)seconds 
                                     target:(id)target 
                                   selector:(SEL)aSelector;

+ (void) pauseAll;
+ (void) resumeAll;
+ (void) stopAll;
@end

typedef struct {
    SEL selector;
    id value;
} idSEL;

// For building a NSValue that represents a selector and an id. 
static inline NSValue* idSELMake(SEL selector, id value) {
    idSEL s;  s.selector = selector;  s.value = value; 
    return [NSValue valueWithBytes:&s objCType:@encode(idSEL)];
}

Implementation:

#import "GameTimers.h"

static GameTimers* singleton = nil;

@implementation GameTimers

-(id)init {
    self = [super init];
    if (self) {
        timers = [[NSMutableArray alloc] init];
        pausedActions = [[NSMutableArray alloc] init];
        pausedTimeRemaining = [[NSMutableArray alloc] init];
    }
    return self;
}

-(void)dealloc {
    [timers release];
    [pausedActions release];
    [pausedTimeRemaining release];
    [super dealloc];
}

- (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector {
    // Wrap the action so we can manage the timer
    NSValue* userInfo = idSELMake(aSelector, target);
    NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(fire:) userInfo:userInfo repeats:NO];

    [timers addObject:timer];
    return timer;
}

// Helper for Stop and Pause to invalidate & remove NSTimers
- (void) clearTimers {
    for (NSTimer* timer in timers) {
        [timer invalidate];
    }
    [timers removeAllObjects];
}

- (void) stopAll {
    [self clearTimers];
    [pausedActions removeAllObjects];
    [pausedTimeRemaining removeAllObjects];
}

- (void) pauseAll {
    NSAssert([pausedActions count] == 0, @"Paused timers not empty");
    NSAssert([pausedTimeRemaining count] == 0, @"Paused timers not empty");

    // Compute the time remaining and store so that resuming can continue the timer
    for (NSTimer* timer in timers) {
        if (timer.isValid) {    // only look at timers that haven't fired yet
            NSTimeInterval timeRemaining = [timer.fireDate timeIntervalSinceNow];
            if (timeRemaining < 0) {
                timeRemaining = 0;
            }
            [pausedTimeRemaining addObject:[NSNumber numberWithDouble:timeRemaining]];
            [pausedActions addObject:timer.userInfo];
        }
        else {
            NSLog(@"Pausing timer that is invalid.");
        }
    }

    // Get rid of the timers.
    [self clearTimers];
}

- (void) resumeAll {
    NSAssert(timers.count == 0, @"Timers didn't get paused correctly");

    // Reschedule all the paused timers
    for (int i = 0, count = [pausedActions count]; i < count; i++) {
        double timeRemaining = [((NSNumber*)[pausedTimeRemaining objectAtIndex:i]) doubleValue];
        NSValue* userInfo = [pausedActions objectAtIndex:i];
        NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:timeRemaining target:self selector:@selector(fire:) userInfo:userInfo repeats:NO];

        [timers addObject:timer];
    }
    [pausedActions removeAllObjects];
    [pausedTimeRemaining removeAllObjects];
}

- (void) fire:(NSTimer*) timer {
    [timers removeObject:timer];    // remove first in case pause is triggered in the selector
    NSValue* fnValue = timer.userInfo;
    if (fnValue) {
        // Convert to idSEL and execute
        idSEL is;
        [fnValue getValue:&is];
        [is.value performSelector:is.selector];
    }
}

#pragma mark - Class Methods -

+ (GameTimers*) timers {
    @synchronized(self) {
        if (singleton == nil) {
            singleton = [[GameTimers alloc] init];
        }
    }

    return singleton;
}

+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector {
    return [[self timers] scheduledTimerWithTimeInterval:seconds target:target selector:aSelector];
}

+ (void)pauseAll {
    [[self timers] pauseAll];
}

+ (void)resumeAll {
    [[self timers] resumeAll];
}

+ (void)stopAll {
    [[self timers] stopAll];
}

@end
Posted in DevSpeak | Tagged , , , , , | 2 Comments

Vote on the next feature!

Thanks to everyone for the reviews and ratings in iTunes over the past year! I’ve tried to incorporate people’s suggestions into the game updates.

Now I’d like to hear what are the next things you’d like to see in The Last Rock. Choose from the list or add your own suggestion:

Posted in Home, Product | Tagged , , , | 2 Comments

The Last Rock Curling v1.4 – adds Game Center matches and iPhone5 support

Big update to The Last Rock Curling this week: I’ve released v1.4, which adds a new game mode to play matches against your friends! All you need to do is sign in or register with Apple’s Game Center after getting the new update. Then try the “Friends” mode on app start screen:

Standard Game Center UI is used for arranging matches and viewing the ongoing games:

Curling lends itself well to a turn-based approach. You can have many games ongoing at once and when your turn (ex. throwing a rock) is over, the other player receives a notification. Instant replay shows the last throw before your turn starts. The number of waiting games is indicated on the app icon in your home screen:

gamenotify

Other Updates

Game mechanics have been improved based on player’s suggestion (a big THANK YOU to everyone who has rated, reviewed or otherwise given me feedback!).

In-Turn / Out-turn Curl

Previously, I was using only the iOS accelerometer to detect the rotation gesture for twisting to curl. This supported a maximum range of devices (anything before the iPhone 4), but was not guaranteed to work correctly in all orientations, such as flat on a table. Now, I’m using the gyroscope + accelerometer API, which fixes those glitches.

Rock with No Handle

Previously, you could throw a rock straight, with no curl and it would go perfectly straight down the ice. But this is not what happens on a real sheet of curling ice; due to imperfections in the pebbled ice, you actually get a random trajectory; the rock will pick up a curl or drift off target. Now I’ve incorporated some randomness into “no handle” throws. The advantage returns to the player who can read the ice and choose the best curl.

Broom Wiggle

Some players were not immediately aware that the skip’s broom could be dragged to control the aim, since the broom graphic is rather static. I’ve added a little wiggle animation, which screams “touch me”. Wiggle stops after you have dragged it once. Sometimes the smallest visual effects can make a big difference to usability!

Tutorial Tweaks

Previously, the tutorial (consisting of 6 instruction screens) would be presented the first time that a player started a game. This was a bit too much of information overload – at the start you only need to know that the broom can be moved. So I split it up. Now, the relevant help screen is presented at the point where it is needed. Ex. after swiping to throw the rock, you get the information about how to curl. Hopefully this makes for a more effective tutorial for new players.

Computer Opponent Tweaks

Previously, it was a bit too common for the Computer opponent to throw very difficult shot – like a raise-double-takeout. I added some randomness (error) to the throws to make it play more fair.

More Voice-Overs

Added a few more voice-overs to let you know what is going on. All with accompanying text.

No Handle – you threw a rock without any curl. Who knows which way it will go?

Hurry Hard – you threw the rock with curl, but man is it going slow. Sweeping might just save the day.

Hogged Rock – in Instant Replay of match play, your opponent was weak. That’s why their rock doesn’t even cross the hog line at the far end of the sheet. Your chance to show them how it’s done.

iPhone 5 Support

Works great on the new iPhone. ’nuff said!

Check out the latest update on the App Store and let me know what you think!


Posted in Home, Product | Tagged , , , , , , , , | Leave a comment

Update: The Last Rock Curling v1.3.5 is out!

Just released a new update, fixing some bugs and enabling the leaderboard and achievements for The Last Rock Curling.

Get it on the App Store.

Posted in Home, Product | Tagged , , , , , | 2 Comments