A simple text-based adventure game


The Lighthouse of Doom

This repository contain a simple text-based adventure game, implemented twice, once in portable C, and once in Z80 assembly language, targetted at the CP/M operating system.

My intention was to write a simple text-based adventure game to run under CP/M. Starting large projects in Z80 assembly language from scratch is a bit of a daunting prospect, so I decided to code the game in C first, so that I could get the design right, and avoid getting stuck in too many low-level details initially.

Quick links within this README file:

Game Plot

  • You're inside a lighthouse (trapped? doesn't really matter I guess).
  • You see a boat on the horizon, heading your way.
  • But "oh no!", the lighthouse is dark.
    • The boat will surely crash if you don't turn on the main light.

The game is over, when you either fix the light, or find another solution.

If you do not achieve victory within a turn-limit the boat runs aground, and death will consume you all. (It is a very big boat!)

C Implementation

The implementation is mostly concerned with creating the correct series of data-structures, which are essentially arrays of objects. Because if we can make the game table-based we simplify the coding that needs to be done - we don't need to write per-object handlers anywhere, we can just add pointers to tables/structures.

The C implementation defines most of the important things in the file globals.h such as:

  • The structure to define a location.
  • The structure to define an object.

The game-state itself is stored in a couple of global variables, there isn't too much state to care about:

  • The current location (i.e. index into location-table).
  • A list of any items you're carrying.
  • The number of turns you've taken.
    • Incremented by one each time you enter a command, be it recognized or not.
  • Whether you won/lost.

Building & Running It

Build the game by running make, and then run ./lighthouse

If you get stuck you can see command by entering help.

Z80 Implementation

The Z80 implementation is based upon the C-implementation, with a few small changes.

The implementation uses a simple set of structures:

  • A command-table to map input-commands to handlers.
  • An item-table to store details about each object in the game.
  • A person table to store telephone messages.

The whole implementation is defined in the file game.z80.

Along the way I did realize that having fixed inventory slots made the coding more of a challenge, so I made the location of each object a property of the object itself.

Z80 Changes

  • The Z80 version has more easter-eggs (Try typing "xyzzy" a few times).
  • There is no grue in this version.
    • It will come.
  • There are two victory conditions.
  • The Z80 version can be built with the text-strings, and game code, protected by simple XOR encryption
    • This stops users from looking through the binary for hints.
    • Run make release to build the protected version.
    • Run make game to build a raw version.

Compiling & Running It

Ensure you have the pasmo assembler installed, then build the code by running make game, or make release.

In either case the output will be a binary named lihouse.com which you should be able to run upon your system - or under a CP/M emulator.

Downloading It

If you look on our release page you can find the latest stable build.

Transfer lihouse.com to your system, and run LIHOUSE to launch it.


Report any bugs as you see them:

  • A crash of the game is a bug.
  • Bad spelling, grammar, or broken punctuation are also bugs.
  • Getting into a zombie-state where winning or losing are impossible is a bug.
    • Albeit unlikely!


  • Fix turn counter

    Fix turn counter

    As discussed in #24 and #25, the turn counter is off-by-1 at the end of the game, because normally the turn count is output before it is incremented, so turns_function outputs TURN_COUNT+1.

    This pull request makes the game increment the turn counter before running the command handler, so that turns_function no longer needs to increment the value it outputs, so that both the value displayed by TURNS command, and the value displayed at the end of the game, are correct.

    I also simplified the JP_HL function, I think this version works but it's possible I misunderstood something about its operation and have subtly broken something - happy to be proven wrong on that one. I'm not a Z80 assembly language expert.

    I'm also not sure if you'll be happy with pushing hl, incrementing the TURN_COUNT, and then popping hl. You might consider it inelegant? It was the simplest thing I could think of, but again I'd be happy to be shown something better.

    opened by jes 4
  • Allow objects to be toggled, cleanly.

    Allow objects to be toggled, cleanly.

    For the purpose of this bug I'm considering only the TRAPDOOR, however a similar process applies to the TORCH - we pretend we have state "TORCH ON" vs. "TORCH OFF", but instead have two objects "TORCH" which is unlit, and "TORCH-LIT" which has text saying "We're on".

    At the moment we have two items in the game:


    When the appropriate action is carried out we remove TRAPDOOR from the world, and replace it with TRAPDOOR-OPEN. Doing the switch in reverse is difficult because if we tried to handle:


    We'd then use our find-object-by-name code and find TRAPDOOR, and never find TRAPDOOR-OPEN.

    So we either need to rename the items, dynamically, allow aliases, or handle things in a different way. What we could do is remove TRAPDOOR-OPEN and instead use a state-flag to determine the state. This would be clean and easy:

    • Each item does have a "STATE" flag.
      • We can decide "STATE: 0 == closed".
      • We can decide "STATE: 1 == opened".

    The only problem with this approach is the description of the item. We do have an examine function pointer so we can use a dedicated "examine_trapdoor" function:

    • if state == 0 : show "this is a trapdoor .."
    • if state == 1 : show "this is an open trapdoor .."

    The place where this falls down is the "LOOK" handler, which will not call the examine handler, so it will always show " the contents of the items description attribute.

    Either we add a function "LOOK_fn" to call, or we have to manually update the pointer to make the appearance dynamic. I think the latter is possible, but it might be a pain to code.

    Updating the pointers will add extra logic to the "use_xxx_fn" but in some ways that has to be easier than swapping locations and duplicating objects?

    enhancement Z80 
    opened by skx 3
  • Spelling and grammar fixes

    Spelling and grammar fixes

    Cool game, thanks for making this!

    I noticed a couple of typos while playing, which I've fixed here.

    I also noticed that the turn count displayed after the end of the game seems to be off-by-1. The fastest speedrun I've found is "down; examine desk; get meteor; up" which I think you'll agree is 4 turns, but the game says I played 5 turns. I had a quick look and it looks like the turn count is incremented at the end of your turn, but turns_function adds 1 to it before displaying it, because the "TURNS" command displays it before it's incremented. Maybe we should move the check for whether you've won or died to at the end of the turn, but right before TURN_COUNT is incremented?

    opened by jes 2
  • Coding change ..

    Coding change ..

    I should use the IX/IY registers to point to our object table, as this would make fetching items from it much simpler.

    It'd be a lot of code-churn for little benefit, but with the aims of clarity it might be worthwhile. I'll make this wishlist on that basis.

    Z80 wishlist 
    opened by skx 1
  • Port to ZX Spectrum

    Port to ZX Spectrum

    Since I've been on a nostalgia trip recently why not port this "game" to the ZX Spectrum?

    • We compile via pasmo which can generate appropriate images.
    • Most of our code is pure assembly, our operating system interface is pretty much:
      • Read input.
      • Output string content.

    It shouldn't be hard to move that over ..

    opened by skx 1
  • Playthrough notes

    Playthrough notes

    I watched a volunteer play, and that was educational.

    Some commands entered (which did not work as expected) included:

    • [x] LOOK AT DOG
    • [ ] PET THE DOG
    • [x] READ THE BOOK
    • [x] OPEN THE DOOR
      • (TRAPDOOR was meant)
    • [x] EXAMINE ROOM
    • [x] SLEEP
    • [x] WAIT
    • [x] LOOK AT BASKET

    Other random comments:

    • [x] Spelling: The boat is approaching - not "an approach ship"
    • [x] Spelling: "I did not understnad that" -> "understand"
    • [x] We agreed that invalid commands should not increase the turn-count.
    • [x] The player did not even attempt to move, take, get, or carry the generator. She said "It was too big!". So it needs to be noted that this is both small and portable.
    bug enhancement spoiler 
    opened by skx 1
  • Need

    Need "get" and "use handler"

    We need to implement custom behaviour:

    • when an item is picked up. (e.g. RUG).
    • when an item is used (e.g. MIRROR, and GENERATOR).

    With those we can implement the rug/trapdoor puzzle, and setup a victory condition which makes the game "complete".

    opened by skx 1
  • When I'm feeling BLUE all I have to do is release a GRUE

    When I'm feeling BLUE all I have to do is release a GRUE

    The GRUE could escape, if the trapdoor is left open.

    Traditionally they're creatures of darkness, but covid-related mutations have been known to drive them out into ~scarier~ sunnier environments.

    opened by skx 1
  • The trapdoor has three logical states.

    The trapdoor has three logical states.

    We have added a logical "invisible" state to the trapdoor. All of our objects are already allowed to be invisible, but they tend to be things that can't be interacted with, or fake-invisible so that you can access them if you know about them.

    The trapdoor must be really invisible though, so it can only be opened/closed once discovered.

    Add the third state, and use it appropriately. This closes #33.

    opened by skx 0
  • Allow turning the torch off.

    Allow turning the torch off.

    We had accidentally left a pop hl instruction in-place as part of our state-update. That meant turning the torch off popped off the correct return-address, and gave us undefined behaviour.

    This closes #34.

    opened by skx 0
  • Using an item twice causes oddness ..

    Using an item twice causes oddness ..

    I think this is related to #33, and is caused by the cleanup we made when we used flag-states rather than duplicate objects (27eda49890919ae091123ec4d32d974dc413bba8).


    • use torch
    • use torch

    Torch goes on, then when you turn it off you drop into undefined behaviour. (Missing ret?)

    bug spoiler 
    opened by skx 0
  • Possible changes to item-table for z80 implementation?

    Possible changes to item-table for z80 implementation?

    I'm writing this down not because I'm committing to making the change, but because it has been on my mind for a couple of days.

    We started our implementation with the obvious commands "GET", "TAKE", "DROP". Each item in the item-table has pointers for those basic-functions. Later we added space for overriding the default behaviour. So TAKE RUG does something distinct from the generic take_command handler, which just looks up the appropriate object and stores it in the inventory.

    After a while we augmented our command-table with synonyms. So for example these three commands both invoke the same use_command:


    However this doesn't always make sense. Because "READ" is mapped to "USE" it is now possible to:

      • You win. (If you're in the right location, etc, etc).

    It does make sense to have more commands than USE. However it doesn't make sense for those to be global. So perhaps our item-structure should change from this:

            DEFW item_3_name  ; torch
            DEFW item_3_desc
            DEFW item_3_long
            DEFB 0,0          ; No take function
            DEFB 0,0          ; No drop function
            DEFB 0,0          ; No examine function
            DEFW use_torch_fn ; USE TORCH
            DEFB 0            ; item state
            DEFB 1            ; this item can be picked up
            DEFB 0x00         ; top-floor

    To something like this:

            DEFW item_3_name  ; torch
            DEFW item_3_desc
            DEFW item_3_long
            DEFB 5, "LIGHT", light_torch_fn
            DEFB 3, "USE", light_torch_fn
            DEFB 0xff

    i.e. Rather than have the actions be global READ, LIGHT, CALL, DIAL, PHONE, we should scope the commands to the objects.

    This feels logical, correct, and good. The downside is that we'd have to make a hell of a lot of updates to change it. The only real addition would be that we could say:

    • PET DOG

    Without also allowing KILL BOOK, and PET TORCH. Which realistically? Nobody is going to type. I think.

    There is still going to be duplication, because I guess we'll have EXAMINE xx and TAKE xxx for essentially all objects. That's why in my updated structure I still have name, description, and extended description. I guess I'll also need to keep the location field. And state.

    Z80 wishlist 
    opened by skx 0
  • release-1.1(Aug 11, 2022)

  • release-1.0(Aug 9, 2022)

    This release features a bunch of internal changes to the Z80 codebase, most notably a number of optimizations relating to the use of the ix register.

    There were some minor changes to the text, and internal details, but the biggest visible difference is the introduction of "widescreen output". The ZX Spectrum's text-mode is limited to 32 characters in width. Via the use of a custom text-drawing routine, and a tiny font, the game now outputs 64-characters of text per line.

    The game feels a little more cramped, but the poor word-wrapping of the previous release is now less significant.

    Source code(tar.gz)
    Source code(zip)
    lihouse.com(12.44 KB)
    lihouse.tap(12.60 KB)
  • release-0.9(Apr 30, 2022)

    This release features a number of small changes to the game output, but nothing terribly significant.

    The biggest difference between this release and the previous is the internal changes, relating to objects. We're now taking advantage of the "state" byte associated with game-objects to handle the use of the trapdoor and torch.

    Using a state-byte within the objects simplifies the code logically, and allows the trapdoor to be closed (for example).

    You can play the most recent version of the code online here:

    • https://steve.fi/software/lighthouse
    Source code(tar.gz)
    Source code(zip)
    lihouse.com(11.91 KB)
    lihouse.tap(11.76 KB)
  • release-0.8(Apr 27, 2022)

    This release features a couple of minor tweaks to the game for the ZX Spectrum, to make it more playable:

    • Backspace will delete a character at the input-prompt.
    • The input will be capped at 30 characters, to avoid scrolling issues.
    • Escape continues to discard any text input.
    • Finally the "unrecognized command" message was reworded to avoid word-wrapping issues.
    Source code(tar.gz)
    Source code(zip)
    lihouse.com(11.70 KB)
    lihouse.tap(11.56 KB)
  • release-0.7(Apr 26, 2022)

    This release features a brand new port, "Lighthouse of Doom" is now available as a .tap image for the ZX Spectrum 48k!

    This was briefly documented upon my blog, here:

    In brief the port involved creating a simple "bios" which could be used to abstract the differences between the APIs supported by CP/M and the ZX Spectrum ROM. I wrote routines like "bios_clear_screen" which would either call the ROM, or CP/M as appropriate.

    There are no major changes to the CP/M code here, and the C-code was left alone. There were some minor additions such as:

    • The prompt to replay the game upon failure.
      • This required resetting the game-state between runs.
    • More correct handling of termination conditions.
      • If you get killed by too much magic we don't need to see the boat-crash message, for example.

    There's a small amount of debugging code present in the game now, accessible via bios.

    Source code(tar.gz)
    Source code(zip)
    lihouse.com(11.27 KB)
    lihouse.tap(11.04 KB)
  • release-0.6(Aug 23, 2021)


    This release features a relatively small number of small changes:

    • There were some grammar and spelling fixups.
      • Thanks to @jes.
    • An off-by-one issue with the turn-counter was fixed
      • Thanks to @jes, once again.
    • The code became a few bytes smaller.
      • Replacing some calls to the BIOS with a call to a common-helper routine for string-output.
    Source code(tar.gz)
    Source code(zip)
    lihouse.com(10.80 KB)
  • release-0.5(Apr 29, 2021)


    This release updates our text in small ways, adds a version notice to the released binary, and updates our parser to support more commands.

    For example EXAMINE ROOM is the same as LOOK, and we can TURN ON xxx instead of being forced to USE xxx.

    Source code(tar.gz)
    Source code(zip)
    lihouse.com(10.81 KB)
  • release-0.4(Apr 28, 2021)


    This release fixes a couple of minor spelling and grammar issues, and increases the number of turns you have available to solve the puzzle, and save the ship.

    The parser has been updated to allow :

    • "EXAMINE THE XXX" instead of just "EXAMINE XXX",.
    • "GO DOWN" instead of just DOWN.

    Several minor changes have been made to the various puzzles and items within the game. Our internal organization has been much improved via the use of offsets calculated by the compiler - rather than hand-counted. This allowed the addition of several new fields in our item-table, allowing cleaner code.

    The game now reminds you to hurry up, every five turns or so, and a completely new second victory condition has been added, to allow for surprises.

    Unrecognized commands now inform the user to run HELP to see the (documented, but not complete) list of commands - and unrecognized commands don't count towards your turn-limit.

    Finally it is now possible to call the ghostbusters, because who else are you going to call?

    Source code(tar.gz)
    Source code(zip)
    lihouse.com(10.12 KB)
  • release-0.3(Apr 26, 2021)

  • release-0.2(Apr 26, 2021)


    This release fixes a couple of display-related bugs, as found when playing the game on real hardware.

    • Previously we used a combination of \r\n and \n characters to make newlines appear on-screen.
    • We're now using the two-character form universally to avoid text not starting at the beginning of lines.
    • The output produced by HELP, LOOK, and INVENTORY are now indented.

    Finally EXAMINE produces the correct results for our TRAPDOOR object.

    Source code(tar.gz)
    Source code(zip)
    lihouse.com(8.05 KB)
  • release-0.1(Apr 26, 2021)

Steve Kemp
Steve Kemp
GB Studio is a quick and easy to use retro adventure game creator for Game Boy available for Mac, Linux and Windows

GB Studio is a quick and easy to use retro adventure game creator for Game Boy available for Mac, Linux and Windows

Chris Maltby 6.8k Jan 8, 2023
Improved version of the X-Ray Engine, the game engine used in the world-famous S.T.A.L.K.E.R. game series by GSC Game World.

OpenXRay OpenXRay is an improved version of the X-Ray Engine, the game engine used in the world-famous S.T.A.L.K.E.R. game series by GSC Game World. S

null 2.2k Jan 1, 2023
Stealthy way to hijack the existing game process handle within the game launcher (currently supports Steam and Battle.net). Achieve external game process read/write with minimum footprint.

Launcher Abuser Stealthy way to hijack the existing game process handle within the game launcher (currently supports Steam and Battle.net). Achieve ex

Ricardo Nacif 80 Nov 25, 2022
Game Boy, Game Boy Color, and Game Boy Advanced Emulator

SkyEmu SkyEmu is low level cycle accurate GameBoy, GameBoy Color and Game Boy Advance emulator that I have been developing in my spare time. Its prima

Sky 321 Jan 4, 2023
SameBoy DX is a Qt-based interface of SameBoy, a free, highly accurate Game Boy and Game Boy Color emulator.

SameBoy DX SameBoy DX is a Qt-based interface of SameBoy, a free, highly accurate Game Boy and Game Boy Color emulator. Build requirements: CMake Pyth

Snowy 7 Oct 2, 2022
To recreate the board game Scotland yard and enable a single player to play the game by letting one of the roles being played by the computer based on written algorithm

Scotland Yard GAME OF SCOTLAND YARD This is a custom version of the classic board game, Scotland Yard .The game uses the London map used in the origin

Brshank 2 Nov 11, 2021
Ncurses based omok game, execute omok game in your terminal

omok_game execute omok game in your terminal Omok game played by two people. 한국어 버전(korean version)

SunjungAn 1 Dec 3, 2022
Simple Ball-Based Game, made using SFML.

Ball Ball is a basic windows-only, ball-based game, made using C++ SFML. The goal of the game is to claim 5 balls every 10 seconds. Ball can never be

orlando 2 Dec 20, 2021
A sample project combining Epic Games' MetaHuman digital characters with Amazon Polly text-to-speech.

Amazon Polly & MetaHumans Sample Project A sample project combining Epic Games' MetaHuman digital characters with Amazon Polly text-to-speech. This Un

AWS Samples 75 Dec 21, 2022
A cycle-accurate Game Boy and Game Boy Color Emulator, with rewind feature.

Azayaka is a free and open-source Game Boy and Game Boy Color emulator written in C++. Features Cycle-Accurate emulation. Console based Debugg

Zach Collins 23 Dec 3, 2022
The Game Boy ROM of the Game Boy bitcoin miner!

game-boy-bitcoin-miner The Game Boy ROM of the Game Boy bitcoin miner! To build this, currently this patch needs to be applied to GBDK: https://gist.g

Ghidra Ninja 80 Dec 11, 2022
CLUSEK-RT is a complex game engine written in C++ and the successor of the CLUSEK game engine

CLUSEK-RT is a complex game engine written in C++ and the successor of the CLUSEK game engine. This engine has been designed with a cross-platform design in mind. Thanks to Vulkan API it delivers a next-gen experience with ray tracing to both Linux and Windows platforms

Jakub Biliński 48 Dec 29, 2022
Ground Engine is an easy to use Game Engine for 3D Game Development written in C++

Ground Engine is an easy to use Game Engine Framework for 3D Game Development written in C++. It's currently under development and its creation will b

 PardCode 61 Dec 14, 2022
Minetest is an open source voxel game engine with easy modding and game creation

Minetest is an open source voxel game engine with easy modding and game creation

Minetest 8.3k Dec 29, 2022
A game made for the Game (Engineless) Jam using Raylib

Fastest Pizza Delivery A fun little 3D game made for the Game (Engineless) Jam. It is still is development but the basic gameplay is something l

Ryuzaki 2 Apr 3, 2022
Slender Man Returns is a game inspired by the original indie game Slender

Slender Man Returns is a game inspired by the original indie game Slender: The Eight Pages, but redesigned to run on the popular Unity game engine. Ported to PSVita. Original project by Rosario Terranova.

null 8 Jul 5, 2022
A Game Boy game that rewards you for playing it on several console models!

GB Corp. A Game Boy game for the Game Boy Competition 2021 by Dr. Ludos (2021) This is the source code, you can get a precompiled rom from here: https

Dr. Ludos 10 Sep 25, 2022
Simple tower defense game using C++ with Entity Component System (ECS)

CubbyTower CubbyTower is a simple tower defense game using C++ with Entity Component System (ECS). The code is built on C++17 and can be compiled with

Chris Ohk 39 Dec 20, 2022
Simple, fast, easy to get started mid-level game engine written in Zig

Alka Game engine written in zig, compatible with master branch. This engine does provide a toolset for you but generally you have to implement how the

Kiakra 23 Dec 5, 2022