Flegma blog header photo
Flegma's c-blog
Fronts 3Posts 1079Blogs 98Following 0Followers 20



Fledgling retrodev: A ship built


Ever thought of building a ship in a bottle?

I happened to take a look at the Wikipedia page for "impossible bottles", which include ships in a bottle.

There seems to be two main ways to accomplish that: either actually build the ship from pieces inside the bottle, or build the ship outside the bottle, fold it up so that it'll fit in through the bottleneck and "unfold" it once it's inside.

I don't think that's a bad analogue for retro game development. Writing the games on the target systems themselves would compare to building the ship within the bottle, with long tools to reach them from the bottle's neck.

And the latter is what my latest retro game project was all about -- writing a game on modern PCs, precompressing almost everything and then letting the game decompress as it is running.

The game I created this time is so heavily graphics-based that I wanted to do as much compressing as I could. Even playing through it takes under 3 minutes! (And so this blog is short as well, with no follow-ups planned.)

Game title screen.

 Actual gameplay: where the game starts.

Game genre and description

The Macventure games are rather famous -- they’re early examples of point-and-click adventure games but their NES ports are likewise well-known. If you played NES back in the day, you may be familiar with titles like The Uninvited, Deja Vu and Shadowgate. (You may be aware that Shadowgate got a Kickstarted remaster in 2014, but I was surprised that ports of the Apple ][gs and Macintosh versions are also on Steam.)

Shadowgate (Apple ][gs version) as on Steam.

I imagine the NES versions are the most well-known, so I’ll talk about them only. The display was split in four windows -- a first-person view, the inventory, an "exit map" and a list of commands. When something was narrated to the player, the last two would be replaced with one window for displaying text. The pointer could be used to point out items in the view window, pick an exit to take on the map, and select items in the inventory and verb list. (Here's a Shadowgate (NES) longplay on YouTube)

These were the second main inspiration for the engine; the first was the concept of an interactive novel that worked like a point’n’click -- choose a target word, then choose the applicable verb to accompany it. This would eliminate the need to guess what verbs need be typed in, which is a frequent complaint about text adventures.

And be gentle about it.

The Macventure games didn’t shy away from having the player constantly killed. Take a book in the hallway? The trapdoor underneath the player gives way and they plummet to their death. Break a wrong mirror out of the three? Get sucked into space. Or something.

The way I talk about my game compares it way too much to Macventure games. It may look like it at first, but this is far too limited to compared to those games.

The inability to pick items in the graphical view is one major difference in how the games play. In hindsight, that was a mistake: while pixelhunting is deservedly undesirable, that can still be avoided.

Legend Entertainment’s games like Eric the Unready (1993) and Frederik Pohl's Gateway (1992) had complete lists of verbs and objects, in addition to the ability to write the command in a prompt. Beside that, games like Maniac Mansion (Lucasfilm Games, 1987) have the player first choose the verb and next the object. That is more congruent with the spoken language. I still think I prefer having the game filter the invalid actions out, though, like point'n'click adventures do. This does, however, make the game much easier to brute-force through.

Screenshot of my game. The black "map pin" beside "south" means an exit direction, the blue target means the object is at this location, and the bag means the coin is in player's inventory.

I wrote the game again with a dev compo in mind. The MSXdev’20 rules permit memory mappers this time. This means rather than using two 16KB memory areas like my Bumper Ship Racing did (both stayed on the second and third page, leaving the first page to contain the BIOS routines (useful built-in routines that, say, copy data to video RAM or change screen mode, read the joystick and so on) and the fourth page to be used for RAM to store changing information, including call stack. (Summary: ROM=read-only memory, RAM=random access memory, and the latter is where I can both read and write data as the program is running. The game's ROM maps to memory addresses between 16KB and 48KB.)

So, back to memory mappers. The mappers would permit to switch the page contents to different blocks on the ROM cartridge. Say, if one 16KB block contained all the data I had to copy to VRAM (video RAM), I could change the second page to display that block, and make a call to a routine that would handle that copying. Past that, the page could be changed to show another block with, say, game level data.

An obvious way to have the engine support the memory mapper would be to have the main game code kept always on the second page and the location data inside the swappable blocks. And if the game code didn’t fit inside 16KB, then the missing code would be repeated on every swappable sheet.

However, I chose not to make a very long adventure and squeeze everything within 32KB. That was more than enough for me. So why did I blather on and on about the memory mappers? Well, because I could and because that's actually something a future version of this rather reusable game engine could support.

Nitty gritty details

The compilation of the ROM happens in three steps. One, I edit the actual game files with GIMP (game graphics) and a text editor (scripts, descriptions).

One location in the game, with the grid showing character boundaries (to help avoiding colour clashes) and the palette available in the second window.

Two, I execute a Python script that reads the above input and compiles it into binaries and ASM compiler instructions, the latter primarily labeling memory locations as this and that instead of actual executable code.

Three, I have the main game code in other files and include the script-produced files before running an ASM compiler.

The main “interesting” part of the Python script output is that the references to objects (locations, items) are actually addresses the ASM compiler determines. So as the ASM compiler determines the memory addresses for all the variables, it is also determining the hard-coded pointers. For instance, the first two bytes of a location record will always contain the memory address of the compressed description of the location. The next two, which tile palette will be used and so on.

That’s the main benefit of this. Another is that optimising the graphics without manual work beyond actually creating the images was included as a part of the pipeline, and optimising the bytes the graphics take is very important indeed. The view window is 14x14 characters. To describe that information without any compression would mean 14*14*8*2 = 3136 bytes per one location (8 bytes to describe the pattern, 8 bytes to describe the two colours on each 8-pixel row). I’d run out of space after only half a dozen locations if I did it that way. (The final game has 15.)

The solution I used was defining a unified tileset for all locations. The location records would still take 14*14 = 196 bytes each, as I’d use one byte to index the tile. To avoid limiting the graphics to only 256 different tiles in all locations, there are tile palettes that map the location-specific tile codes to the correct index in the whole unified tileset.

An actual limitation is that I’ve constrained the number of ways tiles can be coloured to only 256 out of 256**8. This just means the artist (me) will have to be more careful with colours. Of course, automatic optimisation can be done here as well. Since every pixel row in a character can have two colours out of a fixed palette of 16, if the pixel row is monochromatic in one tile, the other colour can be something another tile would need. With the final/near-final graphics, a simple palette combination phase reduced the number of tile colourings from 924 to 236.

The horizontal striping in the sky meant only one of the two colours were used, so the same colour scheme would be easier to reuse with other tiles. I deliberately used a lot of horizontal striping rather than dithering.

Oh, and the Python script in the second step automatically uses a shared codebook to compress every string that is displayed in the game. This isn’t new, though, and the graphics naturally take the most of the memory (15KB vs 4KB).

The scripting language, then. Basically, every action the player takes is defined by a script (action referring to the outcome of picking an object + verb, and possibly target). Taking an object is its own script and it could just as well have been bound to any other (predefined) verb, or even object.

There were several options on how to handle this, in order of distance to metal:

  • Everything is in ASM mnemonics and will be parsed into machine language.
  • Use a simple shorthand script that is then expanded into ASM that will be parsed into machine language.
  • Use a simple shorthand script where each high-level command (such as "set item location to...") is shortened into one byte and the relevant memory addresses, and can be easily parsed on the fly. This is the one I chose to use.

As this is not an action game, execution speed is not the primary constraint here. The language is also very reduced and limited: no nesting, so execution branching means just jumping to another script (without returning back). The commands also lack all arithmetic operations beyond comparing for equality and have a total of 16 bytes reserved for storing data (one of which is for comparison results).

The script executed when entering the starting room. 

It is also complex enough to handle everything the game needed (except changing the location visuals, which I didn't implement yet).

That’s about everything there is to say on the tech side outside of the "Stupidities I noticed" section at the end.

The actual content

The actual generation of content (or creation, as some would call it) is more problematic. Who? What? Why? And fit everything in 32KB, thankyouverymuch. 

For safety, I chose a very simple story: a penguin needs money to buy a gift for Paula, a friend (also a penguin). The Antarctic environment is very white/cyan/blue, which means colour data didn’t take much space. The text files I created were 420 lines (describing displayed strings, items and locations) and 350 lines (describing the scripts used), including empty lines and comments for legibility.

The narrative is very simple and skips many parts that should have been included. I'm not good at making logic puzzles, so I don't think there are any that qualify here. So... it's a bad abridged version of what might have been a half-decent game. Many locations should have been reused for something; instead, they tend to have only one purpose and then the player won't need to return there ever again.

In summary, I think the game engine is reasonably reusable. I can see some commands I really should add to the scripting language. Before a good game can be made with this, it'll need to support memory mappers automatically, but that's not something I have in plans for this competition. The game I implemented is very much throwaway proof-of-concept, but that has to do for now.

As usual, the game file is again up for download and you can try it out in an MSX emulator. It may still see changes (such as the name). But it will never have save/load functionality of any sort or music. Not sure I'll add any sound effects either.

Stupidities I noticed/did

  • When selecting the list of actions for an item, the game lists all verbs associated with the object. Now, if the player picked up a coin with the verb "take" and the item was moved to player's inventory, it would still have that verb associated with it, as nonsensical as taking an object you already got is. Solution? When picking up an object, I place another object almost identical to this one in the player's inventory and place the original one in limbo. The only difference between the two objects is that the one in inventory lacks the script for "take".
  • I thought I was clever and made one person (Paul) into a location that was supposed to be the dialogue screen, and created local objects as topics to talk about (with the action "talk about" associated with it). It still looks like a regular item at the location, which isn't very good.
  • I have forgotten how to properly write script. And that people don't understand it, especially when the characters are squeezed to 8x8 pixels.
  • I'm still on the fence of what to call the game. With the penguins involved, calling it "something-Adventure" would feel too much like trying to coattail on Antarctic Adventure (Konami, 1983) and Penguin Adventure (Konami, 1986), and the game definitely doesn't deserve a pretty name like "Quest for Ice Flower" either. Then there's the second consideration. I thought of "qwerty" or "Jäässä", but the first is too obviously fake and the latter could be translated as "Frozen", and I definitely want to stay away from using names related to that company's IPs. Hence, "Jäästä", or "Of/From Ice". 
  • The scripting language's limitations are very apparent when doing more complex scripts. Since the only branching structure involves jumping to another script and there are no chained conditions, checking for the presence of three items means one script checking the presence of one item and if true, jumping to the second script. This does the same for another object and a third script. All in all, four script pieces are needed for this simple condition.
  • The Python preprocessing script doesn't check if all the referenced locations etc. are actually defined. This means the ASM compiler is the one to do this. At first, this wasn't a problem, but after the graphics optimization step started taking longer and longer... yeah, "fail faster" would've been a good idea.

As I've said before, I won't be replying to any comments by Disqus. I don't know if the site PM feature works or not, but I may try replying with that.

Login to vote this up!


LaTerry   44
Wes Tacos   1



Please login (or) make a quick account (free)
to view and post comments.

 Login with Twitter

 Login with Dtoid

Three day old threads are only visible to verified humans - this helps our small community management team stay on top of spam

Sorry for the extra step!


About Flegmaone of us since 11:34 PM on 01.17.2015

Very much unprofessional writer, don't take anything I write without a truckload of salt.

On a hopefully long-term break from saying anything.

Given the amount of work Niero had to do to purge my Disqus logs the last time, I'm not going to agree to Disqus TOS and use the service again ==> I won't be replying to your comments as much as I'd like to. Except maybe via site PM functionality. If it works.