05 June 2013

Objective-C, Day 3

(Warning: non-FP content)

So, this is day 5 in my Undisclosed Location and I haven't gotten much done -- I'm still engaged in a job search, and spent about eight hours working on a "take-home test" for an employer (with a few interruptions), and I'm pursuing more leads, and I'm trying to socialize with my hosts occasionally, so there are some distractions. But I've got enough information to go on to start implementing something original.

What I want to implement is a small game. I'll get as far as I can in the back-end code today (the Model and Controller parts of the MVC "trinity") and we'll see just how productive I really am in Objective-C. I read most of Objective-C Programming by Aaron Hillegass last night -- it's a very quick read for someone well-versed in C, and it reiterates parts of the iOS Programming book. I haven't covered protocols, categories, blocks, or run loops, but I think I can get by without those things for now.

Many years ago there existed on old-school MacOS a small game called "Polar." It was a very simple game, written by a guy (Go Endo) who was probably a student at the time, but I was fond of it -- fond enough to save it for 23 years, with the intention of studying its design and re-implementing it in the future. (In fact, I've saved a lot of old bits and bobs like this). I made notes of how to beat the first 3 levels (it was one of those "incredibly simple game play, but maddeningly tricky" games), drew out the levels, made notes on how the objects behaved, etc. I haven't been able to run that game for a long time, but today I just got it working under SheepShaver. Here's what level 1 looks like (blown up a bit):

The penguin is your avatar. The rest of the objects are ice blocks, trees, hearts, bombs, mountains, and houses. The world is a sheet of ice. You can walk around on the ice. You can walk through trees. Some objects (trees, mountains, and houses) can't be moved. Bombs, hearts, and ice blocks move without friction -- if you push them, they will keep going until they hit the edge of the world or another object. If an sliding ice block hits another object, it stops. If you push it against another object, it crumbles and disappears. The penguin can walk through trees, but other objects can't. Bombs will blow up mountains when they slide into them. Everything else simply stops them. The goal of the game is to slide all the hearts into a house (cute, huh?) But because the ice is frictionless, it's incredibly easy to get objects stuck against walls or corners where you can no longer move them the way you need to. So you have to carefully plan out your moves, and if you get stuck, there's an option to start the level over.

I should mention that the original game had a copyright notice (1990), and was shareware ($2.00). I can't remember if I ever sent the author $2.00. I'm not sure how he would feel about me taking apart and trying to re-implement his game, or whether he'd try to assert that copyright prevented me from doing so, but I'll assume he's a nice guy and wouldn't care as long as I don't charge for it, and go ahead, on the theory that easier to ask forgiveness than permission. I was not able to find him online -- maybe "Go Endo" was a pseudonym?

Back in 1991 I came up with a C++ class design (actually, it doesn't quite look like C++; I think it was written using THINK C's object-oriented extensions, which are sort of lost in the mists of time to me -- what is that indirect keyword? What did #pragma options(virtual) do? I don't remember for sure, but let's see if we can come up with an Objective-C implementation. The objects, other than the penguin, which can face in different directions, don't seem to have any real distinct properties except for their locations in the board, so I'm tempted not to take the obvious route and implement a board full of instances of the objects. I'm more inclined to just define a class for the board, and let it encapsulate most of the game logic. For the objects themselves, I'd like to just make references (pointers) to their classes (the class object) rather than putting them in a container. That's apparently not really possible -- there are no predefined singletons for the class objects that are accessible at run-time the way there are for NSNull. So I have to make some (sort of) singletons.

Here's what I've got today:

@interface ArcticSlideTile : NSObject
// Not necessarily useful yet, but I am guessing it might
// be helpful to have a separate base class at some point.
@end

@interface ArcticSlideTileStateless : ArcticSlideTile
// In implementation, there will be a single shared instance.
@end

@interface ArcticSlideBomb : ArcticSlideTileStateless
// Bombs can be pushed and will slide until they
// hit an object and stop. If the object is a mountain,
// both bomb and mountain are destroyed and
// there should be an animation. If another object
// hits a bomb it stops (I'm not sure you can test this
// combination in the original game with the board layouts
// available
- (NSString*) description;
@end

@interface ArcticSlideEmpty : ArcticSlideTileStateless
// The penguin can walk on empty space. Pushable objects
// on empty space are on ice and they slide until something
// stops them.
- (NSString*) description;
@end

@interface ArcticSlideHeart : ArcticSlideTileStateless
// When a heart hits a house the heart disappears (getting
// all the hearts into the house is how you win the game).
// Otherwise they cannot be destroyed, and slide like other
// slidable items.
- (NSString*) description;
@end

@interface ArcticSlideHouse : ArcticSlideTileStateless
// Houses cannot be pushed and stop other objects
// except hearts. When a heart hits a house the heart
// disappears (getting the hearts into the house is
// how you win the game). So the model should keep track
// of the number of hearts on the board and trigger a
// "win the level" behavior when the last heart is 
// destroyed.
- (NSString*) description;
@end

@interface ArcticSlideIceBlock : ArcticSlideTileStateless
// Ice blocks can be pushed and will slide until they
// hit an object and stop. If they are pushed directly
// against an object they will be crushed (there should
// be an animation) and disappear.
- (NSString*) description;
@end

@interface ArcticSlideMountain : ArcticSlideTileStateless
// Mountains cannot be moved and are destroyed by bombs.
- (NSString*) description;
@end

@interface ArcticSlidePenguin : ArcticSlideTile
// The penguin is the avatar. It has the special
// quality of being able to face different directions
// in the original game, although that's because you 
// can click near him to turn him and make him walk in
// different directions. In a touchscreen implementation
// I'm not sure how this should be implemented -- maybe
// he can slide in discreet steps. The penguin has the
// ability to walk through trees. We might want to 
// implement this temporary state using using an
// "override" object reference in the model. Sliding
// objects might be implemented the same way.
typedef enum {
    north, south, east, west
}penguinDirection_e;

@property penguinDirection_e facing;
- (NSString*) description;
@end

@interface ArcticSlideTree : ArcticSlideTile
// Trees cannot be pushed or destroyed and stop
// all sliding objects, but the penguin avatar 
// character can walk through them.
- (NSString*) description;
@end

static const int board_width = 24, board_height = 4;
// The short board design is part of what makes it
// so easy to get sliding objects stuck against the
// edges of the world or in corners where you can no longer
// get around to the other side to push them. We could
// consider a bigger board later and maybe implement the
// original puzzles surrounded by impassible water.

@interface ArcticSlideModel : NSObject
{
    ArcticSlideTile* board[board_height][board_width];
}

- (id)init;
- (NSString*) description;

@end

@implementation ArcticSlideTile
{
}
@end

@implementation ArcticSlideTileStateless
{
}
@end

@implementation ArcticSlideBomb
- (NSString*) description
{
    return @"Bomb";
}
@end

@implementation ArcticSlideEmpty
- (NSString*) description
{
    return @"Empty";
}
@end

@implementation ArcticSlideHouse
- (NSString*) description
{
    return @"House";
}
@end

@implementation ArcticSlideIceBlock
- (NSString*) description
{
    return @"Ice Block";
}
@end

@implementation ArcticSlideMountain
- (NSString*) description
{
    return @"Mountain";
}
@end

@implementation ArcticSlidePenguin
- (NSString*) description
{
    return @"Mountain";
}
@end

@implementation ArcticSlideTree
- (NSString*) description
{
    return @"Tree";
}
@end

@implementation ArcticSlideModel
{
    ArcticSlideBomb *bomb;
    ArcticSlideEmpty *empty;
    ArcticSlideHeart *heart;
    ArcticSlideHouse *house;
    ArcticSlideIceBlock *ice_block;
    ArcticSlideMountain *mountain;
    ArcticSlidePenguin *penguin;
    ArcticSlideTree* tree;

}
- (id)init
{
    bomb = [[ArcticSlideBomb alloc] init];
    empty = [[ArcticSlideEmpty alloc] init];
    heart = [[ArcticSlideHeart alloc] init];
    house = [[ArcticSlideHouse alloc] init];
    ice_block = [[ArcticSlideIceBlock alloc] init];
    mountain = [[ArcticSlideMountain alloc] init];
    penguin = [[ArcticSlidePenguin alloc] init];
    tree = [[ArcticSlideTree alloc] init];

    for ( unsigned int idx_y = 0;
         idx_y < board_height; idx_y++ )
    {
        for ( unsigned int idx_x = 0;
             idx_x < board_width; idx_x++ )
        {
            board[idx_y][idx_x] = empty;
        }
    }
    return self;
}

- (NSString*)description
{
    NSMutableString *desc_str =
        [[NSMutableString alloc]init];
    
    [desc_str appendString:@"ArcticSlideModel board state:\n"];
    for ( unsigned int idx_y = 0;
         idx_y < board_height; idx_y++ )
    {
        for ( unsigned int idx_x = 0;
             idx_x < board_width; idx_x++ )
        {
            [desc_str
             appendString:[board[idx_y][idx_x] description]];
            [desc_str appendString:@" "];
        }
        [desc_str appendString:@"\n"];
    }
    return desc_str;
}

@end

My quiet time at my undisclosed location ends tomorrow. I'm not sure I'll be able to get back to this for a few days. I haven't gotten nearly as much done with this as I've hoped. I've been distracted by a lot of job-search things. But hey, it's a start!

No comments: