Showing posts with label Objective-C. Show all posts
Showing posts with label Objective-C. Show all posts

24 June 2013

Objective-C, Day 6 (Back from Dylan-land)

I've been a little sick -- maybe something in our water, because our tap water started tasting like hose water -- but it seems to be clearing up. There's nothing like having flu-like symptoms to celebrate the first couple of days of summer! But I'm more-or-less back on my feet, although still a little queasy. Yesterday the weather station closest to our home in Saginaw hit 93, "feels like 100" with the humidity. I know that's nothing compared to some of the folks out west, but it came on us pretty fast, and I'd happily trade 100 in the low-humidity desert for 90 in Saginaw. I've got the A/C unit set up in the home office, since we finally need it, and I'm pressing on with my re-engineering of the old Mac Polar game.

The Dylan implementation I discussed last time helped focus my thinking about, if not the optimal, at least a fairly clear model for implementing game piece behavior. It also clarified what I should do with game objects in Objective-C, and that is "nothing." The "model" class still deserves to live, but the tile pieces just don't derive any benefit from being classes. The two main reasons are (1) Objective-C doesn't really support static methods in the sense that C++ does, and (2) Objective-C's dispatch mechanism isn't sophisticated enough to help us significantly save on "code to find code." So the tiles will be represented by plain old data, and we'll dispatch on their "types" with plain old logic.

The Dylan code has a small infrastructure of helper functions for accessing, filling, and logging the board state. I won't include all of it, because most of what it does is pretty clear from the function name, but there are functions like this:

define method getTileAtPos( model :: <model>, pos :: <pos-or-false> ) =>
    ( tile :: <tile> )
    if ( pos )
        getTileAtXY( model, pos.y-idx, pos.x-idx );
    else
        $the-edge;
    end if;
end;

define function getAdjacentPos( pos :: <pos>, dir :: <dir> )
    => ( pos-or-false :: <pos-or-false> )
    let y-offset :: <integer> = 0;
    let x-offset :: <integer> = 0;
    if ( dir == #"east" )
        x-offset := 1;
    elseif ( dir == #"south" )
        y-offset := 1;
    elseif ( dir == #"west" )
        x-offset := -1;
    elseif ( dir == #"north" )
        y-offset := -1;
    end if;
    let new-y-idx :: <integer> = pos.y-idx + y-offset;
    let new-x-idx :: <integer> = pos.x-idx + x-offset;
    if ( ( ( new-y-idx >= 0 ) & ( new-y-idx < $board-dim-y ) ) & 
         ( ( new-x-idx >= 0 ) & ( new-x-idx < $board-dim-x ) ) )
        make( <pos>, y-idx: new-y-idx, x-idx: new-x-idx );
    else
        #f
    end if;
end;

define method penguinPush( model :: <model> )
    => ( result :: <boolean> )
    let target-pos :: <pos-or-false> = 
        getAdjacentPos( model.penguin-pos, model.penguin-dir );
    let target-tile = getTileAtPos( model, target-pos );
    pushTile( model, model.penguin-dir, target-pos, target-tile );
end;

define method penguinMove( model :: <model>, dir :: <dir> )
    if ( model.penguin-dir ~= dir )
        model.penguin-dir := dir;
        format-out( "Penguin changed dir to %S\n", dir );
        force-output( *standard-output* );
    else
        if ( penguinPush( model ) )
            format-out ( "Penguin moved to %d, %d\n",
                model.penguin-pos.y-idx, model.penguin-pos.x-idx );
            force-output( *standard-output* );
        end if;
        if ( model.heart-count == 0 )
            format-out( "Heart count reached zero, level cleared!\n" );
            force-output( *standard-output* );
        end if;
    end if;
end;

define method penguinMoveTimes( model :: <model>, dir :: <dir>,
    times :: <integer> )
    for ( count from 1 to times )
        penguinMove( model, dir );
    end for;
end;

define method describe-tile( tile :: <tile> ) => ( str :: <string> )
    case
        ( tile == $the-empty     ) => "___ ";
        ( tile == $the-tree      ) => "tre ";
        ( tile == $the-mountain  ) => "mtn ";
        ( tile == $the-house     ) => "hou ";
        ( tile == $the-ice-block ) => "ice ";
        ( tile == $the-heart     ) => "hea ";
        ( tile == $the-bomb      ) => "bom ";
        otherwise                  => "??? ";
    end case;
end method;

define method describe-board( model :: <model> )
    for ( y-idx from 0 below $board-dim-y )
        for ( x-idx from 0 below $board-dim-x )
            format-out( "%S", 
                describe-tile( model.board[ y-idx, x-idx ]  ) );
        end for;
        format-out( "\n" );
    end for;
    force-output( *standard-output* );
end;

In Objective-C, I'm going to get rid of the singletons and tile classes altogether. They will live on in the comments, to clarify what the pseudo-object-dispatch is doing, and vestigially in the code. The board will have the same internal representation as the raw strings of data taken from the original Polar game resources. I'll keep my three main methods from the Dylan code -- pushing a tile, colliding, and sliding -- but these will be single Objective-C methods rather than multi-methods. The tiles are just chars:

#define POLAR_DATA_LEN_Y 4               // 4x24 grid
#define POLAR_DATA_LEN_X 24
#define POLAR_DATA_NUM_LEVELS 6          // In the original game

typedef char tile_t;

enum {
    polar_tile_empty = '0',
    polar_tile_tree,
    polar_tile_mountain,
    polar_tile_house,
    polar_tile_ice_block,
    polar_tile_heart,
    polar_tile_bomb,
    polar_tile_last = polar_tile_bomb
};

/*
    Not part of the level data; an extra flag value representing
    edge of board
*/
#define polar_tile_edge 'X'

typedef const char polar_level_array_t[POLAR_DATA_NUM_LEVELS]
                                      [POLAR_DATA_LEN_Y]
                                      [POLAR_DATA_LEN_X];

typedef char polar_board_array_t[POLAR_DATA_LEN_Y]
                                [POLAR_DATA_LEN_X];

extern polar_level_array_t polar_levels;

Why use #define for array indices and our tile pieces instead of const int and const char? Because using a const integral variable (yeah... a "const variable...") to dimension an array, or represent a case value for a switch statement, is still not standard C everywhere, although it is a common extension to allow the compiler to treat it as so in contexts like this. Enum works fine with characters. Oddly, I have a build issue when using enum values to define the array boundaries. I haven't figured out quite what that is all about -- I think it may be a Clang bug. But I'll worry about that later.

In the implementation file:

polar_level_array_t polar_levels =
{
    {
        "100000000000000100000400"
        "106020545000000000100100"
        "100000000000000050002300"
        "110000100000000000000000"
    },
    // Etc., for the other five levels
}

The model class gets one as a member:

@interface ArcticSlideModel : NSObject
{
    polar_level_array_t board;
    pos_t penguinPos;
    dir_e penguinDir;
    int heartCount;
}

We'll work ourselves down from the external API to the associated implementation:

// The external API
- (void)penguinMoveDue:(dir_e)dir;
- (void)penguinMoveNTimes:(int)n
                      due:(dir_e)dir;

penguinMoveNTimes:due: calls penguinMoveDue: which calls penguinPushDue:. In Dylan:

define method penguinPush( model :: <model> )
    => ( result :: <boolean> )
    let target-pos :: <pos-or-false> = 
        getAdjacentPos( model.penguin-pos, model.penguin-dir );
    let target-tile = getTileAtPos( model, target-pos );
    pushTile( model, model.penguin-dir, target-pos, target-tile );
end;

That's not strictly translatable to C, since we're taking advantage of a type-union to retrieve the position or #f with getAdjacentPos. This usage extends to the lower levels of the implementation, though, so for now we're going to continue to allow getAdjacentPos to return position values that are invalid, and explicitly check for them so we don't read or write at non-existent array indices.

pos_t getAdjacentPos( pos_t original_pos, dir_e dir )
{
    pos_t updated_pos = original_pos;
    int y_offset = 0;
    int x_offset = 0;
    switch ( dir )
    {
        case dir_east:
            x_offset = 1;
            break;
        case dir_south:
            y_offset = 1;
            break;
        case dir_west:
            x_offset = -1;
            break;
        case dir_north:
            y_offset = -1;
            break;
        default:
            NSLog( @"getAdjacentPos: invalid dir %d", dir );
    }
    updated_pos.y_idx += y_offset;
    updated_pos.x_idx += x_offset;;
    return updated_pos;
}

We rely on posValid to explicitly check for invalid tile cases:

BOOL posValid( pos_t pos )
{
    return ( ( ( pos.y_idx >= 0 ) &&
               ( pos.y_idx < POLAR_DATA_LEN_Y  ) ) &&
             ( ( pos.x_idx >= 0 ) &&
               ( pos.x_idx < POLAR_DATA_LEN_X ) ) );
}

That should be pretty non-controversial. Note that BOOL in Objective-C is not a real type; it's just a #define and a typedef based on char_t. So don't get a false sense of security -- it has the same problems that fake bool types always have, and always will have, in straight C.

Anyway, we can now implement our pushTile function. Here is the Dylan:

define generic pushTile( model :: <model>, dir :: <dir>,
    pos :: <pos-or-false>, target-tile :: <tile> );

// Handle walkable (empty or tree tile). The penguin
// is allowed to move onto this tile (indicated by
// returning #t).
define method pushTile( model :: <model>, dir :: <dir>,
    target-pos :: <pos>, target-tile :: <walkable> )
    => ( result :: <boolean> )
    model.penguin-pos := target-pos;
    #t;
end;

// Handle movable (bomb, heart, ice block) -- call
// collide which specializes in various combinations.
define method pushTile( model :: <model>, dir :: <dir>,
    target-pos :: <pos>, target-tile :: <movable> )
    => ( result :: <boolean> )
    let next-pos :: <pos-or-false>  = 
        getAdjacentPos( target-pos, dir );
    let next-tile = getTileAtPos ( model, next-pos );
    collide( model, dir, target-pos, target-tile,
        next-pos, next-tile );
    #f;
end;

// Handle fixed (house, mountain, edge) -- do nothing.
// The GUI might play a "fail" beep.
define method pushTile( model :: <model>, dir :: <dir>,
    target-pos :: <pos-or-false>, target-tile :: <fixed> )
    => ( result :: <boolean> )
    #f;
end;

Doing all our own dispatch logic, here is a single method in Objective-C:

- (BOOL)pushTile:(tile_t)target_tile
             due:(dir_e)dir
              at:(pos_t)target_pos
{
    switch ( target_tile )
    {
        /*
            Handle the "walkable" cases. The penguin is allowed to move
            onto these tiles, indicated by returning YES
        */
        case polar_tile_empty: /* FALL THROUGH */
        case polar_tile_tree:
            NSLog( @"pushTile: walkable\n" );
            self->penguinPos = target_pos;
            return YES;

        /*
            Handle "movable" cases. Call collide which specializes in
            various combinations.
        */
        case polar_tile_bomb:      /* FALL THROUGH */
        case polar_tile_heart:     /* FALL THROUGH */
        case polar_tile_ice_block:
            NSLog( @"pushTile: movable\n" );
            {
                pos_t next_pos = getAdjacentPos( target_pos, dir );
                /*
                    Note that next-pos can be invalid, which results
                    in the special "edge" tile value.
                */
                tile_t next_tile = [ self getTileAtPos:next_pos ];
                [ self collideTile:target_tile atPos:target_pos
                    due:dir withTile:next_tile
                    atSecondPos:next_pos ];
            }
            return NO;

        /*
            Handle "fixed" cases. Do nothing; the GUI might play
            a "fail" beep.
        */
        case polar_tile_mountain:   /* FALL THROUGH */
        case polar_tile_house:
            NSLog( @"pushTile: fixed\n" );
            return NO;

        default:
            NSLog( @"pushTile: unexpected tile value %d\n",
                   target_tile );
            return NO;
    }
}

And as in the Dylan version, for interesting interactions this method defers to another method:

- (void)collideTile:(tile_t)first_tile
              atPos:(pos_t)first_pos
                due:(dir_e)dir
           withTile:(tile_t)second_tile
        atSecondPos:(pos_t)second_pos
{
    BOOL empty = ( second_tile == polar_tile_empty );
    /* Blocking includes the special edge tile value */
    BOOL blocking = ( second_tile != polar_tile_empty );
    BOOL mountain = ( second_tile == polar_tile_mountain );
    BOOL house = ( second_tile == polar_tile_house );

    BOOL ice_block = ( first_tile == polar_tile_ice_block );
    BOOL bomb = ( first_tile == polar_tile_bomb );
    BOOL heart = ( first_tile == polar_tile_heart );
    BOOL movable = ( ice_block || bomb || heart );

    if ( bomb && mountain )
    {
        /*
            When a bomb meets a mountain, both bomb and mountain blow up
        */
        NSLog( @"collideTile: bomb / mountain\n" );
        [ self setTile:polar_tile_empty AtPos:first_pos ];
        [ self setTile:polar_tile_empty AtPos:second_pos ];
    }
    else if ( heart && house )
    {
        /*
            When a bomb heart meets a house, we are closer to winning
        */
        NSLog( @"collideTile: heart / house\n" );
        [ self setTile:polar_tile_empty AtPos:first_pos ];
        [ self decrementHeartCount ];
    }
    else if ( ice_block && blocking )
    {
        /*
            When an ice block is pushed directly against any
            blocking tile (including the board edge), it is destroyed.
        */
        NSLog( @"collideTile: ice block / blocking\n" );
        [ self setTile:polar_tile_empty AtPos:first_pos ];
    }
    else if ( movable )
    {
        if ( empty )
        {
            /*
                A movable tile pushed onto an empty tile will slide
            */
            NSLog( @"collideTile: movable / empty: start slide\n" );
            [ self slideTile:first_tile atPos:first_pos due:dir
                   toTile:second_tile atSecondPos:second_pos ];
        }
        else if ( blocking )
        {
            /*
                When a generic movable piece meets any other
                blocking pieces not handled by a special case
                above, nothig happens; it stops. Maybe play
                a "fail" beep.
            */
            NSLog( @"collideTile: movable / blocking\n" );
        }
    }
}

This could have been written with a bunch of ugly, redundant-looking switch statements, but the duplicated cases and defaults just don't seem as clear to me as making flags that precisely describe the nature of the "double dispatch" going on. In this program, having to spell out the logic (using code to find code) is not really onerous. But the problem comes, of course, in code where we keep having to add special cases. I could refactor this method to call some smaller methods but that doesn't seem like a real win. In the Dylan implementation if I wanted to add another special interaction, it might only require adding another generic function. That's assuming my whole class hierarchy didn't change.

Finally, the slide method:

- (void)slideTile:(tile_t)first_tile
            atPos:(pos_t)first_pos
              due:(dir_e)dir
           toTile:(tile_t)second_tile
      atSecondPos:(pos_t)second_pos
{
    BOOL empty = ( second_tile == polar_tile_empty );
    /* Blocking includes the special edge tile value */
    BOOL blocking = ( second_tile != polar_tile_empty );
    
    BOOL ice_block = ( first_tile == polar_tile_ice_block );
    BOOL movable = ( ice_block ||
                     first_tile == polar_tile_bomb ||
                     first_tile == polar_tile_heart );

    if ( ice_block && blocking )
    {
        // A specific movable tile, ice-block, meets a
        // blocking tile; don't call collide since the behavior
        // of a sliding ice block is different than a pushed ice
        // block. It just stops and doesn't break.
        NSLog( @"slideTile: ice block / blocking\n" );       
    }
    else if ( movable && empty )
    {
        // A movable tile interacting with an empty tile --
        // move forward on the board and call slide again.
        NSLog( @"slideTile: movable / empty\n" );
        pos_t third_pos = getAdjacentPos( second_pos, dir );
        tile_t third_tile = [ self getTileAtPos:third_pos ];
        [ self setTile:polar_tile_empty AtPos:first_pos ];
        [ self setTile:first_tile AtPos:second_pos ];
        [ self slideTile:first_tile atPos:second_pos due:dir
                  toTile:third_tile atSecondPos:third_pos ];
    }
    else if ( movable && blocking )
    {
        // A movable tile meets a blocking tile: call collide to
        // handle heart/house, bomb/mountain, edge of world, etc.
        NSLog( @"slideTile: movable / blocking\n" );
        [ self collideTile:first_tile atPos:first_pos due:dir
                  withTile:second_tile atSecondPos:second_pos ];
    }
}

That's the bulk of it. Here's an excerpt from the log as it finishes up the first level:

ArcticSlide[2279:c07] penguinPush: tile at 2, 5 pushed
ArcticSlide[2279:c07] pushTile: walkable
ArcticSlide[2279:c07] Penguin moved to: 2, 5
ArcticSlide[2279:c07] Penguin direction changed to EAST
ArcticSlide[2279:c07] Penguin moving EAST
ArcticSlide[2279:c07] penguinPush: tile at 2, 6 pushed
ArcticSlide[2279:c07] pushTile: movable
ArcticSlide[2279:c07] collideTile: movable / empty: start slide
ArcticSlide[2279:c07] slideTile: movable / empty
ArcticSlide[2279:c07] collideTile: heart / house
ArcticSlide[2279:c07] Heart count reached zero, level cleared!
ArcticSlide[2279:c07] ArcticSlideModel board state:
tre__________________________________________treice_____________________
tre_________mtn_______________________________________tre______tre______
tre____________________________________________________________hou______
tretre____________treice________________________________________________

I'll put in logic to play the remaining levels soon, as additional test cases.

Note that I kept the recursive call to slideTile. It's not an idiom commonly used in C and Objective-C. We only recurse when the moving tile traverses more than one empty tile, and so never more than 23 times. I like to write algorithms recursively when possible while sketching out code. If direct recursion like that is verboten, it can be removed. I don't think my compiler is optimizing it out. But the termination logic now starts to look redundant:

else if ( movable && empty )
    {
        while ( NO == blocking )
        {
            pos_t third_pos = getAdjacentPos( second_pos, dir );
            tile_t third_tile = [ self getTileAtPos:third_pos ];
            [ self setTile:polar_tile_empty AtPos:first_pos ];
            [ self setTile:first_tile AtPos:second_pos ];
            first_pos = second_pos;
            second_pos = third_pos;
            second_tile = third_tile;
            blocking = ( third_tile != polar_tile_empty );
        }
        if ( ice_block )
        {
            NSLog( @"slideTile: ice block / blocking\n" );
        }
        else
        {
            [ self collideTile:first_tile atPos:first_pos due:dir
                      withTile:second_tile atSecondPos:second_pos ];
        }
    }

And if I don't want to call back into methods in my own call chain at all -- that is, if I have to give up calling collideTile, well, I could do that but it would involve putting copying of the logic from collideTile into this method, and by that point this method will be badly breaking the "DRY" (Don't Repeat Yourself) axiom, so it might be clearer to turn collideTile and slideTile into one method.

Anyway, the heat is building up in my office and it is about dinnertime. I think it's time to move on to some user interface, so the app can actually be played on an untethered iOS device. I also am still struggling a bit to get going on a Haskell implementation. I know it can be done -- people describe Haskell as a good imperative language too, for modeling state as safely as possible -- but let's just say that the chapters and examples I'm reading haven't quite "gelled" in my brain. I still feel like someone studying a foreign language who can read it and understand it when spoken, but not speak it yet -- especially the Monadic dialects. But I'm still working on that.

UPDATE: I have put the source on GitHub, such as it is -- for now, ignore the license text; I need to pick an actual license. See: https://github.com/paulrpotts/arctic-slide-ios

16 June 2013

Objective-Dylan, or Perhaps Subjective-C?

Yesterday my wife took the kids with her on an overnight trip to Ann Arbor so I've had a bit of extra quiet time. How am I making use of this bounty? Getting on with some minor home repairs? Cleaning my office from top to bottom? Er, no... porting the game logic I've written so far in Objective-C back to Dylan, so that I can do some more thinking about it.

So after a phone job interview yesterday (which went well, I thought -- I'm optimistic about this possibility!) I started working on this task, and then about twelve hours later, around 2 a.m., I had the basic setup and population of the game board working. It's embarrassing to admit how long it took. I started on my Mac, and when I began encountering constant runtime errors switched over to my Ubuntu box, thinking that the Mac version of Open Dylan might just be broken (it isn't; I got the identical behavior on the Linux build). I finally figured out workarounds -- it's funny how taking a break clears my head far better than pressing on ever does -- then read a little Gene Wolfe (I'm working my way through In Green's Jungles, one of his books I've repeatedly tried and failed to finish), and fell asleep with no children in the bed to kick or otherwise interfere with a good night's sleep. I'm back up this morning, had a bath, and I'm drinking a large coffee with soy creamer and stevia and trying to hold off on a lunch break until I have some more done. It's about 10 a.m. and I'm expecting my family back in about six hours, so the race is on!

This has taken far longer than I hoped; I lost quite a bit of time stumbling across things in Open Dylan that still seem just plain broken. I had to start working on a smaller and smaller program to figure out exactly what was broken. These things I've flagged in comments, as places where, basically, I wish Dylan worked a certain way, and it doesn't. I may just be asking for something that doesn't quite match the original spec or isn't quite possible, but I'll share those with the Dylan Hackers team and see if it seems like I can help with them. The biggest thing that was broken, though, was me -- my brain, that is -- since it's been a long time since I've worked with Dylan's type-union and singleton pseudo-classes and I had forgotten the details. The compiler was not a big help with this, since it is such a dynamic language and leaves an awful lot of things to the runtime to figure out, which it does by throwing an error message that may or may not help much. The documentation is a bit scanty, but it does contain everything you need to know, if you re-read and squint at the scanty examples that are out there hard enough.

The good news is that the port is working and I'd like to share it. Dylan is still up there with Scheme (and now Haskell) as one of my favorite languages for designing programs -- yes, even though Dylan is quite old as languages go. I like to see what it can do especially with generic functions and its sophisticated model for object-oriented dispatch. I've been a little stymied as to how to express the design best in Objective-C. If it was a complicated game design, I wouldn't feel bad about having a program that looked complex. But it's really an elegantly simple game, and so I feel like the implementation should reflect that. My Objective-C implementation has been feeling more and more bloated and pointlessly complex, although it works, so my thought was to get it down to a simple implementation that takes full advantage of Dylan's object-oriented programming features, largely borrowed from CLOS, and then port that back to Objective-C, adding whatever minimalist support is needed to fake up some of the features that Dylan gives me that Objective-C doesn't have. This might be by way of also writing a Haskell or Scala implementation later, for yet more learning and language comparison, although really what I should focus on is getting the iOS GUI up and working so that I have something to show people.

Anyway, I've got a Dylan program that plays the Polar game, using singletons to represent tile types, and methods dispatched on singletons to handle specific kinds of collisions. The classes -- which are empty, pretty much used only for their usefulness as types, for driving dispatch -- are like so:

In Dylan you can create some instances, and create something called a type-union, which is something that is a type, I think, but not a class. You can use it to define a slot type or a parameter type. But you can't make one:

define constant $the-bomb = make();
define constant $the-empty = make();
define constant $the-heart = make();
define constant $the-house = make();
define constant $the-ice-block = make();
define constant $the-mountain = make();
define constant $the-tree = make();

define constant  = type-union(
    singleton( $the-bomb ), singleton( $the-empty ),
    singleton( $the-heart ), singleton( $the-house ),
    singleton( $the-ice-block ), singleton( $the-mountain ),
    singleton( $the-tree ), singleton( #f ) );

And eventually dispatch on singletons -- meaning that a given method will be called with it is called with references to the exact objects that you specify:

define method collide( model :: <model>, dir :: <dir>,
    heart-pos :: <pos>, heart-tile == $the-heart,
    house-pos :: <pos>, house-tile == $the-house )
    format-out( "collide: $the-heart / $the-house\n" );
    setTileAtPos( model, heart-pos, $the-empty );
    model.decrementHeartCount();
end;

That gives you an idea of how some of the code in the Dylan program is organized. I have it mostly working, however, I'm not going to present the full code quite yet because I have a crashing bug, and I haven't yet been able to figure out if it is a dumb mistake on my part or a compiler or runtime bug in Open Dylan. I've also asked the Dylan hackers to take a look at my design and see what they think -- if they can find, as I put it, "a simpler design struggling to get out." Which is always the challenge, when trying to write not just functional, but model code, isn't it?

07 June 2013

Objective-C, Day 5

(Warning: non-FP content). (I've been including this for the benefit of any "Planet X" aggregators who are including my feed, where X is a functional language like Haskell. I'm assuming you've got that by now and either don't care or are unsubscribing/ignoring this series if it bothers you. I expect to get back to more functional language code at some point, maybe even implementing the same problem. But I dabble in different languages and sometimes the digressions go on for a while...)

So, today's topic is dispatch. I'm starting to design and implement the logic for pieces interacting on the board. The key in object-oriented designs is always to determine who (which object) manages which state. The design I've come up with is a sort of hybrid design, where position on the board is not actually a property of the board tiles. Instead, there's a board which keeps track of them. The instances of, say, a tree on the board don't have any unique state, so I'm not instantiating different objects for each one; they all point to a singleton (which I should properly enforce with a singleton factory method at some point).

There are trade-offs. On paper, my design involved adding a "push" method to the classes for tile pieces. In a language like Dylan, this "push" method would be a generic function, and dispatch on multiple parameter types, so that I could write some very short methods and use the method dispatcher, instead of explicit if-then or switch logic to find the right bit of code at run-time according to the types of the interacting objects (either their literal types or an enum, or some such). I could even do this in C++ because it has method overloading based on the parameters -- as long as this is based on their static type known at compile-time. Which... isn't true in this case. I miss Dylan's generic function dispatch! Objective-C is a underpowered in this respect even compared to C++. For example, I'd like to be able to write methods like this for the bomb class (this is pseudocode):

bomb::push(mountain)
{
    // blow up the mountain
}

bomb::push(empty)
{
    // slide the bomb onto the tile
}

But I can't. I can't just use the class construct to organize methods to call without an instance -- there doesn't seem to be the equivalent of C++ static methods. There also is no equivalent of a pure virtual function; I can't declare the need for a push() method in the common base class of the tile pieces and have the compiler demand that I implement in any subclasses to make them instantiable. The closest I can come, I think, is to create a method that generates an error if it itself is called instead of being overridden in a subclass. That seems to lack semantic clarity. So maybe I could do this, with double dispatch, but it doesn't seem worth the trouble for a small number of collision behaviors, when the behavior isn't simply supported by the language. I keep telling myself "thin layer on top of C... thin layer on top of C..."

So last night I had my Mac running upstairs in the office, and I used my iPad downstairs to connect to it via VNC, and write some code, running it on the iPad simulator, which I then viewed and controlled with a real iPad (mad scientist laugh). It needs a little tweaking this morning but here's more-or-less what I came up with. Note that I have started using some different naming conventions for file-scope and local variables. I'm not sure they are very standard Objective-C but they are closer to what I've grown comfortable with over the years in C and C++.

typedef struct {
    int x_idx;
    int y_idx;
} pos_t;

typedef enum {
    dir_east,
    dir_west,
    dir_south,
    dir_north
} dir_e;

// A straight C function to return a pos_t updated with a dir_e;
// the result may be out of bounds
pos_t getUpdatedPos( pos_t original_pos, dir_e dir );
BOOL posValid( pos_t pos );

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't get the
// penguin avatar around to the other side to push them.
// We could consider a bigger board later and maybe
// implement the original puzzles surrounded by water,
// or something like that.

// Equivalent of C++ class forward declaration
@class ArcticSlideTile;
@class ArcticSlideBomb;
@class ArcticSlideEmpty;
@class ArcticSlideHeart;
@class ArcticSlideHouse;
@class ArcticSlideIceBlock;
@class ArcticSlideMountain;
@class ArcticSlideTree;

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

- (id)init;
- (id)initWithLevelIndex:(int)level_idx;
- (NSString*) description;
- (ArcticSlideTile*)getTileFromPosition:(pos_t)pos
                            inDirection:(dir_e)dir;
- (void)setTileAtPosition:(pos_t)pos
                       to:(ArcticSlideTile*)type;

@end

@interface ArcticSlideTile : NSObject

- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir;

@end


@interface ArcticSlideBomb : ArcticSlideTile
// 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. If another object hits a bomb
// it stops (I think -- I'm not sure it is possible to set 
// up a board such that you can slide something into a bomb).

// push is called when the penguin pushes against a tile.
// It returns YES if the penguin can move onto the tile with
// this action. This is only ever the case for a tree or empty
// tile.
- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir;
- (NSString*) description;
@end

@interface ArcticSlideEmpty : ArcticSlideTile
// The penguin can always step onto an empty tile
- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir;
- (BOOL)slideFromPosition:(pos_t)pos inDirection:(dir_e)dir;
- (NSString*) description;
@end

@interface ArcticSlideHeart : ArcticSlideTile
// 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.
- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir;
- (NSString*) description;
@end

@interface ArcticSlideHouse : ArcticSlideTile
// 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 removed.
- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir;
- (NSString*) description;
@end

@interface ArcticSlideIceBlock : ArcticSlideTile
// 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.
- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir;
- (NSString*) description;
@end

@interface ArcticSlideMountain : ArcticSlideTile
// Mountains cannot be moved and are destroyed by bombs.
- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir;
- (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.
- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir;
- (NSString*) description;
@end

Here's part of the implementation. It seems way too wordy; I need to rethink the amount of code required for each step. At the least, some refactoring seems to be in order. As I mentioned earlier, I'm not sure the tile classes are really earning their keep. I got rid of the singleton instantiation machinery but now there are order of initialization dependencies. I'll need to do further thinking as I consider what communication needs to happen between the "model" part and "controller" part -- how to interact with the GUI, how to indicate that tiles need to be redrawn, or animated transitions should play, or sound effects should play, or that the score is changed, and what to do when a level is completed. There is lots more to think about for such a simple little game! And of course I haven't really even begun to implement the "view" parts.

static ArcticSlideEmpty* empty_p;
static ArcticSlideTree* tree_p;
static ArcticSlideMountain* mountain_p;
static ArcticSlideHouse* house_p;
static ArcticSlideIceBlock* ice_block_p;
static ArcticSlideHeart* heart_p;
static ArcticSlideBomb* bomb_p;

static ArcticSlideModel* model_p;

pos_t getUpdatedPos( pos_t original_pos, dir_e dir )
{
    pos_t updated_pos = original_pos;
    int x_offset = 0;
    int y_offset = 0;
    if ( dir_east == dir )
    {
        x_offset = 1;
        y_offset = 0;
    }
    else if ( dir_west == dir )
    {
        x_offset = -1;
        y_offset = 0;
    }
    else if ( dir_north == dir )
    {
        x_offset = 0;
        y_offset = -1;
    }
    else if ( dir_south == dir )
    {
        x_offset = 0;
        y_offset = +1;
    }
    updated_pos.x_idx += x_offset;;
    updated_pos.y_idx += y_offset;
    return updated_pos;
}

BOOL posValid( pos_t pos )
{
    return ( ( ( pos.x_idx >= 0 ) ||
               ( pos.x_idx < board_width  ) ) ||
             ( ( pos.y_idx >= 0 ) ||
               ( pos.y_idx < board_height ) ) );
}

@implementation ArcticSlideTile

- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir
{
    // Should be implemented in subclass
    return NO;
}

@end

@implementation ArcticSlideBomb

- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir
{
    // Penguin has pushed bomb in the given direction.
    // Get our own position:
    pos_t bomb_pos = getUpdatedPos( pos, dir );
    // What are we being pushed into?
    ArcticSlideTile *target_tile_p =
    [model_p getTileFromPosition:bomb_pos
                     inDirection:dir];
    
    if ( nil == target_tile_p )
    {
        // Edge of the world. TODO:
        // queue a "boop" sound effect
    }
    else if ( mountain_p == target_tile_p )
    {
        // bomb pushed into mountain
        // TODO: queue animation of bomb moving onto
        // mountain, animate explosion
        // remove bomb and mountain
        pos_t new_bomb_pos = getUpdatedPos( bomb_pos, dir );
        [model_p setTileAtPosition:new_bomb_pos
                                to:empty_p];
        new_bomb_pos = getUpdatedPos( new_bomb_pos, dir );
        [model_p setTileAtPosition:new_bomb_pos
                                to:empty_p];
    }
    else if ( empty_p == target_tile_p )
    {
        // TODO: queue bomb moving into space
        pos_t new_bomb_pos = getUpdatedPos( bomb_pos, dir );
        // Set bomb at new position
        [model_p setTileAtPosition:new_bomb_pos
                                to:bomb_p];
        // Remove bomb from old position
        [model_p setTileAtPosition:bomb_pos
                                to:empty_p];

        // Bombs will continue to slide until stopped
        ArcticSlideTile *target_tile_p =
        [model_p getTileFromPosition:new_bomb_pos
                         inDirection:dir];

        while ( empty_p == target_tile_p )
        {
            // TODO: animate bomb moving into space
            pos_t new_bomb_pos = getUpdatedPos( bomb_pos, dir );
            // set bomb at new position
            [model_p setTileAtPosition:new_bomb_pos
                                    to:bomb_p];
            // remove bomb from old position
            [model_p setTileAtPosition:bomb_pos
                                    to:empty_p];
        }

        if ( mountain_p == target_tile_p )
        {
            // bomb pushed into mountain
            // TODO: queue animation of bomb moving
            // onto mountain, animate explosion
            // remove bomb and mountain
            [model_p setTileAtPosition:new_bomb_pos
                                    to:empty_p];
            new_bomb_pos = getUpdatedPos( new_bomb_pos, dir );
            [model_p setTileAtPosition:new_bomb_pos
                                    to:empty_p];
        }
    }
    // The penguin cannot actually move in this turn
    return NO;
}

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

@end

@implementation ArcticSlideEmpty
- (BOOL)pushFromPosition:(pos_t)pos inDirection:(dir_e)dir
{
    // If the penguin pushes onto an empty tile, he can always
    // move there
    return YES;
}

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

I'm leaving out unfinished tile classes for clarity, but here is the model implementation:

@implementation ArcticSlideModel

- (id)init
{
    // Initialize the global tile objects. I messed around
    // with singleton factory methods for creating a single
    // instance of each of these and accessing it everywhere
    // but the resulting code was too wordy to justify this.

    empty_p = [[ArcticSlideEmpty alloc] init];
    tree_p = [[ArcticSlideTree alloc] init];
    mountain_p = [[ArcticSlideMountain alloc] init];
    house_p = [[ArcticSlideHouse alloc] init];
    ice_block_p = [[ArcticSlideIceBlock alloc] init];
    heart_p = [[ArcticSlideHeart alloc] init];
    bomb_p = [[ArcticSlideBomb alloc] init];

    self = [super 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_p;
        }
    }

    return self;
}

- (id)initWithLevelIndex:(int)level_idx
{
    self = [self init];

    // Lookup table to decode the original Polar resource
    // data as strings
    ArcticSlideTile *
        polar_data_tile_map[POLAR_DATA_NUM_TILE_VALS] =
    {
        empty_p, tree_p, mountain_p, house_p, ice_block_p,
        heart_p, bomb_p
    };

    if ( level_idx > ( num_polar_levels - 1) )
    {
        NSLog(@"initWithLevelIndex: bad level_idx %d!\n",
              level_idx);
    }
    else
    {
        unsigned int level_data_idx = 0;
        for ( unsigned int idx_y = 0;
             idx_y < board_height; idx_y++ )
        {
            for ( unsigned int idx_x = 0;
                 idx_x < board_width; idx_x++ )
            {
                int polar_data_tile_val =
                    polar_levels[level_idx]
                                [level_data_idx] - '0';
                if ( ( polar_data_tile_val < 0 ) ||
                     ( polar_data_tile_val > 
                       polar_data_max_tile_val ) )
                {
                    NSLog(@"tile value %d out of range!\n",
                          polar_data_tile_val );
                    self = nil;
                }
                else
                {
                    board[idx_y][idx_x] =
                        polar_data_tile_map[polar_data_tile_val];
                    level_data_idx++;
                }
            }
        }
    }

    return self;

}

- (ArcticSlideTile*)getTileFromPosition:(pos_t)pos 
                            inDirection:(dir_e)dir
{
    pos_t updated_pos = getUpdatedPos(pos, dir);
    if ( posValid( updated_pos ) )
    {
        return board[updated_pos.y_idx]
                    [updated_pos.x_idx];
    }
    else
    {
        return nil;
    }
}

- (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:@"\n"];
    }
    return desc_str;
}

- (void)setTileAtPosition:(pos_t)pos to:(ArcticSlideTile*)type
{
    board[pos.y_idx][pos.x_idx] = type;
}

@end

It's not much yet, and it doesn't have any kind of user interface outside of NSLog, but my code will successfully respond to moving the penguin through trees, through open space, and pushing a bomb, which then moves into an open space, continues to slide until it comes up against a mountain, and destroys the mountain. I'm driving this with a test method like this:

NSLog(@"%@\n", model_p);
// Penguin starts at 0,0, on a tree tile
pos_t penguin_pos = { 0, 0 };

// Walk the penguin south onto another tree tile
ArcticSlideTile* tile_p =
[model_p getTileFromPosition:penguin_pos 
                 inDirection:dir_south];
NSLog(@"Penguin is facing: %@\n", tile_p);
BOOL allowed = [tile_p pushFromPosition:penguin_pos
                            inDirection:dir_south];
NSLog(@"Penguin allowed: %s\n", ( allowed ? "YES" : "NO" ) );
tile_p = [model_p getTileFromPosition:penguin_pos
                              inDirection:dir_south];
penguin_pos = getUpdatedPos(penguin_pos, dir_south);
NSLog(@"Penguin is facing: %@\n", tile_p);
    
// Walk the penguin east onto an empty space
tile_p = [model_p getTileFromPosition:penguin_pos
                              inDirection:dir_east];
NSLog(@"Penguin is facing: %@\n", tile_p);
allowed = [tile_p pushFromPosition:penguin_pos
                       inDirection:dir_east];
NSLog(@"Penguin allowed: %s\n", ( allowed ? "YES" : "NO" ) );
tile_p = [model_p getTileFromPosition:penguin_pos
                          inDirection:dir_east];
penguin_pos = getUpdatedPos(penguin_pos, dir_east);

// Try walking into a bomb, which should slide
// and blow up a mountain
tile_p = [model_p getTileFromPosition:penguin_pos
                          inDirection:dir_east];
NSLog(@"Penguin is facing: %@\n", tile_p);
allowed = [tile_p pushFromPosition:penguin_pos
                           inDirection:dir_east];
NSLog(@"Penguin allowed: %s\n", ( allowed ? "YES" : "NO" ) );

NSLog(@"%@\n", model_p);

I'll think on this whole model of updates some more. Maybe it can be even simpler. And I have to consider how the penguin state will be managed, including its orientation (in the original, the penguin can face in the cardinal directions). Should I preserve that in a touch-driven game user interface?

06 June 2013

Objective-C, Day 4

(Warning: more non-FP content)

It's time to get the board representation filled out with a real board. I've only got a couple of hours left before I have to pack up my computer to leave my Undisclosed Location, but let's see if I can get a little more done. Let's review what level 1 looks like:

Level layouts are taken from the original Macintosh Polar game created by Go Endo. These were originally MacOS resources of type 'STGE.' Let's see if we can decode them. Using ResEdit, the raw data for 'STGE' resource ID -16000 looks like:

0x0000 0x0000 0x0003 0x0001
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0001 0x0000
0x0000 0x0000 0x0000 0x0000
0x0004 0x0000 0x0000 0x0001
0x0000 0x0006 0x0000 0x0002
0x0000 0x0005 0x0004 0x0005
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0000 0x0000
0x0000 0x0001 0x0000 0x0000
0x0001 0x0000 0x0000 0x0001
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0000 0x0005
0x0000 0x0000 0x0000 0x0002
0x0003 0x0000 0x0000 0x0001
0x0001 0x0000 0x0000 0x0000
0x0000 0x0001 0x0000 0x0000
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0000 0x0000
0x0000 0x0000 0x0000

There are 99 16-bit values. My best guess is that this corresponds to the 24x4 grid (96 board positions) plus 3 extras for some kind of of header of footer data (maybe the total number of hearts is indicated, for example). There are 7 unique values, so it seems extremely likely that they correspond almost exactly to our eight different tile types, with zero representing a blank space. But the counts of each type don't _quite_ match up. The first board has 8 trees, 1 bomb, 2 hearts, 2 ice blocks, 2 mountains, 3 hearts, 1 house, and 1 penguin (there is always 1 penguin), while this 'STGE' resource has: 9 ones, 2 twos, 2 threes, 2 fours, 3 fives, and 1 six. The counts are very close, so this has to represent level 1, and the the 5 almost certainly represents a heart, but I'm not clearly seeing the layout. The first vertical column goes penguin, tree, tree, tree. I don't quite see a pattern that looks like that, but resources -1599 and -15996 give me a hint that the "extra" data is at the front: they contain 0x0007 and 0x0008 as their third values. Those don't appear anywhere else so they probably don't indicate tiles. So let's try rearranging resource -16000 without the first 6 bytes, remove redundant zeroes for clarity, and looking at the values aligned by groups of 24 instead of 4:

1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 4 0 0
1 0 6 0 2 0 5 4 5 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 2 3 0 0
1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

There's the board. The left column is actually all trees -- when the board first appears, the penguin is hiding a tree. There are actually nine trees. So the encoding looks like so: empty space = 0, tree = 1, mountain = 2, home = 3, ice block = 4, heart = 5, and bomb = 6. The penguin doesn't have a value, but his starting position is probably represented by the first two values, (0,0), most likely encoded as row index, column index to correspond to row-major indexing, and there are 3 hearts to count down as the game is solved.

Can we validate this with the second board? Yes, it looks like there's a 4 indicating 4 hearts. In all the boards I've seen so far (the first four), the penguin is in the upper left. The fifth resource has 0, 1 for its first two values, so I'm guessing I can confirm the encoding of the penguin's position if and when I get to that stage.

And now to come up with a quick way to populate the board in my code. As I'm still a complete n00b in Objective-C, and on the general principle that there's no real need to make this any more clever or less obvious than it has to be, let's just use a string with the data from the resource. Let's add an init method to the interface for the model class:

- (id)initWithLevelIndex:(int)level_idx;

And here is some data, and an initializer method:

static const int num_polar_levels = 1;
static const int polar_data_len = 96;
static const int polar_data_num_tile_vals = 7; // 0-6 inclusive
static const int polar_data_max_tile_val = 6;
static const NSString *polar_levels[num_polar_levels] =
{
    @"100000000000000100000400" \
    @"106020545000000000100100" \
    @"100000000000000050002300" \
    @"110000100000000000000000"
};

- (id)initWithLevelIndex:(int)level_idx
{
    // Call our own basic initializer. This will 
    // result in redundant setting of board values,
    // but maybe I will clean that up later.
    self = [self init];

    // A simple lookup table to decode the original
    // Polar resource data as strings
    ArcticSlideTile 
        *polar_data_tile_map[polar_data_num_tile_vals] = {
        empty, tree, mountain, house,
        ice_block, heart, bomb };

    if ( level_idx > ( num_polar_levels - 1) )
    {
        NSLog( @"initWithLevelLayout: level %d out of range!\n",
               level_idx );
        self = nil;
    }
    else
    {
        const NSString* level_str = polar_levels[level_idx];
        unsigned int level_data_idx = 0;
        for ( unsigned int idx_y = 0;
             idx_y < board_height; idx_y++ )
        {
            for ( unsigned int idx_x = 0;
                 idx_x < board_width; idx_x++ )
            {
                NSRange range = NSMakeRange(level_data_idx, 1);
                const NSString * item_str =
                    [level_str substringWithRange: range];
                int polar_data_tile_val = [item_str intValue];
                if ( polar_data_tile_val >
                    polar_data_max_tile_val )
                {
                    NSLog(@"tile val %d out of range!\n",
                        polar_data_tile_val );
                    self = nil;
                }
                else
                {
                    board[idx_y][idx_x] =
                        polar_data_tile_map[polar_data_tile_val];
                    level_data_idx++;
                }
            }
        }
    }
    return self;
}

Hmmm, that seems overly complicated. There probably is a better, more idiomatic Objective-C way to use NSStrings for that, but I'm tempted to just write it with a basic C string. Using NSString objects in this context didn't even really help me catch bugs or avoid crashes, since I had forgotten to initialize a heart object and the result was hitting a nil object pointer at runtime and crashing, pretty much the same result as dereferencing a null pointer in straight C except with a better error logged. I'm a little disconcerted by Objective-C's inability to allocate objects on the stack, but that comes down to the aforementioned "thin veneer" over C. I don't really care about the overhead in using method dispatch in this simple piece of code operating on such a small amount of data, but I do care about simplicity. Just to compare, here's a more straightforward C implementation of that inner loop:

static const char *polar_levels[num_polar_levels] =
{
    "100000000000000100000400"
    "106020545000000000100100"
    "100000000000000050002300"
    "110000100000000000000000"
};

int polar_data_tile_val = level_str_p[level_data_idx] - '0';
if ( ( polar_data_tile_val < 0 ) || 
     ( polar_data_tile_val > 
       polar_data_max_tile_val ) )
{
    NSLog(@"polar data tile value %d out of range!\n",
          polar_data_tile_val );
    self = nil
}

Maybe the lesson here is "use Objective-C objects only when objects are a win."

I'm going to continue with this project, developing the code to handle the interactions of objects on the playing field and eventually the user interface. However, now that my week away from home is done, I might not be able to make progress very quickly. Stay tuned -- I'll post what I can, when I can. As always, if you have any comments or questions, I'm happy to have feedback.

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!

03 June 2013

Objective-C, Day 2

(Warning: non-FP content)

So today I'm working on chapter 3 in iOS Programming. This is about memory management. I have vague memories of manual reference-counting using retain and release in earlier experiments with Objective-C, but this book teaches ARM (Automatic Reference Counting). You enable ARM when you configure an XCode project of iOS.

The idea behind object references (well, pointers in iOS) and trees of objects and their owners is not new to me. And here they they still do not clarify whether a statement like items = nil actually invokes runtime behavior to destroy objects. I believe it does not. When I override a dealloc method to log, I see that the dealloc methods are called when the curly brace at the end of @autoreleasepool is reached. This makes sense, as in C it is the point where variables go out of scope. So reference-counting bookkeeping must be invoked then. It does not seem to be possible to step in with the debugger at this point to view what is happening, though -- Apple's Objective-C runtime is proprietary software.

We next get into weak references. There's a __weak specifier that can appear as part of an object pointer declaration, and iOS Programming says that "an interesting property of weak references is that they know when the object they reference is destroyed... the [parent object] automatically sets its [child object] instance variable to nil." I'm going to have to read more about that. I really wish this book were more precise in its language. There's another specifier, __unsafe_unretained, which is not set -- and so a pointer to a destroyed object could still be dereferenced. I suppose I should just be happy that this works and use it, but I'm the type to want to know what is going on at the register level. Apparently ARC (the "automatic" in reference-counting) is all due to some amazing work done in clang, where the static analysis actually figures out all the possible points of object creation and deletion and can wrap up your reference-counting for you. That seems like a great development -- because I've never quite liked how garbage collectors (at least, those without hardware support), no matter how efficient they are, had to actually be implemented, looking at dumb machine pointers. It seems to me that there could be room in purely-GC'ed languages for this kind of static analysis. But I haven't thought very hard about that yet.

Properties seem to be a shorthand, asking the compiler to provide getters and setters. There's a great example as to how much code this can eliminate. There's a strange wart in that we are asked to specify (nonatomic) in every property, because that is not the default setting. Presumably this is to support multiple threads accessing the object. Again, a dark corner to look into.

For a little refresher, I took a break by reading the first few chapters of Brad Cox's book. He outlines his vision of a software IC, along the lines of a hardware C, that would support reusability primarily through late binding, limited interfaces, and wrapping up functionality with data so that clients don't have to write the operations on an object's data type. It's an interesting vision.

Cox writes of early Objective-C:

Objective-C adds precisely one new data type, the object, to those C provides already, and precisely one new operation, the message expression.

Cox's book indicates that early versions of Objective-C used an intermediate preprocessor step, between the C macro-preprocessing step and the regular C compiler. If you are my age you might recall that, early on, C++ was treated similarly, with a program called CFront. This is no longer the case with either Objective-C or C++, although this approach lives on in tools like Qt, with its Meta Object Compiler, or moc).

Cox describes the pragmatic design of Objective-C:

One of Objective-C's key features is that it is always possible to bypass the object-oriented machinery to access an object's private information directly. This is one of a hybrid language's greatest theoretical weaknesses and one of its greatest pragmatic strengths. Low-level code is often best developed in a conventional manner, exploiting static binding to obtain high machine efficiency and strong type checking. Conversely, user-level code is often best written as objects. It is important that these levels interface smoothly and efficiently.

There is an interesting passage about object identifiers. In a slight muddling of his earlier statement he writes that "objects are identified by a new Objective-C data type called an id, or object identifier." In practice, though, this isn't really a new data type per se. The address (a pointer to) an object is this identifier. The veneer over C is so thin that, he writes, it would have been perfectly feasible to implement the dynamic message sends using straightforward C language calls like

reply = _msg_(aReceiver, "aMessage", argument1, ...)
(I assume he'd use C's variable-length argument list mechanism here), but for efficiency he wanted to avoid string comparison in the dispatch mechanism. Cox was quite aware of the reaction that messages like [anObject do:arg1 with:arg2] would inspire in C programmers, writing "the convention seems strange to those accustomed to conventional function call syntax, and frankly, I find it a mixed blessing."

In this formulation, Objective-C classes were still objects, and class methods were called "factory methods" (but I'm peeking ahead a number of pages, so there might be some differences from their current incarnation), as distinct from instance methods. Cox writes "...the programmer conceives the initial object... the loader gives it life by installing it in memory... this is done once for each class in the system. These primal objects are called factory objects... every factory's name is published in a global variable."

Cox is a very thoughtful writer and he presents a minimalist view of what an object-oriented language requires in order to provide the most basic advantage of OOP. He writes:

One last time: the only substantive difference between conventional programming and object-oriented programming is the selection mechanism. This is not to minimize its importance, but to demystify what is basically a very simple notion. Its significance is that it moves a single responsibility across the boundary between the consumer of a service and the supplier of that service. In conventional programming, the consumer is responsible for choosing functions that operate properly on the data managed by that service. The selection mechanism moves that single responsibility off the consumer and onto the supplier. In this small change lies all the power of the technique.

That really caused me to, as they say, nearly drop my monocle. Could this really be where most of the advantage, such as it is, imperfectly realized, understood, and applied, of OOP comes from?

The mainstream business world got C++ instead, a mash-up of C and Simula, and then Java instead, the Cobol of object-oriented programming languages. I was not pleased. As I consider my career I also consider which implementation languages I should focus my efforts on, for the next decade. After exposure to Haskell it's hard to believe that, ultimately, functions -- without explicit state -- won't prove to be a cleaner reusable element than classes, whatever kind of binding they use. And don't get me wrong -- I like classes. I'm pretty certain that I'll still be writing C code, at least occasionally, in ten years. I'd prefer to be writing less C++. But what I'd really like is to move on -- to pick a "last programming language." Objective-C isn't that, for me -- it's too imperative, and can't truly be made safe, just safer. I'm enjoying it in this context, but would Objective-C even be an viable option outside of the Apple ecosystem? It doesn't seem to have gained much adoption. The state of GnuStep doesn't seem terribly robust. And so my career-long quest for a Better Way continues, while at the same time trying to gain facility with all the Good Ways along the way.

02 June 2013

Objective-C, Day 1

(Warning: more non-FP content).

So I'm in an undisclosed location away from home specifically to spend a few quiet days focusing on programming. I had a goal to teach myself enough iOS programming to get a small demo app submitted to the app store by the end of the week. I'm not sure I'll manage that, but I'm learning. I have 3 relevant books with me: iOS Programming: The Big Nerd Ranch Guide (Third Edition, Second Printing, August 2012), by Joe Conway and Aaron Hilegass; Objective-C Programming: The Big Nerd Ranch Guide (Second Printing, January 2012), by Aaron Hillegass; and a much older book, Object Oriented Programming: An Evolutionary Approach (Reprinted with corrections August 1986), by Brad J. Cox. Brad Cox is the guy who created Objective-C, and although the language he describes in his book is not the same as modern Objective-C, I thought it might give me some insight into the language design. I own, but did not bring with me, a number of books on Smalltalk including the "blue book" (Smalltalk-80: The Language and its Implementation by Adele Goldberg and David Robinson). I wish I had brought it! But on the other hand, maybe it's best that I not go too far down a rabbit hole into the history of programming languages and the way language constructs are adopted over time by other languages -- even though it's one of my favorite subjects.

So far, I've worked through a few chapters of each. I'm not really here to write reviews of the books; let's just say they have their various strengths and weaknesses. But here are some thoughts on the process so far.

1. Learning Objective-C is a little frustrating (that's good, actually -- most languages are a LOT frustrating to learn).

1a. One of the fundamental strengths of C, as a kind of portable assembly language, is that almost everything about the behavior of your program is right on the surface. I don't mean that there aren't sometimes surprising behaviors. But as far as the usage of memory and the order of execution, C is pretty easy to reason about, with a few minor exceptions (for example, the switch statement implies that the compiler may create conditional tests whose order and number don't match exactly what is indicated in your source code).

1b. Objective-C hides some functionality in a way that C programmers may find disturbing. Those coming to it from another language might not even notice that this is an issue, since they might expect that the runtime is doing a lot of implicit things. Or they might not think about it at all.

1c. The syntax of Objective-C is a weird mash-up of Smalltalk and C. It has strange aesthetics.

1d. Nevertheless, it is pretty clear that Objective-C is an enormously pragmatic and highly successful language. Therefore, from the perspective of a language designer or someone who studies the history and design of programming languages, it would be an enormous mistake to dismiss it because of its hybrid design. By all accounts it is an extremely productive language once a certain level of expertise is achieved.

2. These books are not necessarily written for someone who knows other languages (such as C and C++) well, and that is also frustrating.

2a. The iOS Programming book uses language imprecisely here and there and it is driving me to distraction.

2b. This is not necessarily their fault; terminology is often only precise within a specific domain and has imprecise meaning or different meaning outside of that domain.

3. Now, some details.

3a. The sentence "The class acts as a factory that creates instances of that class." This needs a little unpacking. The language of "factories" seems to come from design patterns and is also big in .NET and enterprise-y Java as the concept of a factory method. But this sentence actually buries a few surprises for C++ programmers. The first is that classes are objects -- they actually have a distinct run-time representation. And instances have a different run-time representation than classes. This seems reminiscent of the concept of prototypes in languages like NewtonScript, Self, and Omega, where objects can inherit from other objects, but it is different as well, since in NewtonScript a prototype is just another instance of a fundamental container data structure, a frame. I'm not sure yet whether in Objective-C you can create a class at runtime, but I wouldn't be surprised.

3b. From Smalltalk: a method call is triggered by sending an object a message. This implies more run-time overhead than a function call, even a virtual function call from C++, although no doubt this has been highly optimized in the runtime. A message-sending expression can return a value. So, for example, it seems to be idiomatic Objective-C to create an object with a statement like

Object *obj = [[Object alloc] init]

This wraps up quite a bit of behavior: Object is used as a type for a simple pointer variable obj, and also as a reference to the runtime Object class object. Sending Object the alloc message returns a new instance of an object, which can then be used as the receiver for the nested message send, sending it the init message, which returns its receiver.

3c. This "message" is a distinct thing, and seems to either be the same as or closely related to a "selector." Syntactically a selector is something like "methodNamePart1:argument1Name:argument2Name" and a selector like this is compatible with a message-sending statement like "[receiver methodNamePart1: argument1Name: argument1 argument2Name: argument2]." This implies (but I have not seen clearly explained, yet) that the compiler has to break all that down and keep track of it. iOS Programming mentions "arguments interposed into the selector." This seems to be similar to using required keyword arguments as in Dylan which, I think, borrowed the idea from CLOS. I think the packed string with colon-separated selectors might be an innovation in Objective-C; I'm not certain.

3d. iOS Programming mentions that an expression like obj_p = nil "destroys the object and sets its instance variable to nil." If this is accurate, and they don't just mean that the object is "cut loose," this implies hidden run-time behavior that I would like to see explained much more fully.

3e. Sending a message to nil is apparently a no-op at run-time. This scares me a bit; won't that tend to hide a situation where we might actually want a crash?

3f. I like the extensions to printf format strings: %@ representing "any object," producing a string generated by calling the object's description method.

3g. I'm not sure I understand the need for NSNull, if the compiler can already wrap special run-time behavior around nil. But I don't claim to understand everything yet.

3e. Instance variables are not included in class objects. That's... interesting. It sounds a bit like how static methods and fields in a C++ class are available without creating an instance.

3f. Getters and setters are not created for you, and by convention (which can be important, apparently, for compatibility with code that uses introspection) are called set+instance var name and the instance var name. I have a great distaste for different things that have the same name.

3g. Some interesting bits and pieces to digest further, later: instance methods; initializers; id, isa, self, super, and the "designated initializer." iOS Programming talks about the importance of the designated initializer for some time, which is a shame because it also doesn't seem to explain in any way how you designate an initializer.

3h. Class names seem to be all in one namespace -- in other words, I don't see evidence of a real module system. I know from watching this process in other languages that it can be very difficult to retrofit a language with a module system later.

That's about all I've got for now. I'm sure it will make more sense as I press on, for better or worse!