2017-11-28

Amy Zing and the Amazing Mazes

This is a very little game created solely for one very little toddler who kept harrassing me to draw mazes for him to solve. The effort involved in creating the mazes vastly outweighed the effort it took him to solve them, so I figured I needed to automate it. The game is loosely based on the snail maze built into some revisions of the Sega Master System BIOS but it isn’t a remake. Like the snail maze, the objective is to guide the red square through the maze from the starting point to the finishing point.

Here’s a screenshot:

AmyZing

I used the Kruskal algorithm to generate the mazes. On the linked page the author complains that the algorithm “tends to create a lot of short dead-ends”, but that property makes it perfect for toddlers who don’t have the patience for extensive backtracking when they take the wrong path.

The game itself took almost no time at all to write; excluding libraries and presentation fluff the game weighs in at around 600 lines of C. The most complex tasks were fixing a weird rendering bug (that turned out to be a missing conversion between co-ordinate systems in the layer library) and writing a bunch of transitions between scenes. The first, and most troublesome, transition looks just like this one I made 7 years ago in JavaScript (hit refresh if the image doesn’t show up; the script doesn’t preload the image), but with the addition of fade out as the image disappears. Unfortunately that proved too complex for both the DS and the 3DS to render at 60fps so I replaced it with a simpler cross-fade transition.

There’s no title screen bitmap and the player’s character is just a red box. I’d intended to come up with a pixel art title screen showing Amy Zing herself, and an Amy Zing sprite that would wander around the mazes, but that’s beyond the limit of my drawing ability (if anyone is interested in contributing a title screen and a sprite in 3 sizes, let me know).

Tinkering with menu systems, transitions, difficulty levels, presentation and general polish took long enough that the toddler in question has long since lost interest in solving mazes.

Download it here:

2017-11-02

SZLib Super

Something missing from SZLib is a way for subclasses to call the implementation of a method they’ve overridden. For example, in Objective-C:

@interface ClassA : NSObject

- (void)printMe;

@end

@interface ClassB : ClassA
@end

@interface ClassC : ClassB
@end

@implementation ClassA

- (void)printMe {
    printf("ClassA");
}

@end

@implementation ClassB
@end

@implementation ClassC

- (void)printMe {
    [super printMe];
    printf("ClassC");
}

@end

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        ClassC *c = [[ClassC alloc] init];
        [c printMe];
    }

    return 0;
}

The output of this program is ClassAClassC. The call to super in ClassC is smart enough to realize that ClassB doesn’t implement the method and will fall back to the implementation in ClassA. super can be used recursively to move up through the class hierarchy despite the type of the object being acted upon not changing.

I’ve been trying to come up with a way of supporting something like this in SZLib, but it turns out that it’s tricky to implement tidily without compiler support.

First, I moved the callbacks struct out of the objects that I create and into their metaclasses. Here’s the callback struct from SZObject:

typedef struct {
    int (*equals)(SZTypeRef, SZTypeRef);
    int (*compare)(SZTypeRef, SZTypeRef);
    unsigned long (*hash)(SZTypeRef);
    SZTypeRef (*copy)(SZTypeRef);
    void (*dealloc)(SZTypeRef);
    char *(*description)(SZTypeRef);
} SZObjectCallbacks;

Previously I’d pass this into the object’s initializer:

SZTypeRef SZObjectInit(SZTypeRef ref, const SZObjectCallbacks *callbacks);

Aside from being awkward, this meant that each object could only reference the single callback struct passed to it in its initializer. There was no way to work back through the class hierarchy and examine the callbacks of any superclasses. The workaround I used was for classes to expose their callback functions and allow subclasses to call them (so SZSomeObject had a function __SZSomeObjectEquals() exposed in its header, which subclasses of SZSomeObject could call in their own equals implementation). This too was awkward, as it exposed the internals of the superclasses and required that subclasses knew more than they really needed to about the implementation of their superclass.

Callbacks are now passed to the allocator, not the initializer, as part of the metaclass:

typedef struct __SZMetaClass {
    const struct __SZMetaClass *parentClass;
    const char *className;
    const SZObjectCallbacks *callbacks;
} SZMetaClass;

SZTypeRef SZObjectAllocate(size_t size, const SZMetaClass *metaclass);

Each metaclass has a pointer to its superclass, instantly giving us a hierarchy we can walk.

Now that we have a hierarchy we still need to be able to:

  • Call the superclass’ implementation of a given function from an overriding function in a subclass;
  • Walk back up the hierarchy if we find that a superclass doesn’t implement a given function until we find a class that does;
  • Allow recursive calls of the function so that each subclass in the hierachy can call its superclass’ implementation.

This is last point in particular is harder than it sounds. The obvious approach fails miserably:

void SZSomeObjectEquals(SZTypeRef ref1, SZTypeRef ref2) {
    SZObjectRef obj1 = (SZObjectRef)ref1;
    SZObjectRef obj2 = (SZObjectRef)ref2;

    // Attempt to call "super" implementation of "equals"
    return obj1->metaclass->parentClass->equals(obj1, obj2);
}

Accessing the parentClass in this way gives us the same metaclass each time, so if the superclass’ implementation of equals calls its own superclass it’ll get stuck in a loop. We’d need to pass the metaclass as an argument to the function so that we can update it on each successive call:

void SZSomeObjectEquals(SZTypeRef ref1, SZTypeRef ref2) {
    SZObjectRef obj1 = (SZObjectRef)ref1;
    SZObjectRef obj2 = (SZObjectRef)ref2;

    // Call the immediate superclass' implementation of "equals"
    return obj1->metaclass->parentClass->equals(obj1, obj2, obj->metaclass->parentClass);
}

That implies a public API that doesn’t include a metaclass parameter vs an internal API that expects a metaclass parameter.

We also need to deal with the possibility that some classes have opted not to override the function, so we end up with something like this in SZObject:

int SZObjectEqualsUsingMetaClass(SZTypeRef ref1, SZTypeRef ref2, const SZMetaClass *metaclass) {
    const SZObjectRef obj1 = (SZObjectRef)ref1;
    const SZObjectRef obj2 = (SZObjectRef)ref2;

    // Hunt for the next valid implementation of "equals" in the superclass hierarchy
    while (metaclass && metaclass->parentClass && !metaclass->callbacks->equals) {
        metaclass = metaclass->parentClass;
    }

    // Call the implementation of "equals" that we found
    return metaclass->callbacks->equals(obj1, obj2, metaclass);
}

We’d use that like this:

void SZSomeObjectEquals(SZSomeObject obj1, SZSomeObject obj2) {

    // Call the "super" implementation of "equals"
    return SZObjectEqualsUsingMetaClass(obj1, obj2, obj.object->metaclass->parentClass);
}

Our callback struct has to accept the metaclass as a parameter in each callback function:

typedef struct {
    int (*equals)(SZTypeRef, SZTypeRef, const SZMetaClass *);
    int (*compare)(SZTypeRef, SZTypeRef, const SZMetaClass *);
    unsigned long (*hash)(SZTypeRef, const SZMetaClass *);
    SZTypeRef (*copy)(SZTypeRef, const SZMetaClass *);
    void (*dealloc)(SZTypeRef, const SZMetaClass *);
    char *(*description)(SZTypeRef, const SZMetaClass *);
} SZObjectCallbacks;

This is still a little awkward:

  • I’d rather hide the metaclass stuff somehow rather than force each overridden function to deal with it;
  • There needs to be a function to lookup the correct callback for each callback;
  • Callbacks need to be looked up each time the function is called.

The overhead of the lookup isn’t significant so I’m not worried about that, at least with the shallow class hierarchies I’m building. The proliferation of additional functions is unfortunate but I don’t see a way around it in C (I came up with a macro that would generate lookup functions automatically, but it transpires that the C preprocessor doesn’t allow the token pasting operator to inject struct fields (obj->metaclass->##name## is forbidden) which killed that idea).

In spite of the limitations this system works well enough, and achieves the initial goals:

  • Superclasses can stop exposing their implementations of the various callbacks;
  • Subclasses can call the standard …UsingMetaClass() functions instead of trying to figure out what their superclasses are doing;
  • Subclasses can specify NULL as their callback function and the system will gracefully fall back to the superclass’ implementation.

2017-10-18

Replacing CrashPlan

It’s old news now, but CrashPlan are shutting down their consumer backup business. This is unfortunate because, despite its reliance on Java, CrashPlan is my backup system of choice for a number of reasons:

  • Backup to any computer running CrashPlan in addition to CrashPlan’s servers;
  • Multiple computers on a single plan;
  • Unlimited storage;
  • User-defined encryption key;
  • Backup external disks;
  • iOS app;
  • Reasonably priced;
  • Simple restore process.

I’ve been looking around for a replacement system. CrashPlan themselves recommend two options: their “CrashPlan for small business” offering, and former competitors Carbonite. Neither one matches my requirements. The CrashPlan alternative has two huge drawbacks:

  • Instead of being ~$14/month to backup all of my computers, it’d cost $10/month per computer (which works out as $40/month for me).
  • It can’t backup to other computers running CrashPlan, so I’d need a second system for local backups.

If the feature set was the same I’d consider keeping CrashPlan, but they’re charging more for less.

Carbonite has its own set of limitations:

  • The $5/month service doesn’t backup external drives, so I’d need the $8/month service (per computer);
  • The $8 service can only backup one external hard drive;
  • Files over 4GB aren’t backed up automatically;
  • It can only backup to Carbonite’s servers;
  • It only backs up files in your user directory.

It’s cheaper than CrashPlan’s own option, but a backup system that requires a greater-than-zero amount of effort to maintain after the initial setup is a bad idea.

I’m currently evaluating BackBlaze as a possible replacement. It retains many of CrashPlan’s essential capabilities:

  • Unlimited storage;
  • User-defined encryption key;
  • Backs up external disks;
  • iOS app;
  • Simple restore process (much improved since the last time I evaluated it).

It’s priced at $5/month per computer, which makes it more expensive than the old CrashPlan service but cheaper than the other options, and as a bonus it’s a native app.

That takes care of online backup, but what about a local backup? Local backups were unique to the consumer version of CrashPlan, so regardless of what I switch to I’m going to have to run two systems. I have a MacMini that acts as a backup server, so I need something that can backup to that. Time Machine would be ideal, except its GUI melts my GPU and it won’t reliably backup for me across a network (which is why I switched away from it and started using CrashPlan for local backups in the first place).

Arq looks like it might work out. It seems to do everything TimeMachine does, except the UI is a standard Mac app and it backs up via SFTP.

2017-10-15

SZLib 3DS

Just in time for it to be killed off by Nintendo in favour of the Switch (if you could ever find them in the shops; grumble grumble), Hanky Alien and Chuckie Egg now run on the 3DS. Getting the new platform up and running was pretty easy. Input was straightforward: the d-pad required switching to a couple of new functions for reading the button state but otherwise the constants are the same in libctru as those in libnds. Touch somehow just worked, which was surprising.

The hardest part was getting the graphics up and running. Not only does the 3DS introduce yet another set of RGB encodings, but the output of the LCDs is rotated 90 degrees from the VRAM. What’s that? You have a bunch of 2D graphics functions that are all optimized around VRAM being laid out in rows, from left to right? Bwahaha! Now VRAM is organized into columns, from bottom to top.

Though graphics were the trickiest part, the most laborious exercise was checking that the projects continued to build correctly for all 5 of the currently supported platforms (DS, 3DS, PSP, Dreamcast and SDL). I really need to come up with a better build system for the PSP and Dreamcast, and it’d be neat if devKitARM supported makefile names other than “makefile”.

There are a few things still to do:

  • libctru introduces yet another sound API, so there’s no sound yet;
  • SZLib doesn’t cater for the possibility that a device has multiple screens with different dimensions, so the bottom screen is off-center.

Anyhow, here are early 3DS builds, which I’ve tested out on my old 3DS XL and Citra (which is shaping up to be a great homebrew tool):

2017-05-24

Fake DS Lite

I had some problems with devKitARM recently that broke Chuckie Egg and Hanky Alien. Thinking that the problem might be related to some DSi-specific code that got added to libnds, I decided to try out the games on my DS Lite. Unfortunately, the console no longer recognized DS carts. No amount of fiddling would get it to read either of the two carts I tested.

Nuts.

I’d heard that AliExpress - purveyor of fine knock-off Chinese electronics like the GB Boy Colour - had a selection of fake DS Lites on offer, so I went digging. I eventually settled on a cobalt blue DS Lite for the very reasonable price of $40. Given that an original Nintendo console would have cost more than double that back in the day, and given that it’s difficult to find an original, new DS Lite for less than ten times that, it seemed like a chance worth taking.

Here’s what arrived:

Fake DS Lite

Let’s start with the bad first. Side by side with a real DS, the shell is obviously not original. The top case is slightly larger than it should be and bulges outwards slightly along the edge where the clamshell opens. The square black rubber pads that are dotted around the upper screen are fitted imprecisely. The plastic feels cheap, especially when compared to the beautifully glossy Nintendo device. The A, B, X and Y buttons are horribly mushy, and the Start and Select buttons are too small for the holes that they sit in. The cover over the top screen is fitted incorrectly, allowing light from the screen to bleed through its top edge. The screens themselves are set deeper into the casing than on the original. The DS cartridge slot makes a series of nasty griding sounds when a cartridge is inserted, and the cover over the GBA slot is looser than on the original handheld.

In short, the casing is cheap and poorly assembled. But it’s close enough.

Now the good. My original DS Lite came from the factory with a dead pixel; a common problem. Cracked hinges were another, and the hinges on mine cracked not long after I bought it. The clone device doesn’t exhibit either problem (yet). Both screens are perfect and indistinguishable from the original. We’ll see how long the hinge lasts.

Functionally it appears identical to the Nintendo console. When you consider that the K1-GBA still isn’t a perfect clone of the GBA, and that this thing has a GBA embedded within it, that’s stunning. The only explanations that make any sense are that this clone is from a DS factory secretly making extra devices on the side, or that someone stole and replicated the PCBs like the Everdrive clones, or that it’s assembled from spare OEM parts and a third-party case.

It even comes with a box (missing the serial number, of course), manual, soft case and screen protectors.

I haven’t really tried the stylus yet. It’s possible that the touchscreen is terrible. The screen’s texture feels different to the original, though that could just be because it’s new. I haven’t tried the charger yet as the battery is fully charged. The combination of a knock-off charger and a knock-off battery is a little concerning. Other than that, it looks to be a good alternative to either buying a second-hand DS Lite (and possibly ending up with a device that was mistreated) or paying a crazy amount for a new official device.

Oh, and now that I have a second DS Lite, my original console has started recognizing carts again.

2017-05-13

10 Years

This blog is 10 years old. I’d planned to write a retrospective and publish it on the anniversary, but I completely forgot. I did post a new version of Chuckie Egg the day before which was far more entertaining.

In the first post, entitled “Go!”, the blog was running on WordPress and I complained that I didn’t have time to write my own blogging software. After 10 years and 518 posts, the blog runs on my own blogging software that is written in Go. I like to pretend that was clever foreshadowing.

2017-04-10

Chuckie Egg 20170410

With egg hunting season almost upon us, it seems appropriate to release a little update for my favorite egg hunting game. Grab it now:

I’d hoped to get a level editor into the next release, but it’s not finished. This version fixes a few bugs and adds a couple of minor new features.

Changelog:

  • Scores are persisted on DS and PSP.
  • PSP analog stick can control Harry.
  • Fixed crash when completing a level if a cheat label is showing.
  • Fixed crash when switching to high score table.
  • Harry dismounts ladders correctly when climbing and jump is pressed.
  • Player does not get points when hens eat grain.
  • Bonus cannot be negative.

I haven’t looked into working with the Dreamcast’s VMU yet.

2016-12-29

Transparency in SZLib

One of the neat features in early Woopsi was screen dimming. The class responsible read each overlapped pixel from the framebuffer, adjusted its brightness, and wrote it back. I eventually had to kill the feature when I changed how the rendering system worked and broke any support for transparency.

Woopsi’s drawing algorithm (essentially a reversed painter’s algorithm) looks like this:

  • Pass in a rect that describes the area to be redrawn;
  • For each layer in the layer hierarchy, from the topmost down:
    • Draw the parts of the layer that intersect with the rect;
    • Subtract the drawn parts from the rect.

Each layer is expected to own all pixels within its rectangular region. To support transparency, there would need to be a way for layers to opt out of subtracting themselves from the redraw rect so that lower layers could draw into the same area. Even if there were a way to achieve that, we’re traversing the hierarchy the wrong way. Transparent views need to render over the top of existing framebuffer data. We need to draw from the bottom up.

The CanvasLayers JavaScript library has a workaround but it’s really inefficient. When setting the library up, it includes the option to support transparency. With that enabled, this is the algorithhm:

  • Pass in a rect that describes the area to be redrawn;
  • For each layer in the layer hierarchy, from the bottommost up:
    • Draw the parts of the layer that intersect the rect.

It’s just the painter’s algorithm. Its awfulness is slightly mitigated by all of the dirty rectangle work that the library does, but we’re still pointlessly drawing a bunch of stuff below opaque layers. We’re also forced to maintain two algorithms to do the same work.

SZLib needed to support transparency in order for overlapping objects in Hanky Alien and Chuckie Egg to work, so I put some thought into the problem and arrived at the obvious (with hindsight) solution: a two-pass algorithm. In pass 1, we iterate over the layers from top-down to figure out which layer will be responsible for drawing what. In pass 2, we iterate over pass 1’s results from bottom-up and perform the drawing.

Pass 1 looks like this:

  • Pass in a rect that describes the area to be redrawn;
  • Create an array of layer/rect tuples;
  • For each layer in the layer hierarchy, from the topmost down:
    • Find the intersection of the layer with the rect;
    • Create a tuple containing the intersection and the layer;
    • If the layer is opaque, remove the intersection from the rect.

Note that we don’t consider an intersection to be “used” if the layer is transparent, which allows lower layers to create their own tuple in subsequent iterations.

In pass 2, we iterate backwards over the tuple array and redraw each tuple. Pass 1 gave us an array ordered by z-index from high to low; by iterating backwards we therefore redraw from the bottom up. Lower opaque layers are drawn before higher transparent layers.

This new algorithm supports transparency with the minimum amount of redundant drawing.

2016-11-19

Chuckie Egg Releases

Just in time for the holidays, here are final versions of Chuckie Egg for DS and PSP, and an almost-final version for the Dreamcast:

Chuckie Egg DS

Post Mortem

PSP

The PSP has a huge screen. Well, relative to the DS; maybe not so huge if you compare it to a modern phone. What do you do with all that space if you’re remaking a game that fits in 256x192 pixels? The obvious thing to do is double up the pixels and make the graphics twice the size. But wait; at 272 pixels tall the PSP’s screen is only 70% larger vertically than the Dragon 32, which means we can’t double up the resolution without losing 30% of the game’s display. I imagine it’s possible to wrap the game’s bitmap onto a quad and get the GPU to stretch it, but I eventually decided just to centre the game at its native resolution.

The PSP’s screen is 480 pixels wide but its default framebuffer is 512 pixels wide. Trying to change the width of the framebuffer to match the screen does not work (it results in the display being smeared horizontally, which always means “the dimensions of your bitmap do not match the layout of memory”). I had to add in the concept of “usable” vs “available” framebuffer space (“physical” vs “logical”, I suppose) and base the layout of the various screens on that.

It’s possible to push the PSP’s “home” button and quit from a game back to the launcher. Making this work entails creating a background thread and wiring up a callback that the OS can use to signal to the game that it needs to quit. It’s very simplistic; if the game doesn’t implement the callback then the system just hangs forever. The OS doesn’t appear to forcibly quit rogue processes. I’m not even sure that it’s really a multi-process system. I imagine Sony handled errors like this in their QA process so they didn’t bother with anything more elaborate to catch badly-implemented games.

Different geographic regions have different PSP button layouts. I don’t quite know what Sony thought the advantage of swapping the O and X buttons was, but it means well-behaved homebrew has to work around it. Games typically either allow the buttons to be remapped or come in different builds for different layouts. Official games were region-locked so that wasn’t an issue; it was probably fixed during localization. I’m too lazy to deal with it.

Getting sound working was troublesome. The basic PSPSDK libraries offer a way - I think - to craft PCM data and play it back, which really doesn’t help if you just want to play a WAV and not put any thought into it. I ended up using libmikmod, which has some peculiarities. Its MikMod_Update() function must be called regularly in order for sounds to work, but that can be stuffed into another background thread and forgotten about. It offers two ways to play back samples: Sample_Play(), which will automatically choose a channel to play back on, and Voice_Play() which lets you choose the channel. I found that Sample_Play() always chose exactly the wrong channel and ended up stopping sounds that I wanted to continue playing, so I switched to the alternative function. Voice_Play() doesn’t have any useful documentation. Inspecting the library’s code reveals that the trick is to set the volume, frequency and panning of the voice each time a new sample begins:

Voice_Play(channel, sound, 0);
Voice_SetVolume(channel, sound->volume << 2);
Voice_SetPanning(channel, PAN_CENTER);
Voice_SetFrequency(channel, sound->speed);

DS

The DS has a second screen. What do you do with two screens when remaking a game designed for one? I had a few ideas:

  • Level editor;
  • Chuckie Egg title screen;
  • Simian Zombie logo;
  • Dragon 32 image.

In the end the SZ logo was simplest, so I went with that. Otherwise, the DS is my default platform, so there aren’t any surprises here.

Dreamcast

It appears that calling snd_sfx_stop() on a channel playing a stereo WAV file will only stop one of the channels, which explains why sounds weren’t behaving themselves in the beta. Switching to mono files fixed it. Another problem was caused by limitations of the audio hardware: the Dreamcast can only play back the first 65534 samples in a WAV file, which is far shorter than several of the sounds in the game. To work around this I halved the sample rates of the longer samples. That fixed most of them, but the title song and Spectrum loading sound were still too long. I converted those to MP3 and used the streaming API, but that created problems of its own. Sounds still cut out or don’t play, and the emulator I’ve been using to help development crashes whenever I call mp3_stop(). There’s still some work to do there.

The DC has a similar problem to the PSP: I’m using the 320x240 NTSC graphics mode, but a good portion of that is off-screen on my TV. The “usable” framebuffer space concept helped out here.

The DC feels noticeably faster than the other two platforms, which is odd, as I’d expect them to run at identical framerates. Perhaps it’s just because it runs on such a massive display.

The Dreamcast’s reversed ARGB ordering means I’ve had to convert bitmaps into both DS and DC sourcecode and use #ifdefs to choose the right version of the data for the current target platform at compile time.

Cross Platform

What was most surprising was how little effort I had to put into Chuckie Egg in order to make it performant on these different platforms. The effort that went into Hanky Alien presumably helped a lot. I was testing HankyAlien with up to ~140 views on screen at once and up to ~45 of those updating simultaneously. In contrast, despite superficially being a more complex game, Chuckie Egg has ~45 views on screen at once and only ~10 updating simultaneously. (It’s interesting to note that, while both the DS and DC struggled with the HankyAlien test scenario and their framerates suffered, the PSP sauntered through it without a hitch. I really must write something specifically for the PSP in future.)

Writing object-oriented C is fun, but sometimes I look at the amount of boilerplate necessary to create a new “class” and decide to do things another way, particularly if I’m creating a base class. The most simple class requires:

  • A static metaclass struct, which forms a linked list back through the chain of superclasses (it enables the program to determine the type of an object at runtime).
  • A static callback struct that includes pointers to equality/comparison/hash/copy/dealloc functions.
  • Declarations of those functions.
  • Implementations of those functions.
  • A struct with the member variables of the class.
  • A typedef that defines a pointer to the struct, allowing the struct to be hidden.
  • A constructor that calls the allocator and initializer methods of the class.
  • An initializer function that sets up the members of class instances and calls back to the superclass’ initializer.
  • A convenience release method that wraps the basic release method.

If the class is intended to be subclassed it must also:

  • Provide an additional private header including:
    • A callback struct definition that nests the superclass’ callback struct and enables “methods” to be “overridden” (really it’s the template pattern with function pointers).
    • The struct itself, so subclasses can directly access properties of the struct and include the struct as the first item in their own structs.
  • Call the functions in the callback struct when appropriate (including those for the superclass).

Every time I create a new class I do so by copying-and-pasting an existing class and modifying it rather than start it from scratch and have to retype all of the boilerplate. Sometimes I’ll think about the work involved in setting one of these things up, pine for C++ or Objective-C, and then use a different structure that’s less “correct” but will still get the job done without smelling too badly. The grain and eggs, for example, should really inherit from a single “collectible item” class, but the correctness of that design doesn’t in any way compensate for the amount of work involved in setting it all up.

Keeping a strict separation between the model (game logic), the views (the stuff you see on screen) and the view controller (delegate of the model; updates the views to match incoming events) results in some beautifully clean code but makes prototyping difficult, especially early on when none of the infrastructure is in place. I did most of the early prototyping in the view controller before moving it into a separate model.

Each level renders its static content (ladders, platforms and bird cage) as a bitmap when it begins. It passes that up through the model to the view controller, which uses the bitmap as the background view for the game. That eliminated the need to create dozens of views to represent tiles that never change during the course of a level.

There are now 134,000 lines of code in the project, but a sizable number of those are included in bitmaps that have been converted to C.

2016-11-14

PSP Surprise

I’ve mentioned before that SZLib presents challenges:

The downside to completely ignoring the capabilities of the DS hardware and doing all of the graphics work with just the CPU and a framebuffer is that it is insanely expensive.

However, its bare-bones approach - it just needs a framebuffer and a way to read a joystick, pretty much - made getting it working on the Dreamcast fairly trivial. Getting it working on other platforms should be simple too, but when choosing a new platform for the library I eliminated everything but the Dreamcast as a potential target.

I was too hasty when I crossed the PSP off that list. The handheld does offer the ability to write directly to the framebuffer. It took about an hour or so to get to this point:

ChuckieEggPSP

By far the hardest part was getting MikMod set up to play sound effects, which took a couple of hours on its own.

There’s a little bit of work to do. The PSP has a physical screen size of 480x272, but for some reason it seems that the frame buffer is 512 pixels wide. The disconnect between the physical and logical sizes means everything is slightly off-center. Also, the “home” button isn’t wired up yet. Overall, though, the game works just as well on the PSP as it doesn on the Dreamcast and NDS.

Excitingly, because they’re based on the same libraries, HankyAlien also now runs on the PSP.