My Applesoft BASIC port of Richard Garriott’s “D&D #1”
When I was 10 years old, I encountered an Apple ][ game called
Akalabeth
, and it made quite an impression on my 10-year-old mind.
At the lowest levels of the dungeon, you could encounter the
Balrog,
which was amazing and terrifying. Most importantly, it was possible to
dig in and LIST the program, and I spent countless hours trying to
disect it.
Later, a game called
Ultima
(to date myself, I’ll note that it wasn’t called
Ultima I, just Ultima!) appeared as a present under the Christmas tree.
Today I am a
professor of
Electrical
of Computer Engineering,
and I can
trace my current occupation back to my fascination with those games and
the machine they ran on.
The author of those games, Richard Garriott, a.k.a. Lord British, is back
in the games industry after a
lengthy haitus
(and
a few false starts)
with Shroud of the Avatar.
As a promo, his company
Portalarium
is running
(or possibly has run, depending on when you are reading this)
a contest to port his
first computer role-playing game, “D&D #1,” to run in either a web
browser or the Unity engine.
I wasn’t particularly interested in competing
in the contest myself, but I thought it would still
be fun to play around with,
so I ported his
PDP-11 BASIC source code to Applesoft BASIC.
I “cut my teeth” on Applesoft BASIC (even writing an CRPG myself,
“Wrath of Iffadaq,” for a class project when I was a sophomore in high
school), and it was what Richard wrote his early commercial games in, so
I thought Applesoft would be a fitting match. More importantly, it’s quite
compatible with the PDP-11 BASIC that Richard used for D&D #1,
and Apple ][ emulators are in abundance.
Down to business
The text
of my port is here: dnd1_applesoft.txt.
If you’d like to play it, paste the source code into
Joshua
Bell’s online Applesoft BASIC interpreter. You will need to start
a “NEW” game. Although you can save your game to a “file”,
it seems that Joshua’s
interpreter doesn’t keep those files persistent between runs, so attempting
to load a saved game at the beginning runs into trouble. (Loading
a saved game “should”
work on a more complete Apple ][ emulator, but I hvaen’t tried it.)
When you start, answer
“NO” to “DO YOU NEED INSTRUCTIONS” and “SHAVS” to “PLAYERS NME” (Richard
may be a brilliant game designer and programmer, but his spelling is
atrocious!), or else it will kick you right out right away! When you
are asked “DUNGEON #?” type a number between 1 and 6 (inclusive).
My port is based on the
remastered version
created by
“dejayc” on the SotA forums. I am not entering the formal contest myself,
but perhaps people who are entering the contest may find my port useful
in some way.
One problem with the materials Richard posted is that there appear to
be six dungeons that you can explore, but the data files for the dungeon
themselves are missing. There’s a hidden “command 12” that accesses a
cryptic level editor that lets you create and save levels, but you
can’t access it without the game having some levels to read to begin with
in order to be able to enter that command.
So I added some code that create six default dungeons that have walls on
the edges and are empty otherwise.
The normal lookaround command, 6, only shows an 11×11 area around the
player. There’s a “cheat” lookaround command, 10,
that shows the entire dungeon.
(Either the cheat display code or my dungeon creation code may have a
bug, since it seems the southern wall I think I am creating doesn’t display).
From a modern viewpoint, the game
is mostly unplayable. It was probably
unplayable from a 1977 viewpoint too (Richard made 27 more of these, until
28b became Akalabeth). But it’s a quirky look into the mind of a clever
teenage kid with access to a teletype.
Details on the porting
First, the easy stuff:
- Search and replace “GO TO” with “GOTO”
- Search and replace “RND(0)” with “RND(1)” (in PDP-11 BASIC,
RND(0) returns a new random number between 0 and 1; in Applesoft
BASIC, RND(1) returns a new random number between 0 and 1; but in
Applesoft BASIC, RND(0) returns the previous random number) - Search and replace “THEN” with “GOTO”
- Search and replace “STOP” with “END”
(Careful — there’s
several instruction strings that
have the word STOP in them, you don’t want to replace those!) - Change PRIT to PRINT on these lines:
01330 PRIT "YOUR A CLERIC YOU CANT USE THAT "
02417 PRIT "GP= ";C(7)
09950 PRIT "DONE"
- Some PDP-11 BASIC commands have no equivalent in Applesoft BASIC, so
let’s just comment those out, writing them as:
00100 REM BASE 0
00160 REM LET J9=RND(CLK(J9))
00210 REM FILE #1="DNG1"
00220 REM FILE #2="DNG2",#3="DNG3",#4="DNG4",#5="DNG5",#6="DNG6"
00230 REM RESTORE #4
00240 REM FILE #7="GMSTR"
00245 REM RESTORE #7
00250 REM RESTORE #1
00260 REM RESTORE #2
00261 REM RESTORE #3
00262 REM RESTORE #4
00263 REM RESTORE #5
00264 REM RESTORE #6
- I fixed a major bug in the original:
I changed
00370 IF Q$="Y" GOTO 00720
to
00370 IF Q$="Y" GOTO 01730
so it jumps to the “who said you can play” line instead of jumping into the
middle of an unrelated FOR loop. - I added the space to this line, so it will say things like
BALROG WATCH IT instead of BALROGWATCH IT:
07600 PRINT B$(K);" WATCH IT"
The trickiest part was porting the file access — the reading and writing
of the dungeon maps and the “save game” file.
Here is the original “load game” cod:
01770 REM READ OUT OLD GAME 01775 RESTORE #7 01780 READ #7,D 01790 READ #7,X 01800 READ #7,J 01810 READ #7,G 01820 READ #7,H 01830 READ #7,K 01840 FOR M=0 TO 25 01850 FOR N=0 TO 25 01860 READ #7,D(M,N) 01870 NEXT N 01880 NEXT M 01890 FOR M=1 TO X 01900 READ #7,W(M) 01910 NEXT M 01920 FOR M=1 TO 10 01930 READ #7,B$(M) 01940 FOR N=1 TO 6 01950 READ #7,B(M,N) 01960 NEXT N 01970 NEXT M 01980 FOR M=0 TO 7 01990 READ #7,C$(M) 02000 READ #7,C(M) 02010 NEXT M 02020 READ #7,N$ 02030 READ #7,F1 02040 READ #7,F2 02050 FOR M=1 TO 15 02060 READ #7,I$(M) 02070 NEXT M 02080 READ #7,X3 02090 FOR M=1 TO X3 02100 READ #7,X4(M) 02110 NEXT M 02120 READ #7,X1 02130 FOR M=1 TO X1 02140 READ #7,X2(M) 02150 NEXT M 02151 READ #7,F2 02152 READ #7,F1
I changed the “load game” code to:
01770 REM READ OUT OLD GAME 01775 PRINT CHR$(4);"OPEN GMSTR":PRINT CHR$(4);"READ GMSTR" 01780 INPUT D 01790 INPUT X 01800 INPUT J 01810 INPUT G 01820 INPUT H 01830 INPUT K 01840 FOR M=0 TO 25 01850 FOR N=0 TO 25 01860 INPUT D(M,N) 01870 NEXT N 01880 NEXT M 01890 FOR M=1 TO X 01900 INPUT W(M) 01910 NEXT M 01920 FOR M=1 TO 10 01930 INPUT B$(M) 01940 FOR N=1 TO 6 01950 INPUT B(M,N) 01960 NEXT N 01970 NEXT M 01980 FOR M=0 TO 7 01990 INPUT C$(M) 02000 INPUT C(M) 02010 NEXT M 02020 INPUT N$ 02030 INPUT F1 02040 INPUT F2 02050 FOR M=1 TO 15 02060 INPUT I$(M) 02070 NEXT M 02080 INPUT X3 02090 FOR M=1 TO X3 02100 INPUT X4(M) 02110 NEXT M 02120 INPUT X1 02130 FOR M=1 TO X1 02140 INPUT X2(M) 02150 NEXT M 02151 INPUT F2 02152 INPUT F1: PRINT CHR$(4);"CLOSE GMSTR"
This is the original “save game” code:
06610 REM SAVE GAME 06615 RESTORE #7 06620 WRITE #7,D 06630 WRITE #7,X 06640 WRITE #7,J 06650 WRITE #7,G 06660 WRITE #7,H 06670 WRITE #7,K 06680 FOR M=0 TO 25 06690 FOR N=0 TO 25 06700 WRITE #7,D(M,N) 06710 NEXT N 06720 NEXT M 06730 FOR M=1 TO X 06740 WRITE #7,W(M) 06750 NEXT M 06760 FOR M=1 TO 10 06770 WRITE #7,B$(M) 06780 FOR N=1 TO 6 06790 WRITE #7,B(M,N) 06800 NEXT N 06810 NEXT M 06820 FOR M=0 TO 7 06830 WRITE #7,C$(M) 06840 WRITE #7,C(M) 06850 NEXT M 06860 WRITE #7,N$ 06870 WRITE #7,F1 06880 FOR M=1 TO 15 06890 WRITE #7,I$(M) 06900 NEXT M 06910 WRITE #7,X3 06920 FOR M=1 TO X3 06930 WRITE #7,X4(M) 06940 NEXT M 06950 WRITE #7,X1 06960 FOR M=1 TO X1 06970 WRITE #7,X2(M) 06971 NEXT M 06972 WRITE #7,F2 06980 WRITE #7,F1
I changed the “save game” code to:
06610 REM SAVE GAME 06615 PRINT CHR$(4);"OPEN GMSTR":PRINT CHR$(4);"WRITE GMSTR" 06620 PRINT D 06630 PRINT X 06640 PRINT J 06650 PRINT G 06660 PRINT H 06670 PRINT K 06680 FOR M=0 TO 25 06690 FOR N=0 TO 25 06700 PRINT D(M,N) 06710 NEXT N 06720 NEXT M 06730 FOR M=1 TO X 06740 PRINT W(M) 06750 NEXT M 06760 FOR M=1 TO 10 06770 PRINT B$(M) 06780 FOR N=1 TO 6 06790 PRINT B(M,N) 06800 NEXT N 06810 NEXT M 06820 FOR M=0 TO 7 06830 PRINT C$(M) 06840 PRINT C(M) 06850 NEXT M 06860 PRINT N$ 06870 PRINT F1 06880 FOR M=1 TO 15 06890 PRINT I$(M) 06900 NEXT M 06910 PRINT X3 06920 FOR M=1 TO X3 06930 PRINT X4(M) 06940 NEXT M 06950 PRINT X1 06960 FOR M=1 TO X1 06970 PRINT X2(M) 06971 NEXT M 06972 PRINT F2 06980 PRINT F1: PRINT CHR$(4);"CLOSE GMSTR"
The original dungeon saving code, which is part of the primitive level
editor accessible with command 12, is:
11062 IF Q1 GOTO 7000 11063 FOR M=0 TO 25 11070 FOR N=0 TO 25 11080 WRITE #D2,D(M,N) 11090 NEXT N 11100 NEXT M 11110 GOTO 7000
I changed the dungeon saving code to:
11062 IF Q1 GOTO 7000 11063 PRINT CHR$(4);"OPEN DNG"+STR$(D2) 11064 PRINT CHR$(4);"WRITE DNG"+STR$(D2) 11065 FOR M=0 TO 25 11070 FOR N=0 TO 25 11080 PRINT D(M,N) 11090 NEXT N 11100 NEXT M 11105 PRINT CHR$(4);"CLOSE DNG"+STR$(D2) 11110 GOTO 7000
The original dungeon reading code is:
01400 REM READ DUNGEON AND START GAME 01410 RESTORE #D 01415 PRINT "READING DUNGEON NUM. ";D 01420 FOR M=0 TO 25 01430 FOR N=0 TO 25 01431 D(M,N)=0 01432 IF D=0 GOTO 01450 01440 READ #D,D(M,N) 01443 IF D(M,N)0 GOTO 01450 01445 IF RND(1)<.97 GOTO 01447 01446 D(M,N)=7 01447 IF RND(1)<.97 GOTO 01450 01448 D(M,N)=8 01450 NEXT N 01460 NEXT M 01470 REM YEA START
I changed the dungeon reading code to:
01400 REM READ DUNGEON AND START GAME 01410 REM RESTORE #D 01412 PRINT CHR$(4);"OPEN DNG"+STR$(D) 01413 PRINT CHR$(4);"READ DNG"+STR$(D) 01415 PRINT "READING DUNGEON NUM. ";D 01420 FOR M=0 TO 25 01430 FOR N=0 TO 25 01431 D(M,N)=0 01432 IF D=0 GOTO 01450 01440 INPUT D(M,N) 01443 IF D(M,N)0 GOTO 01450 01445 IF RND(1)<.97 GOTO 01447 01446 D(M,N)=7 01447 IF RND(1)<.97 GOTO 01450 01448 D(M,N)=8 01450 NEXT N 01460 NEXT M 01465 PRINT CHR$(4);"CLOSE DNG"+STR$(D) 01470 REM YEA START
I wrote
some code to create six dummy dungeons,
with walls at the edges, and emptyness elsewhere:
20000 FOR D2 = 1 TO 6 21063 PRINT CHR$(4);"OPEN DNG"+STR$(D2) 21064 PRINT CHR$(4);"WRITE DNG"+STR$(D2) 21065 FOR M=0 TO 25 21070 FOR N=0 TO 25 21072 XX = 0 21073 IF N=0 OR N=25 OR M=0 OR N=25 THEN XX = 1 21080 PRINT XX 21090 NEXT N 21100 NEXT M 21105 PRINT CHR$(4);"CLOSE DNG"+STR$(D2) 21200 NEXT D2 21300 RETURN
And here’s a bit of code I added right at the beginning to call
the dungeon creation routine:
00005 PRINT "CREATING DUNGEON FILES": GOSUB 20000