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.
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.
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:
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 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.
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.