



We’re developing a text adventure for the C64 using Commodore BASIC, and we’ve made significant progress, but it’s still incomplete. So… what’s our next step?
I’m really pleased with my choice to use the Commodore 64 Visual Studio extension. It generates valid C64 BASIC code without line numbers, allowing for labels instead. If you’re not using this tool yet, I highly recommend checking it out.
If you do need line numbers, I’ve got you covered – grab that version on GitHub.
The game isn’t fully playable yet:
- The player can navigate and collect items, but they can’t interact with them yet.
- No help text is available.
- There are no winning conditions to complete the game.
- Mysteries and hidden areas are still needed.
- We haven’t defined how the player can lose.
- The introduction screen needs some design improvements.
Play the Adventure in Your Browser
You can experience the adventure as it currently stands in your browser using my C64 emulator here.
As I write this, I am also working on my version of a browser-based code editor and emulator experience for the 8bitworkshop, which will include Commodore BASIC, assembler, and C.
If successful, this will make it easier for people to follow these tutorials and create their own exciting C64 (and beyond) games and programs.
Interacting with Game Objects
Our variables encompass game objects, and we can place them within rooms.
Operating within the limitations of 1980s BASIC, we’ve chosen to implement several lists (arrays):
- Object names.
- Object descriptions.
- Current room location of each object (if it shows as in room 0, it is in the player’s inventory).
Next, we need to enable the ability to use objects.
if left$(i$,4) = "use " then gosub useobject
Initially, we can leverage the logic we used for get and drop actions to identify the object’s number from the list based on its name.
How should we structure interactions within this data setup?
We could establish on/off flags for our objects, similar to how we arranged the game map.
IF MID$(EX$(PL),1,2)<>"00" THEN PRINT ". NORTH"
In the same spirit, we might say, “if it is 0, then the light is off; if 1, then the light is on.”
For my XC-BASIC text adventure engine, this is the path I took. You can try it out here. Unfortunately, I didn’t get very far with it because XC-BASIC 3 was released and was incompatible with XC2. I may revisit it someday to make it work again.
After some thought, I believe simplicity is key for now.
There’s a point where abstraction can lead to increased complexity and additional work, especially with the limitations of this language version.
Ultimately, your approach depends on whether you’re creating a game or a game engine.
If we choose not to adopt a complicated data-driven route, we can implement completely flexible logic for any situation without requiring prior knowledge of how things may need to operate. You have full creative freedom to devise whatever scenarios you choose – drop a piano on the character, electrocute them in a wet bathroom, or launch them into space – it’s your game!
Resulting in something like the following:
objectactions:
rem actions for using objects
if f=1 and pl=3 then print "boom! the furnace explodes, filling the room with fire and smoke!":gosub waitkey:gosub gameover: rem matches in furnace room
if f=1 and pl=2 then print "suddenly, the furnace roars to life, filling the room with heat and light!":ex$(2)="010604000300":m=m-1: if m<=0 then print "You are out of matches":ol(f-1)=-1: return : rem remove matches from inventory
if f=1 and pl <> 2 then print "you strike a match and light it, illuminating the room for a moment.":m=m-1: if m<=0 then print "You are out of matches":ol(f-1)=-1: rem remove matches from inventory
if f=2 and pl=4 then print "click! the door has unlocked!":ex$(4)="020005000000":ol(f-1)=-1: rem remove key from inventory
if f=2 and pl<>4 then print "you try to use the key, but it doesn't fit any locks here."
RETURN
The first scenario shows that when lighting a match (f=1) in the service hatch (pl=3), it ignites a gas leak, causing an explosion.
On a milder note, using a key could unlock a door (or not).
Replacing Parts of Strings in C64 BASIC
As I previously mentioned, we could manipulate portions of fixed-length strings, but for convenience in my example, I’ll showcase a method of re-creating the string that reveals a new exit.
Here’s how we can surgically add a room into the data. To check for an south door, we’d use mid$(ex$(pl),5,2). This checks the character position and returns two characters.
Modifying specific characters in a C64 BASIC string can be intricate:
10 a$="abcdefgh"
20 print a$
30 a$=left$(a$,3)+"x"+mid$(a$,len(a$)-4,4)
40 print a$
Later versions of Commodore BASIC allow MID$ to be utilized in reverse, enabling character assignments at specified positions mid$(ex$(pl),5,2)="05" – which is far more user-friendly.
This way, ex$(4)="020000000000" transforms to 020005000000
Integrating New Screens (Help Text, Winning, and Intro)
Creating a basic text screen is straightforward with a subroutine (either a numbered line or a label in the Visual Studio method):
help:
rem show help screen
gosub newscreen
print "you have woken up in a dark, damp basement, with no memory of how you got here."
print ""
print "your head is throbbing, and you feel disoriented. You need to find a way out."
print ""
print "enter the command you want to use:"
print " North, South, East, West, Up, Down"
print " (n, s, e, w, u, d)"
print ""
print " get"
We’ve already established subroutines for waiting for a keypress and clearing the screen with the chosen text colors, so this just builds on our existing code.
The “winning condition” functions similarly. We have a return at the end, allowing the player to continue exploring, but you could use goto gameover: at the end of the code to exit the game entirely.
What if we want a more intricate display, perhaps something designed in a PETSCII screen editor?
Here’s how I display the introduction screen with the drawing of the house created using petscii.krissz.hu:
INTRO:
for i = 0 to 999
read c:
get i$: if i$ <> "" then return
poke 1024+i,c
next i
data 32, ... (data goes on)
gosub waitkey
RETURN
However, the rendering time is inefficient even for BASIC, so this may not make it into the final game!
The actual screen data resides in data statements. Each time we read c, we fetch the next data item (a character code) and save it into c.
We loop through a full screen of characters, utilizing poke to place them into memory starting at 1024, which corresponds to the top-left of the screen. We then repeat the process for character color information, incrementally filling the color cell memory at 55296.
Due to the lengthy rendering time, I included an escape option. If a key is pressed, the process short-circuits, as it can become tedious during multiple playthroughs.
INTRO:
for i = 0 to 999
read c:
get i$: if i$ <> "" then return
poke 1024+i,c
next i
More Advanced IF Logic in C64 BASIC
You might have noticed that each time the player strikes a match, we reduce the count by 1. This serves as a demonstration of how such a mechanism might work. It’s important to ensure it’s engaging and not frustrating for the player, providing multiple success avenues that don’t rely on match availability!
if f=1 and pl <> 2 then print "you strike a match and light it, illuminating the room momentarily.":m=m-1: if m<=0 then print "You are out of matches":ol(f-1)=-1: rem remove matches from inventory
Once we reach zero, I remove the match from inventory by marking its location as -1. This serves as a demonstration, but in reality, you’d likely still keep an empty matchbook. We could consider either replacing it with an empty matchbook object or simply changing the name/description of the existing one.
This example highlights a concept that many overlook about C64 BASIC: the functionality of AND and OR, as well as the ability to execute multiple commands if the logic holds true.
Even in vintage magazine code listings, I often see unnecessary GOTO statements for these scenarios, which may stem from repurposing code across different machines.
An interesting quirk of C64 BASIC is that you can use AND in conjunction with IF F=1 THEN IF PL<>2 THEN, and it operates effectively… but I find that using AND and OR is cleaner.
Monitor Memory Usage
As we continue to build out our adventure, it’s essential to keep an eye on our available BASIC memory, especially as we may start to run low:
gosub clrscr
POKE 53281,6 : POKE 53280,14
print "Goodbye!"
print ""
print "memory free",fre(0)
print ""
Complete Adventure Code Thus Far
rem text adventure game by chris garrett 2025 retrogamecoders.com
rem initialize variables etc.
gosub INIT
displayroom:
rem show room details
gosub clrscr
if pl=0 then pl = pp : rem player location cannot be 00, as that represents inventory
pp = pl : rem back up the location in case of illegal move
print rv$+lo$(pl)+ro$
print ""
if pl=5 then gosub YOUWIN
print "Visible objects:" + lb$
for i = 0 to oc-1 : rem check object locations from the first object to the object count
if ol(i) = pl then print ". ";ob$(i) : rem print object if in current location
next i
print ""
print wt$+"Exits available:"+lb$
rem check each potential exit
if mid$(ex$(pl),1,2)<>"00" then print ". north"
if mid$(ex$(pl),3,2)<>"00" then print ". east"
if mid$(ex$(pl),5,2)<>"00" then print ". south"
if mid$(ex$(pl),7,2)<>"00" then print ". west"
if mid$(ex$(pl),9,2)<>"00" then print ". up"
if mid$(ex$(pl),11,2)<>"00" then print ". down"
getcommand:
i$=""
print ""
print yl$+"What now?"+lb$
input i$
if left$(i$,3) = "go " then gosub fullmove
if i$ = "n" then gosub abrmove
if i$ = "e" then gosub abrmove
if i$ = "s" then gosub abrmove
if i$ = "w" then gosub abrmove
if i$ = "u" then gosub abrmove
if i$ = "d" then gosub abrmove
if left$(i$,1) = "i" then gosub inventory
if left$(i$,4) = "get " then gosub getobject
if left$(i$,5) = "take " then gosub takeobject
if left$(i$,1) = "h" then gosub help
if left$(i$,4) = "quit" then gosub gameover
if left$(i$,4) = "exit" then gosub gameover
if left$(i$,5) = "drop " then gosub dropobject
if left$(i$,8) = "examine " then gosub examineobject
if left$(i$,4) = "look" or left$(i$,1) = "l" then ?"":print rd$(pl):?"":gosub waitkey
if left$(i$,1) = "q" then goto gameover
if left$(i$,4) = "use " then gosub useobject
goto displayroom
fullmove:
rem detailed move command (e.g., GO SOUTH or GO S)
d$ = mid$(i$,4,1)
gosub moves
return
abrmove:
rem abbreviated move command (e.g., N)
d$ = i$
gosub moves
return
MOVES:
rem update the player location (PL)
if d$ = "n" then pl = val(mid$(ex$(pl),1,2))
if d$ = "e" then pl = val(mid$(ex$(pl),3,2))
if d$ = "s" then pl = val(mid$(ex$(pl),5,2))
if d$ = "w" then pl = val(mid$(ex$(pl),7,2))
if d$ = "u" then pl = val(mid$(ex$(pl),9,2))
if d$ = "d" then pl = val(mid$(ex$(pl),11,2))
return
INVENTORY:
rem objects in the player’s possession
print ""
print "Objects in your inventory:"
for i = 0 to oc-1 : rem check object location from the first object to the object count
if ol(i) = 0 then print ". ";ob$(i) : rem if the object is in zero, print it
next i
print ""
waitkey:
print cy$+rv$+" press a key to continue "+ro$
waitingforkey:
get i$
if i$="" goto waitingforkey
RETURN
takeobject:
rem alternative action to get
f=-1:r$=""
r$ = mid$(i$,6) : rem r$ is object requested
goto getobjid
getobject:
rem allow the player to pick up available objects and store in inventory
f=-1:r$=""
r$ = mid$(i$,5) : rem r$ is object requested
getobjid:
rem find the object id
for i = 1 to oc
if ob$(i-1) = r$ then f=i : rem it exists
next i
rem can't find it?
print ""
if f=-1 then print "Can't see that here, check spelling and be specific?" : goto donegetting
if ol(f-1)=pl then goto gotit
if ol(f-1)=0 then print "You already have that" : goto donegetting
print "I can't see that around here"
goto donegetting
gotit:
ol(f-1)=0 : rem set the object location to the inventory (room zero)
print ""
print "Got the ";ob$(f-1)
donegetting:
print ""
gosub waitkey
RETURN
DROPOBJECT:
rem drop objects the player is carrying
f=-1:r$=""
r$ = mid$(i$,6) : rem r$ is object requested
rem get the object id
for i = 1 to oc
if ob$(i-1) = r$ then f=i : rem it exists
next i
rem can't find it?
print ""
if f=-1 then print "Can't seem to find that, check spelling and be specific?" : goto donedropping
if ol(f-1)=0 then print "Ok, dropped!" : ol(f-1)=pl : goto donedropping
print "No can do, are you sure you have that?"
donedropping:
gosub waitkey
RETURN
examineobject:
rem examine objects the player is carrying
f=-1:r$=""
r$ = mid$(i$,9) : rem r$ is object requested
rem get the object id
for i = 1 to oc
if ob$(i-1) = r$ then f=i : rem it exists
next i
rem can't find it?
print ""
if f=-1 then print "Can't seem to find that, check spelling and be specific?" : goto doneexamining
if ol(f-1)=0 then print od$(f-1) : goto doneexamining
print "No can do, are you sure you have that?"
doneexamining:
gosub waitkey
RETURN
useobject:
rem use an object the player is carrying
f=-1:r$=""
r$ = mid$(i$,5) : rem r$ is object requested
rem get the object id
for i = 1 to oc
if ob$(i-1) = r$ then f=i : rem it exists
next i
rem can't find it?
print ""
if f=-1 then print "Can't seem to find that, check spelling and be specific?" : goto doneusing
if ol(f-1)=0 then gosub objectactions : goto doneusing
print "No can do, are you sure you have that?"
doneusing:
gosub waitkey
RETURN
objectactions:
rem actions for using objects
if f=1 and pl=3 then print "Boom! The furnace explodes, filling the room with fire and smoke!":gosub waitkey:gosub gameover: rem matches in furnace room
if f=1 and pl=2 then print "Suddenly, the furnace roars to life, filling the room with warmth and light!":ex$(2)="010604000300":m=m-1: if m<=0 then print "You are out of matches":ol(f-1)=-1: return : rem remove matches from inventory
if f=1 and pl <> 2 then print "You strike a match and light it, illuminating the room for a brief moment.":m=m-1: if m<=0 then print "You are out of matches":ol(f-1)=-1: rem remove matches from inventory
if f=2 and pl=4 then print "Click! The door has been unlocked!":ex$(4)="020005000000":ol(f-1)=-1: rem remove key from inventory
if f=2 and pl<>4 then print "You try to use the key, but it doesn't fit any locks here."
RETURN
help:
rem show help screen
gosub newscreen
print "You have awakened in a dark, damp basement, with no memory of how you got here."
print ""
print "Your head is pounding and you feel disoriented. You need to find a way out."
print ""
print "Enter the command you want to use:"
print " North, South, East, West, Up, Down"
print " (n, s, e, w, u, d)"
print ""
print " get"
Original article by retrogamecoders.com