Why building another Adventure Engine?


When I started playing adventure games back in the 80ies, there were basically two types of game implementations:

  • Games written as actual programs
  • Games using some portable virtual machine or database

Programmed games

When you booted an 8 bit computer back around 1982, on most machines you would land on a blinking prompt allowing you to program in BASIC directly, so with just your computer manual and a bit (a lot) of patience, you could totally write a complete adventure game doing something like that:

10 PRINT"WELCOME ADVENTURER"
20 INPUT"What's next",A$
30 IF A$="QUIT" THEN PRINT"Goodbye!":END
40 IF A$="HELP" THEN PRINT"Nobody can help you, muahahaha!":GOTO 20
50 IF A$="NORTH" THEN GOTO 100
60 IF A$="SOUTH" THEN GOTO 200
70 IF A$="WEST" THEN GOTO 300
80 IF A$="EAST" THEN GOTO 400
90 GOTO 20

100 REM Handle NORTH command
110 (...)
199 GOTO 20

200 REM Handle SOUTH command
210 (...)
299 GOTO 20

300 REM Handle WEST command
310 (...)
399 GOTO 20

400 REM Handle EAST command
410 (...)
499 GOTO 20

And indeed quite a few games on the machine I owned (a 1984 Oric Atmos) were written like that, including “Encounter”.

The main problem with this approach is that you can’t reuse the game engine to make another game, because there is no separation between the game data (locations, items, actions, texts, …) and the code that manages that.

The second issue is that not all BASIC implementations are identical, specially in the audio and graphical domain, so the more “advanced” your game is, the harder it would be to port to another game system.

Virtual machines and databases

A solution to these problems was to separate the engine from the game, solution which has been implemented differently by different people, two examples would be:

…and I guess we could argue that’s similar to Lucas Arts’s SCUMM used for their graphical adventure games

Z-machine

Infocom games were never released on the Oric, but in 1997 a couple of programmers made an Oric implementation of the system, called Pinforic, which allowed Oric users to finally play Zork on their machine

Since then, this system has been used by Hugo Labrande for his game Tristam Island as well as Stefan Vogt’s Hibernated

image.png

The Quill

The Quill is a program who’s been released on quite a few machines, including the Oric.

The first versions only supported text adventures, but some later extensions added support for graphics, unfortunately due to the poor sales of the product this expansion was never released for that machine.

image.png

A-code

Quite a few Level 9 games were released on the Oric, all using the same A-code interpreter, such as Colossal Adventure, Adventure Quest, Dungeon Adventure, Lords of Time and Snowball.

image.png

Unfortunately there is no easy was to make new games using this system on the Oric

Reinventing the wheel

The main reason for me to build my own engine is that none of the existing ones supported the extended memory supported by a full Oric system with a disk drive, and none of them had support for graphics, audio, animation, etc…

As a remainder, here is what my game looks like: image.png

So I built my own (it’s still a work in progress), using a semi-generic format for the game data, so technically if someone was interested it would not be too hard to port the game to another system.

I’ve a disclaimer though: I did not properly “design” the system, it cames organically from the needs when I wrote the game, and it progressively turned into a minimalistic byte-code based scripting language.

Game structure

The idea being to remove dependencies between the engine and the game means that no engine file should directly reference any location, item or action made by the player:

All that should be data driven in some way or another.

The gamedata in Encounter is structured this way:

  • A table of locations, with the links to other locations and pointers to descriptions and “location scripts”
  • A table of items, with various flags (is it a container, can it be moved, is it open or closed, …)
  • A table of keywords, mapping the written representation (“USE”) into an enumerated value
  • A table of actions, mapping enumerated values into “script callbacks”
  • One table for each possible mapped action
  • Actual scripts

Here is how it’s done (roughly, it’s not exactly like that in the actual code, but these blog posts formats are a bit narrow for large lines of code):

Locations

There are about 40 locations in the game, and each one has the following fields:

  • Associated North, South, East, West, Up and Down locations
  • Textual description
  • Script to run when reaching this location
Locations
  LOCATION(TUNNEL,NONE,ALLEY,NONE,TextLocMarket,ScriptMarket)
  LOCATION(STREET,NONE,ROAD,MARKET,TextLocAlley,ScriptAlley)  
  LOCATION(PATH,NONE,NONE,ALLEY,TextLocRoad,ScriptRoad)       

Items

The ‘items’ word basically means ‘anything that can be interacted with’, including items but also landscape features (“an open pit”) and creatures (“a growling dog”, “a sleeping thug”).

Items have quite a few parameters:

  • Textual description
  • Location where they can be found
  • Associated item (for containers)
  • Flags (movable, container, open/close, disabled/enabled, …)
  • Containers that can be used to transport them (ex: Water needs a bucket or bag)
Items
  ITEM(TextFridge,KITCHEN,IMMOVABLE|CLOSED)
  ITEM(TextSilverKnife,GARDEN)

Keywords

Nothing special there, just a list of text strings and an associated enumeration.

WordsArray
  MAPPING("READ"  , WORD_READ)
  MAPPING("USE"   , WORD_USE)
  MAPPING("OPEN"  , WORD_OPEN)
  MAPPING("FRIDGE", ITEM_Fridge)
  MAPPING("KNIFE" , ITEM_SilverKnife)  
  

Main Action table

Another straightforward table associating an enum to another table

ActionMappingsArray
  MAPPING(WORD_READ      ,ReadItemMappingsArray)
  MAPPING(WORD_USE       ,UseItemMappingsArray)
  MAPPING(WORD_OPEN      ,OpenItemMappingsArray)
  (...)

Action tables

And another one, mapping the enum of an item to an action script

ReadItemMappingsArray
  MAPPING(ITEM_Newspaper          , ReadNewsPaper)
  MAPPING(ITEM_HandWrittenNote    , ReadHandWrittenNote)

Scripts

Here is an example of “location script” associated to the “Market place” location from the location table

ScripMarket
    WAIT(DELAY_FIRST_BUBBLE)
    WHITE_BUBBLE(2)
    _BUBBLE_LINE(4,100,0,"The market place")
    _BUBBLE_LINE(4,106,4,"is deserted")
    END

Here is a script associated to the “READ” + “NOTE” combination of keywords found in the action mapping table

_ReadHandWrittenNote
    DISPLAY_IMAGE(LOADER_PICTURE_HANDWRITTEN_NOTE)
    WAIT(50*2)
    INFO_MESSAGE("That could be useful...")
    WAIT(50*2)
    INFO_MESSAGE("...if I can access it!")
    WAIT(50*2)
    UNLOCK_ACHIEVEMENT(ACHIEVEMENT_READ_THE_NOTE)
    END_AND_REFRESH

Run time

The way all that goes together is quite simple:

  • The game maintains a global location where the player is
  • When the player moves into a location
    • The matching picture is loaded from disk
    • The associated script for that location is executed
    • The engine asks the player what they want to do
  • When the player enters a series of commands
    • The parser isolate each keyword (space separated) and tries to find a matching value in the word mapping table
    • It searches in the action mapping table for any matching value for the first keyword
    • It runs the associated script

And that’s about it, the scripts themselves are relatively basic and can do the following:

  • Basic flag checking to validate the state of an item or their position in the world
  • Simple conditional and unconditional jumps
  • Delays
  • Commands to increase the player score, trigger achievements and game over conditions
  • Set of graphical commands to show pictures and graphic overlays on top of pictures
  • Set of textual commands to print texts and error messages

You can read more about the nitty gritty details on the documentation of the project on GitHub

I hope that was interesting!

Get Encounter HD (Oric)

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.