Akalabeth Main Game File
This is basically a diary of my attempts to understand the BASIC code, in the order I went through it.
Over time, I may break things off into more of a reference structure.
Adventure Shop
The very last routine is a good place to start. It's the adventure shop.
First we clear the screen and print the title.
60200 HOME : PRINT "WELCOME TO THE ADVENTURE SHOP"
Next we ask the player what item they want to buy (they just press a key).
60210 PRINT "WHICH ITEM SHALT THOU BUY ";: GET Q$: IF Q$ = "Q" THEN PRINT : PRINT "BYE": FOR Z = 1 TO 1000: NEXT : TEXT : HOME : RETURN
If "Q"
is pressed, we say BYE
, pause, clear the screen again and return.
Next, given what was pressed, we work out the corresponding index Z
into some array and the price P
.
We initially set Z
to -1
so we know if we've fallen through the if statements without matching anything.
60215 Z = - 1
60220 IF Q$ = "F" THEN PRINT "FOOD":Z = 0:P = 1
60221 IF Q$ = "R" THEN PRINT "RAPIER":Z = 1:P = 8
60222 IF Q$ = "A" THEN PRINT "AXE":Z = 2:P = 5
60223 IF Q$ = "S" THEN PRINT "SHIELD":Z = 3:P = 6
60224 IF Q$ = "B" THEN PRINT "BOW":Z = 4:P = 3
60225 IF Q$ = "M" THEN PRINT "AMULET":Z = 5:P = 15
If Z
is still -1
, we did match anything so display and message and try again:
60226 IF Z = - 1 THEN PRINT Q$: PRINT "I'M SORRY WE DON'T HAVE THAT.": GOTO 60210
Next we check if the player's class allows them to have a rapier or bow. Class is stored in PT$
and M
corresponds to a Mage.
60227 IF Q$ = "R" AND PT$ = "M" THEN PRINT "I'M SORRY MAGES": PRINT "CAN'T USE THAT!": GOTO 60210
60228 IF Q$ = "B" AND PT$ = "M" THEN PRINT "I'M SORRY MAGES": PRINT "CAN'T USE THAT!": GOTO 60210
Next we check if the player can afford the price P
. C(5)
seems to be the amount of gold the player has.
60230 IF C(5) - P < 0 THEN PRINT "M'LORD THOU CAN NOT AFFORD THAT ITEM.": GOTO 60210
PW
seams to be an array of how many items of each type the player has. Normally buying an item increments its count by 1 but food is incremented by 10 so in that case we first increment it by 9 before the general case increments it by another one. We also subtract the price P
from C(5)
.
60235 IF Z = 0 THEN PW(Z) = PW(Z) + 9
60236 PW(Z) = PW(Z) + 1:C(5) = C(5) - P
Finally, we update the display with the new counts:
60237 VTAB (10): HTAB (16): PRINT C(5);" "
60240 VTAB (5 + Z): HTAB (25 - LEN ( STR$ (PW(Z)))): PRINT PW(Z);: HTAB (1): VTAB (14): PRINT
and go back to asking if they want to buy anything else:
60250 GOTO 60210
So, in summary:
C(5)
seems to contain the amount of gold the player hasPW()
seems to contain the count of various items the player has
The index of PW()
seems to range over:
0
= FOOD1
= RAPIER2
= AXE3
= SHIELD4
= BOW5
= AMULET
On the screen:
(16, 10)
seems to hold the amount of gold(5 + z, 25 - len(PW(z)))
holdsPW(z)
Stats and Inventory Display
The routine above the Adventure Shop seems to be for displaying stats and inventory.
60080 TEXT : HOME : PRINT : PRINT : PRINT " STAT'S WEAPONS": PRINT : FOR X = 0 TO 5: PRINT C$(X);C(X); TAB 24);"0-";W$(X): NEXT : POKE 34,12: HOME : POKE 35,15
This suggests that C$()
contains the name of the stats and C()
the value (which would make 5
correspond to gold (see above). It also suggests W$()
contains the names of the items.
The POKE 34,12
and POKE 35,15
set the top and bottom of the text window.
The TAB 24)
without opening parentheses is odd and may be a bug in my de-tokenizer.
We show the key to quit:
60081 VTAB (11): HTAB (18): PRINT "Q-QUIT"
If the amount of food is more than zero, we clear the hires screen (not sure why only if the amount of food is more than zero):
60082 IF PW(0) > 0 THEN CALL 62450
Next we display the item counts, as we saw in the shop code:
60085 FOR Z = 0 TO 5: VTAB (5 + Z): HTAB (25 - LEN ( STR$ (PW(Z)))): PRINT PW(Z);: NEXT
Next we print the PRICE
, DAMAGE
, and ITEM
headers:
60090 VTAB (17): HTAB (5): PRINT "PRICE";: HTAB (15): PRINT "DAMAGE";: HTAB (25): PRINT "ITEM"
The names of the items:
60100 FOR X = 0 TO 5: VTAB (19 + X): HTAB (25): PRINT W$(X): NEXT
And various hard-coded values:
60110 VTAB (19): HTAB (5): PRINT "1 FOR 10": HTAB (15): PRINT "N/A": VTAB (20): HTAB (5): PRINT "8": HTAB (15): PRINT "1-10": VTAB (21): HTAB (5): PRINT "5": HTAB (15): PRINT "1-5"
60120 VTAB (22): HTAB (5): PRINT "6": HTAB (15): PRINT "1": VTAB (23): HTAB (5): PRINT "3": HTAB (15): PRINT "1-4": VTAB (24): HTAB (5): PRINT "15": HTAB (15): PRINT "?????": HOME
60130 RETURN
So far:
C()
seems to contain the player stats (include5
= GOLD)C$()
seems to contain the names of the statsPW()
seems to contain the count of various items the player hasW$()
seems to contain the names of the items
Item Name Setup
The routine at 60070 sets up the item names, then GOSUBs to the stat/inventory display and adventure shop.
60070 DIM W$(5): DATA "FOOD","RAPIER","AXE","SHIELD","BOW AND ARROWS","MAGIC AMULET": FOR X = 0 TO 5: READ W$(X): NEXT
60075 GOSUB 60080: GOSUB 60200: RETURN
Note that 60080 and 60200 are GOSUB'd to elsewhere so this is not the only entry point to those routines.
Character Creation
The last (or actually first) of the 60000-series routines seems to be character creation and initialization.
First we ask for the player's lucky number which is put in LN
and will be used to generate a random ZZ
.
60000 TEXT : HOME : VTAB (5): INPUT "TYPE THY LUCKY NUMBER.....";Q$:LN = VAL (Q$)
Next we ask for the level of play LP
and repeat until the value is between 1 and 10:
60005 VTAB (7): INPUT "LEVEL OF PLAY (1-10)......";Q$:LP = INT ( VAL (Q$))
60006 IF LP < 1 OR LP > 10 THEN 60005
We generate a random ZZ
based on the player's lucky number LN
.
60010 ZZ = RND ( - ABS (LN))
Here are our stat names. Note they are padded to length 15:
60020 DATA "HIT POINTS.....","STRENGTH.......","DEXTERITY......","STAMINA........","WISDOM.........","GOLD..........."
Next we declare PW()
(item/weapon counts), declare and initialize C$()
(stats names) and declare C()
(stats values):
60025 DIM PW(5)
60030 DIM C$(5): FOR X = 0 TO 5: READ C$(X): NEXT
60040 DIM C(5)
Next we declare M$()
, ML%
and MZ%
:
60041 DIM M$(10),ML%(10,1),MZ%(10,1)
And initialize M$
with the monster names:
60042 DATA "SKELETON","THIEF","GIANT RAT","ORC","VIPER","CARRION CRAWLER","GREMLIN","MIMIC","DAEMON","BALROG"
60043 FOR X = 1 TO 10: READ M$(X): NEXT
Next we initialize the player stats:
60050 FOR X = 0 TO 5:C(X) = INT ( SQR ( RND (1)) * 21 + 4): NEXT X
And display them, asking the player to accept them:
60060 HOME : VTAB (8): FOR X = 0 TO 5: PRINT C$(X),C(X): NEXT : PRINT : PRINT "SHALT THOU PLAY WITH THESE QUALITIES?": HTAB (20): GET Q$: IF Q$ < > "Y" THEN 60050
Finally, we ask if they want to be a fighter or a mage (putting M
or F
in PT$
):
60061 VTAB (15): PRINT : PRINT "AND SHALT THOU BE A FIGHTER OR A MAGE?": HTAB (20): GET PT$
When we're done, we go to the routine at 60070 that sets up the item names and GOSUBs to the stat/inventory display and adventure shop. The RETURN in 60075 will effectively act as the RETURN from this routine.
60062 IF PT$ = "M" OR PT$ = "F" THEN 60070
Or, if the class selection was invalid, we ask again:
60063 GOTO 60061
The global variables we know so far:
C$()
player stat/quality names (padded to 15)C()
player stat/quality valuesW$()
weapon/item namesPW()
player weapon/item countsM$
monster namesML%
don't know yetMZ%
don't know yetPT$
player type/class (F
for Fighter,M
for Mage)LN
lucky number (used in generation ofZZ
)ZZ
don't know yet
Stats / Qualities
0
= HIT POINTS1
= STRENGTH2
= DEXTERITY3
= STAMINA4
= WISDOM5
= GOLD
Weapons/Items
0
= FOOD1
= RAPIER2
= AXE3
= SHIELD4
= BOW AND ARROWS5
= AMULET
Monsters
1
= SKELETON2
= THIEF3
= GIANT RAT4
= ORC5
= VIPER6
= CARRION CRAWLER7
= GREMLIN8
= MIMIC9
= DAEMON10
= BALROG
Weapon/Items Prices and Damage
(displayed on lines 19–24 and horizontal tabs at 5 and 15)
1 FOR 10 N/A
8 1-10
5 1-5
6 1
3 1-4
15 ?????
Subroutines
There are 15 GOSUB
s in Akalabeth.
They go to:
- 100 (two times)
- 200
- 500 (four times)
- 2000
- 4000
And the three we've already seen:
- 60000
- 60080 (three times)
- 60200 (two times)
There are only 8 RETURN
s. They are at:
- 190 (return for 100 although it's possible to start at 90)
- 490 (return for 200 although line 491 should form part of this section too)
- 590 (return for 500)
- 2090 (return for 2000)
- 4999 (return for 4000)
- 60075 (return for 60000)
- 60130 (return for 60080)
- 60210 (60200 actually goes until 60250 but that last line is a go to back to 60210 which returns if the player quits from the shop)
Other code sections not covered by this:
- 1000–1700
- 3087–3089
- 6000–6060 (player death)
- 7000–7990 (Lord British's Castle, including quest bestowal, quest hand-in and game completion)
Lord British's Castle
This code is lines 7000–7990. We get there from line 1515.
We clear the screen and go to text mode.
7000 HOME : TEXT : HOME
7001 CALL 62450
CALL 62450
clears the hires screen to black.
If the player has a name (in PN$
) they've been here before so we skip the next part.
7010 IF PN$ < > "" THEN 7500
However, if the player is new we welcome them.
Newcomer
7020 PRINT : PRINT : PRINT " WELCOME PEASANT INTO THE HALLS OF": PRINT "THE MIGHTY LORD BRITISH. HEREIN THOU MAYCHOOSE TO DARE BATTLE WITH THE EVIL": PRINT "CREATURES OF THE DEPTHS, FOR GREAT": PRINT "REWARD!"
And ask their name:
7030 PRINT : PRINT "WHAT IS THY NAME PEASANT ";: INPUT PN$
We next ask if they want grand adventure. If they don't answer "Y" we remove their name and GOTO 1090.
7040 PRINT "DOEST THOU WISH FOR GRAND ADVENTURE ? ";: GET Q$: IF Q$ < > "Y" THEN PRINT : PRINT "THEN LEAVE AND BEGONE!":PN$ = "": PRINT : PRINT " PRESS -SPACE- TO CONT.";: GET Q$: GOTO 1090
However, if they accept, we set them a task:
7045 PRINT
7050 PRINT : PRINT "GOOD! THOU SHALT TRY TO BECOME A ": PRINT "KNIGHT!!!": PRINT : PRINT "THY FIRST TASK IS TO GO INTO THE": PRINT "DUNGEONS AND TO RETURN ONLY AFTER": PRINT "KILLING A(N) ";:TASK = INT (C(4) / 3): PRINT M$(TASK)
TASK
is initially calculated from the player's WISDOM stat and indicates the monster to kill (equivalent to the index into M$
which gives us the monster's name).
At quest bestowal, the player gains 1 point on all stats (including GOLD). Again we exit with a GOTO 1090.
7060 PRINT : PRINT " GO NOW UPON THIS QUEST, AND MAY": PRINT "LADY LUCK BE FAIR UNTO YOU.....": PRINT ".....ALSO I, BRITISH, HAVE INCREASED": PRINT "EACH OF THY ATTRIBUTES BY ONE!"
7070 PRINT : PRINT " PRESS -SPACE- TO CONT.";: GET Q$: FOR X = 0 TO 5:C(X) = C(X) + 1: NEXT : HOME : GOTO 1090
Return
First we check if TASK
is still positive (indicating a quest was assigned but not completed).
If so, we remind the player of their task and send them back with a GOTO 1090.
7500 IF TASK > 0 THEN PRINT : PRINT : PRINT PN$;" WHY HAST THOU RETURNED?": PRINT "THOU MUST KILL A(N) ";M$(TASK): PRINT "GO NOW AND COMPLETE THY QUEST!": PRINT : PRINT " PRESS -SPACE- TO CONT.";: GET Q$: HOME : GOTO 1090
If ABS(TASK) = 10
then, at this point, TASK
must be -10
. If this is the case, the player has become a knight.
7510 PRINT : PRINT : PRINT : PRINT "AAHH!!.....";PN$: PRINT : PRINT "THOU HAST ACOMPLISHED THY QUEST!": IF ABS (TASK) = 10 THEN 7900
If the player hasn't done enough to become a knight, we give them a new quest (with TASK
+= 1).
We then GOTO 7060 (the last part of the initial quest bestowal) where stats are increased and we leave the castle (GOTO 1090):
7520 PRINT "UNFORTUNATELY, THIS IS NOT ENOUGH TO": PRINT "BECOME A KNIGHT.":TASK = ABS (TASK) + 1: PRINT : PRINT "NOW THOU MUST KILL A(N) ";M$(TASK)
7530 GOTO 7060
We can see from the above that a positive TASK
means "kill monster M$(TASK)
" whereas a negative TASK
means, "player has successfully killed monster M$(ABS(TASK))
".
Knighthood
Now, if the player has completed TASK 10 (the BALROG), they become a knight.
7900 TEXT : HOME : PRINT : PRINT : PRINT :PN$ = "LORD " + PN$: PRINT " ";PN$;","
7910 PRINT " THOU HAST PROVED THYSELF WORTHY": PRINT "OF KNIGHTHOOD, CONTINUE PLAY IF THOU": PRINT "DOTH WISH, BUT THOU HAST ACOMPLISHED": PRINT "THE MAIN OBJECTIVE OF THIS GAME..."
We skip if the level being played at was 10.
7920 IF LP = 10 THEN 7950
If the level wasn't 10, we suggest them trying at one higher level than they just played.
7930 PRINT : PRINT " NOW MAYBE THOU ART FOOLHEARTY": PRINT "ENOUGH TO TRY DIFFICULTY LEVEL ";LP + 1
We exit with a GOTO 7070 which still ups the stats but doesn't show the message saying that's what's being done.
7940 GOTO 7070
However, if the BALROG is killed on level 10, we reach here, with a suggestion to call California Pacific Computer to report the accomplishment.
7950 PRINT : PRINT "...CALL CALIFORNIA PACIFIC COMPUTER": PRINT "AT (415)-569-9126 TO REPORT THIS": PRINT "AMAZING FEAT!"
We still bump the stats on exit from the castle (although again, without mentioning it).
7990 GOTO 7070
So three new global variables are now known:
PN$
player nameLP
difficulty level being played atTASK
if positive, the monster the player is on a quest to kill, if negative thenABS(TASK)
gives the quest monster just killed
Player Death
Player death is dealt with in lines 6000–6060. 6000 is called from line 1093 if HIT POINTS (C(0)
) hits or drops below 0.
First we set the window width to 40 then print the death announcement.
6000 POKE 33,40: PRINT : PRINT : PRINT " WE MOURN THE PASSING OF"
If the player name is longer than 22 characters or there isn't a player name, we just call the player "THE PEASANT":
6005 IF LEN (PN$) > 22 THEN PN$ = ""
6010 IF PN$ = "" THEN PN$ = "THE PEASANT"
We then add " AND HIS COMPUTER" to the name.
6020 PN$ = PN$ + " AND HIS COMPUTER"
We centre the name:
6030 HTAB (20 - INT ( LEN (PN$) / 2)): PRINT PN$
And offer a resurrection via the ESC key:
6035 PRINT " TO INVOKE A MIRACLE OF RESSURECTION"
6040 PRINT " <HIT ESC KEY>";
We loop until we get the ESC key:
6050 IF PEEK ( - 16384) = 155 THEN 1
6060 GOTO 6050
On ESC we go back to the start of the program on line 1.
So far, I've noticed the following typos:
- ACOMPLISHED (line 7510)
- RESSURECTION (line 6035)
Commands
The command loop and command handling is done in lines 1000–1700.
First we go to the bottom of the screen and print the prompt. CALL -868
clears the line to the right of the cursor:
1000 VTAB (24): PRINT "COMMAND? ";: CALL - 868
Then we loop until we get a key press:
1001 X = PEEK ( - 16384): IF X < 128 THEN 1001
We see how much memory is available (not yet sure why):
1002 Q = FRE (0)
and reset the keyboard:
1010 POKE - 16368,0
Command Dispatch
Many commands are handled differently depending on whether we're outside or inside.
These commands are:
CR
(141) NORTH 1100 or FORWARD 1150RIGHT ARROW
(149) EAST 1200 or TURN RIGHT 1250LEFT ARROW
(136) WEST 1300 or TURN LEFT 1350/
(175) SOUTH 1400 or TURN AROUND 1450X
(216) GO 1500 or 1550A
or27
(193 or 155) ATTACK 1600 or 1650
SGN(INOUT)
will be 0 if we're outside and 1 if we're inside.
1030 IF X = 141 THEN ON SGN (INOUT) + 1 GOTO 1100,1150
1040 IF X = 149 THEN ON SGN (INOUT) + 1 GOTO 1200,1250
1050 IF X = 136 THEN ON SGN (INOUT) + 1 GOTO 1300,1350
1060 IF X = 175 THEN ON SGN (INOUT) + 1 GOTO 1400,1450
1070 IF X = 216 THEN ON SGN (INOUT) + 1 GOTO 1500,1550
1080 IF X = 193 OR X = 155 THEN ON SGN (INOUT) + 1 GOTO 1600,1650
SPACE
passes:
1081 IF X = 160 THEN PRINT "PASS": GOTO 1090
S
shows stats/inventory:
1085 IF X = 211 THEN 1700
P
toggles PAUSE ON/OFF which we store as PA
(1 for ON, 0 for OFF):
1086 IF X = 208 THEN IF PA = 1 THEN PA = 0: PRINT "PAUSE OFF": GOTO 1000
1087 IF X = 208 THEN IF PA = 0 THEN PA = 1: PRINT "PAUSE ON": GOTO 1000
Note that PAUSE ON/OFF doesn't call the Post-Command code (so doesn't reduce food, etc)
Any other keys and we print "HUH?" and go back to the start of this routine:
1089 PRINT "HUH?": GOTO 1000
Post-Command
Once the command is executed we normally GOTO line 1090.
First we reduce food by 1 (or 0.1 if we're inside). If FOOD hits 0 then we set HIT POINTS to 0 too, announce the player has starved and go to line 1093 (which in turn will send us to the Death routine at 6000)
1090 PW(0) = PW(0) - 1 + SGN (INOUT) * .9: IF PW(0) < 0 THEN C(0) = 0: PRINT : PRINT "YOU HAVE STARVED!!!!!": GOTO 1093
If we haven't starved, though, we update our FOOD / HIT POINTS / GOLD display:
1091 POKE 33,40: VTAB (22): HTAB (30): PRINT "FOOD=";PW(0);: CALL - 868: VTAB (23): HTAB (30): PRINT "H.P.=";C(0);: CALL - 868: VTAB (24): HTAB (30): PRINT "GOLD=";C(5);: CALL - 868: POKE 33,29: HTAB (1)
We round our FOOD to one decimal place.
1092 PW(0) = INT (PW(0) * 10) / 10
If HIT POINTS are zero or less, we go to the Death handling routine:
1093 IF C(0) < = 0 THEN 6000
If we're in a dungeon (IN
is same as INOUT
) we move the monsters around. If this has resulted in HIT POINTS being zero or less, we go back to the previous line to go to the Death handling routine.
1095 IF IN > 0 THEN GOSUB 4000: IF C(0) < = 0 THEN 1093
The following is the same as line 1091. I guess we do it again because the monster movement in the previous line may have changed some values.
1096 POKE 33,40: VTAB (22): HTAB (30): PRINT "FOOD=";PW(0);: CALL - 868: VTAB (23): HTAB (30): PRINT "H.P.=";C(0);: CALL - 868: VTAB (24): HTAB (30): PRINT "GOLD=";C(5);: CALL - 868: POKE 33,29: HTAB (1)
Not yet sure what GOSUB 100 or GOSUB 200 do. Perhaps draw.
1097 IF INOUT = 0 THEN GOSUB 100: GOTO 1000
1098 IF INOUT > 0 THEN GOSUB 200: GOTO 1000
NORTH (Outside)
If there's not a mountain in the way, move north.
1100 PRINT "NORTH": IF TER%(TX,TY - 1) = 1 THEN PRINT "YOU CAN'T PASS THE MOUNTAINS": GOTO 1090
1110 TY = TY - 1: GOTO 1090
FORWARD (Inside)
If it's possible to move forward, make the move:
1150 IF DNG%(PX + DX,PY + DY) < > 1 AND DNG%(PX + DX,PY + DY) < 10 THEN PX = PX + DX:PY = PY + DY
1155 PRINT "FORWARD"
First we check if the location the player moved to has a TRAP. If it does, the player loses a random amount of HIT POINTS based on dungeon level, MR
is set to 1 (don't know what that is yet), the dungeon level is incremented and we GOSUB to 500 to generate a new dudgeon map. Note that INOUT
and IN
are the same variable.
1160 IF DNG%(PX,PY) = 2 THEN PRINT "AAARRRGGGHHH!!! A TRAP!":C(0) = C(0) - INT ( RND (1) * INOUT + 3):MR = 1:INOUT = INOUT + 1: PRINT "FALLING TO LEVEL ";IN: GOSUB 500: GOTO 1090
If there's a chest where the player is located, we take the chest, randomly give an amount of GOLD based on dungeon level as well as an item.
1165 Z = 0
1170 IF DNG%(PX,PY) = 5 THEN DNG%(PX,PY) = 0: PRINT "GOLD!!!!!":Z = INT ( RND (1) * 5 * INOUT + INOUT): PRINT Z;"-PIECES OF EIGHT":C(5) = C(5) + Z
1175 IF Z > 0 THEN Z = INT ( RND (1) * 6): PRINT "AND A ";W$(Z):PW(Z) = PW(Z) + 1: GOTO 1090
1190 GOTO 1090
EAST (Outside)
Moving EAST is the same as moving NORTH but we increment TX
instead of decrementing TY
.
1200 PRINT "EAST": IF TER%(TX + 1,TY) = 1 THEN PRINT "YOU CAN'T PASS THE MOUNTAINS": GOTO 1090
1210 TX = TX + 1: GOTO 1090
TURN RIGHT (Inside)
Rotate the player:
1250 PRINT "TURN RIGHT"
1255 IF DX < > 0 THEN DY = DX:DX = 0: GOTO 1090
1260 DX = - DY:DY = 0: GOTO 1090
WEST (Outside)
Moving WEST is the same as moving EAST but we decrement TX
instead of incrementing it.
1300 PRINT "WEST": IF TER%(TX - 1,TY) = 1 THEN PRINT "YOU CAN'T PASS THE MOUNTAINS": GOTO 1090
1310 TX = TX - 1: GOTO 1090
TURN LEFT (Inside)
Rotate the player:
1350 PRINT "TURN LEFT"
1355 IF DX < > 0 THEN DY = - DX:DX = 0: GOTO 1090
1360 DX = DY:DY = 0: GOTO 1090
SOUTH (Outside)
Moving SOUTH is the same as moving NORTH but we increment TY
instead of decrementing it.
1400 PRINT "SOUTH": IF TER%(TX,TY + 1) = 1 THEN PRINT "YOU CAN'T PASS THE MOUNTAINS": GOTO 1090
1410 TY = TY + 1: GOTO 1090
TURN AROUND (Inside)
Flip the direction of the player:
1450 PRINT "TURN AROUND":DX = - DX:DY = - DY: GOTO 1090
GO (Outside)
If it's a town, enter the shop:
1500 IF TE%(TX,TY) = 3 THEN GOSUB 60080: GOSUB 60200: GOTO 1090
If it's a dungeon and INOUT
is 0 (not sure when it wouldn't be at this point), enter the dungeon. Set INOUT
to 1, randomize the dungeon map (GOSUB 500) then initialize the player location and direction.
1510 IF TE%(TX,TY) = 4 AND INOUT = 0 THEN PRINT "GO DUNGEON": PRINT "PLEASE WAIT ":INOUT = 1: GOSUB 500:DX = 1:DY = 0:PX = 1:PY = 1: GOTO 1090
If it's Lord British's castle, enter:
1515 IF TE%(TX,TY) = 5 THEN 7000
Otherwise print "HUH?" and loop back (although don't do post-command):
1520 PRINT "HUH?": GOTO 1000
GO (Inside)
Skip ahead if it's not a ladder down (not yet sure distinction between 7 and 9):
1550 IF DNG%(PX,PY) < > 7 AND DNG%(PX,PY) < > 9 THEN 1580
Otherwise say we're going down a level, increment INOUT
and randomize the next level (GOSUB 500):
1555 PRINT "GO DOWN TO LEVEL ";INOUT + 1
1560 INOUT = INOUT + 1: GOSUB 500:MR = 1: GOTO 1090
So here's where we go if it's not a down ladder (7 or 9). If it's also not an up ladder (8), print "HUH?"
1580 IF DNG%(PX,PY) < > 8 THEN PRINT "HUH?": GOTO 1090
But if it's an up ladder...
If the player was already at the first level, print that they're leaving and change IN
(INOUT
) to 0:
1581 IF IN = 1 THEN PRINT "LEAVE DUNGEON":IN = 0: GOTO 1586
Otherwise say they're going up a level, decrement INOUT
and randomize the dungeon level (GOSUB 500). Also set MR
to 1 (not yet sure what that's for).
1584 PRINT "GO UP TO LEVEL ";INOUT - 1
1585 INOUT = INOUT - 1: GOSUB 500:MR = 1
If we've left the dungeon, increment the HIT POINTS (C(0)
) by LK
(which is incremented on monster kill below).
1586 IF IN = 0 THEN PRINT "THOU HAST GAINED": PRINT LK;" HIT POINTS":C(0) = C(0) + LK:LK = 0
1587 GOTO 1090
ATTACK (Outside)
Attack is a no-op outside (although we run the post-command to reduce food, etc)
1600 GOTO 1090
ATTACK (Inside)
See separate analysis below.
STATISTICS / INVENTORY
Call the stats/inventory routine then prompt to hit CR to go back.
1700 GOSUB 60080: HOME : PRINT "PRESS -CR- TO CONTINUE";: INPUT Q$: TEXT : HOME : GOTO 1090
What we learnt from the command handling routines above:
TER%()
contains the terrain type at an outside locationDNG%()
contains what is at a particular location in a dungeonTX
andTY
are the player's location outsidePX
andPY
are the player's location insideDX
andDY
are howPX
andPY
will change if the player steps forward (i.e. they give the direction the player is facing in the dungeon)
Values of TER%()
are:
1
= MOUNTAINS3
= TOWN (SHOP)4
= DUNGEON5
= LORD BRITISH'S CASTLE
Values of DNG%()
are:
1
= WALL?2
= TRAP5
= CHEST7
or9
= DOWN LADDER8
= UP LADDER
Attack
First we initialize MN
(the monster being attacked) and DAM
(the damage being done) to 0. Then we ask what weapon the player wants to use:
1650 MN = 0:DAM = 0: PRINT "ATTACK": PRINT "WHICH WEAPON ";: GET Q$
Then, depending on what they select, we set the appropriate damage DAM
. But if the player does not own the item, say so and go back to asking which weapon:
1651 IF Q$ = "R" THEN DAM = 10: PRINT "RAPIER": IF PW(1) < 1 THEN PRINT "NOT OWNED": GOTO 1650
1652 IF Q$ = "A" THEN DAM = 5: PRINT "AXE": IF PW(2) < 1 THEN PRINT "NOT OWNED": GOTO 1650
1653 IF Q$ = "S" THEN DAM = 1: PRINT "SHIELD": IF PW(3) < 1 THEN PRINT "NOT OWNED": GOTO 1650
1654 IF Q$ = "B" THEN DAM = 4: PRINT "BOW": IF PW(4) < 1 THEN PRINT "NOT OWNED": GOTO 1650
If MAGIC AMULET has been selected, skip down to line 1680:
1655 IF Q$ = "M" THEN PRINT "MAGIC AMULET": GOTO 1680
Check player class ($PT
) and, if player is a mage and they picked a BOW or RAPIER, tell them they can't use it and go back to asking which weapon again:
1656 IF Q$ = "B" AND PT$ = "M" THEN PRINT "MAGES CAN'T USE BOWS!": GOTO 1650
1657 IF Q$ = "R" AND PT$ = "M" THEN PRINT "MAGES CAN'T USE RAPIERS!": GOTO 1650
If none of the weapons matched, DAM
will still be 0 and we'll assume HANDS.
1659 IF DAM = 0 THEN PRINT "HANDS"
If it's an AXE or BOW, we skip ahead to line 1670:
1660 IF DAM = 5 OR DAM = 4 THEN 1670
Melee Weapon
This next line is unique to Melee Weapons. We work out which monster type is in the direction the player is facing. (Looks like DN%()
might contain 10 x the monster level/type at that location).
1661 MN = DN%(PX + DX,PY + DY) / 10:MN = INT (MN)
Common Attack Code
But from this point on, the code is shared with Ranged weapons too...
If there are no monsters, MN
will be < 1 (not sure if it will ever be negative). The hit chance is based on DEXTERITY, the monster level and the dungeon level.
1662 IF MN < 1 OR C(2) - RND (1) * 25 < MN + INOUT THEN PRINT "YOU MISSED": GOTO 1668
If we hit, we work out actual damage (based on DAM
and STRENGTH) and put it back in DAM
and reduce the HIT POINTS accordingly. MZ%(MN,1)
must contain the HIT POINTS of the monster of type MN
.
1663 PRINT "HIT!!! ":DAM = ( RND (1) * DAM + C(1) / 5):MZ%(MN,1) = MZ%(MN,1) - DAM
We display the monster's new HIT POINTS:
1664 PRINT M$(MN);"'S HIT POINTS=";MZ%(MN,1)
If that has dropped to 0 or less, it's a kill. The reward is an amount of gold equal to the monster level + dungeon level.
1665 IF MZ%(MN,1) < 1 THEN PRINT "THOU HAST KILLED A ";M$(MN): PRINT "THOU SHALT RECEIVE":DA = INT (MN + IN): PRINT DA;" PIECES OF EIGHT"
We add the gold to the player inventory. We then remove the monster from the DNG%
map and set MZ%(MN,0)
to 0. ML%(MN,0)
must contain the X-cordinate of the monster and ML%(MN,1)
the Y-coordinate.
1666 IF MZ%(MN,1) < 1 THEN C(5) = INT (C(5) + DA):DNG%(ML%(MN,0),ML%(MN,1)) = DNG%(ML%(MN,0),ML%(MN,1)) - 10 * MN:MZ%(MN,0) = 0
LK
is the number of HIT POINTS the player will gain when they leave the dungeon. We increment LK
by half the monster level MN
times the dungeon level IN
. If the monster was the quest monster, we mark the quest done (by negating TASK
).
1667 LK = LK + INT (MN * IN / 2): IF MN = TASK THEN TASK = - TASK
Regardless of hit or miss, we end up here...
If PAUSE is ON, we wait for a CR to continue:
1668 IF PA = 1 THEN PRINT "-CR- TO CONT. ";: INPUT Q$
And we're done.
1669 GOTO 1090
Axe or Bow
If it's an AXE, we give the player the option to THROW (ranged) or SWING (melee). If they SWING, we just go back to the Melee Weapon section above.
1670 IF DAM = 5 THEN PRINT "TO THROW OR SWING:";: GET Q$: IF Q$ < > "T" THEN PRINT "SWING": GOTO 1661
If it's an AXE (and we've already established at this point we're throwing it) we reduce the count by one:
1671 IF DAM = 5 THEN PRINT "THROW":PW(2) = PW(2) - 1
Ranged Attack
We iterate over the 5 squares in the direction the player is facing. If we go off the map (a coordinate hit 0 or 10), we go back to Common Attack Code with MN
still 0.
If DNG%(x, y)
has a monster, it will contain monster level * 10
.
1672 FOR Y = 1 TO 5: IF PX + DX * Y < 1 OR PX + DX * Y > 9 OR PY + DY * Y > 9 OR PY + DY * Y < 0 THEN 1662
1673 MN = DNG%(PX + DX * Y,PY + DY * Y):MN = INT (MN / 10): IF MN > 0 THEN 1662
1674 NEXT : GOTO 1662
Magic Amulet
If there's no magic amulet in the player's possession, we go back to asking them which weapon to use.
1680 IF PW(5) < 1 THEN PRINT "NONE OWNED": GOTO 1650
If the player is a fighter, the amulet's action is random:
1681 IF PT$ = "F" THEN Q = INT ( RND (1) * 4 + 1): GOTO 1685
If the player is a mage, they get to pick the amulet's action:
1682 PRINT "1-LADDER-UP","2-LADDER-DN": PRINT "3-KILL","4-BAD??": PRINT "CHOICE ";: GET Q$:Q = VAL (Q$): PRINT Q: IF Q < 1 OR Q > 4 THEN 1682
But in that case, there's a 25% chance the amulet will be on its last charge (and will be removed from the inventory).
1683 IF RND (1) > .75 THEN PRINT "LAST CHARGE ON THIS AMULET!":PW(5) = PW(5) - 1
Regardless of fighter or mage, we end up here.
1685 ON Q GOTO 1686,1690,1691,1692
Q = 1. Make an up-ladder:
1686 PRINT "LADDER UP":DNG%(PX,PY) = 8: GOTO 1090
Q = 2. Make a down-ladder:
1690 PRINT "LADDER DOWN":DNG%(PX,PY) = 7: GOTO 1090
Q = 3. A magic attack. Set the damage to 10 + dungeon level and go to Ranged Attack.
1691 PRINT "MAGIC ATTACK":DAM = 10 + INOUT: GOTO 1672
Q = 4. Random badness.
1692 ON INT ( RND (1) * 3 + 1) GOTO 1693,1695,1697
a) get turned into a TOAD and have STRENGTH, DEXTERITY, STAMINA and WISDOM all turned to 3.
1693 PRINT "YOU HAVE BEEN TURNED": PRINT "INTO A TOAD!"
1694 FOR Z2 = 1 TO 4:C(Z2) = 3: NEXT Z2: GOTO 1090
b) get turned into a LIZARD MAN and have HIT POINTS, STRENGTH, DEXTERITY, STAMINA and WISDOM all multiplied by 2.5:
1695 PRINT "YOU HAVE BEEN TURNED": PRINT "INTO A LIZARD MAN": FOR Y = 0 TO 4:C(Y) = INT (C(Y) * 2.5): NEXT : GOTO 1090
c) backfire and halve HIT POINTS
1697 PRINT "BACKFIRE":C(0) = C(0) / 2: GOTO 1090
More global variables, we've learnt about in the attack code:
DNG%()
(orDN%()
) stores the monster type/level in the tens column. So37
would mean a GIANT RAT (3) AND a DOWN LADDER (7).MZ%(MN,0)
unsure but is set to 0 when monster killed.MZ%(MN,1)
stores the HIT POINTS of the monster of typeMN
at this dungeon levelML%(MN,0), ML%(MN,1)
location of monster of typeMN
at this dungeon levelLK
hit points to gain on leaving dungeon
Generating a Dungeon Level
Dungeon level generation is a routine from lines 500–590 (with a call out to 2000).
The dungeon level information is primarily stored in DNG%()
(sometimes referred to as just DN%()
) which is an 11 x 11 matrix.
We generate a ZZ but I still don't know what that's for.
500 ZZ = RND ( - ABS (LN) - TX * 10 - TY * 1000 + INOUT * 31.4)
Next we zero out the inside of DNG%()
:
501 FOR X = 1 TO 9: FOR Y = 1 TO 9:DNG%(X,Y) = 0: NEXT : NEXT
Then we set the outsides to WALLS (1
):
510 FOR X = 0 TO 10:DNG%(X,0) = 1:DNG%(X,10) = 1:DNG%(0,X) = 1:DNG%(10,X) = 1: NEXT
Then we draw rows of walls on the even coordinates.
520 FOR X = 2 TO 8 STEP 2: FOR Y = 1 TO 9:DNG%(X,Y) = 1:DNG(Y,X) = 1: NEXT : NEXT
The result at this point is:
11111111111
10101010101
11111111111
10101010101
11111111111
10101010101
11111111111
10101010101
11111111111
10101010101
11111111111
Then, for each element of those rows of inside walls, we randomly replace the wall with a TRAP (2), ? (3), ? (4), CHEST (5) or DOWN LADDER? (9).
530 FOR X = 2 TO 8 STEP 2: FOR Y = 1 TO 9 STEP 2
540 IF RND (1) > .95 THEN DNG%(X,Y) = 2
541 IF RND (1) > .95 THEN DNG%(Y,X) = 2
542 IF RND (1) > .6 THEN DNG%(Y,X) = 3
543 IF RND (1) > .6 THEN DNG%(X,Y) = 3
544 IF RND (1) > .6 THEN DNG%(X,Y) = 4
545 IF RND (1) > .6 THEN DNG%(Y,X) = 4
546 IF RND (1) > .97 THEN DNG%(Y,X) = 9
547 IF RND (1) > .97 THEN DNG%(X,Y) = 9
548 IF RND (1) > .94 THEN DNG%(X,Y) = 5
549 IF RND (1) > .94 THEN DNG%(Y,X) = 5
568 NEXT : NEXT
(2,1) is always cleared. If the player is on an even level, (7,3) is a DOWN LADDER and (3, 7) is an UP LADDER. If the player is on an odd level, (7,3) is an UP LADDER and (3,7) is a DOWN LADDER (of course).
569 DNG%(2,1) = 0: IF INOUT / 2 = INT (INOUT / 2) THEN DNG%(7,3) = 7:DNG%(3,7) = 8
570 IF INOUT / 2 < > INT (INOUT / 2) THEN DNG%(7,3) = 8:DNG%(3,7) = 7
If the player is on the first level of the dungeon (closest to surface) then (1,1) is an UP LADDER instead of (7,3). Presumably this is where the player starts.
580 IF INOUT = 1 THEN DNG%(1,1) = 8:DNG%(7,3) = 0
We call out to 2000 to place monsters.
585 GOSUB 2000
And we're done.
590 RETURN
Monster Placement
We initialize NM
(the number of monsters created for this dungeon level) to 0 and loop 10 times.
2000 NM = 0: FOR X = 1 TO 10
X
, our loop variable will be the monster level/type.
We set MZ%(X,0)
for the monster to 0 and the number of HIT POINTS it has (MZ%(X,1)
) to 3 + monster level + dungeon level (although oddly we override this once we actually place the monster below).
2005 MZ%(X,0) = 0:MZ%(X,1) = X + 3 + INOUT
If the monster is more than two levels higher than the dungeon level, we skip it and continue the loop. Even if the monster in consideration is an okay level, there's still only a 40% chance we have one.
2010 IF X - 2 > INO OR RND (1) > .4 THEN 2090
Next we pick a location for the monster.
2020 ML%(X,0) = INT ( RND (1) * 9 + 1):ML%(X,1) = INT ( RND (1) * 9 + 1)
If it's already occupied (not just by a monster but anything, like a wall) we go back and pick a new location:
2030 IF DNG%(ML%(X,0),ML%(X,1)) < > 0 THEN 2020
If it's the same location as the player, we go back and pick a new location:
2040 IF ML%(X,0) = PX AND ML%(X,1) = PY THEN 2020
Now that we've established we definitely have a monster, we place it in DNG%()
(by putting the monster type/level in the tens column):
2050 DNG%(ML%(X,0),ML%(X,1)) = X * 10
We set MZ%(X,0) to 1 for the monster and increment our monster count for the dungeon level.
2051 MZ%(X,0) = 1
2052 NM = NM + 1
Finally we set the HIT POINTS to be 2 * (monster level + dungeon level * player difficulty level).
2055 MZ%(X,1) = X * 2 + IN * 2 * LP
And we loop back for the next monster type before returning.
2090 NEXT : RETURN
Monster Movement
We iterate through the 10 types of monsters and check if any of them exist at this level of the dungeon. If not, we skip this entire routine.
4000 FOR MM = 1 TO 10: IF MZ%(MM,0) = 0 THEN 4999
So, there's one at level MM
. It's location is currently given by ML%(MM,0)
and ML%(MM,1)
.
First we calculate the distant between the monster and the player:
4010 RA = SQR ((PX - ML%(MM,0)) ; 2 + (PY - ML%(MM,1)) ; 2)
(is ; 2 really how you square? Is that a de-tokenization bug?)
If the HIT POINTS is less than the dungeon level * the player difficulty level, skip to 4030:
4011 IF MZ%(MM,1) < IN * LP THEN 4030
If the distance is < 1.3 (i.e. it's adjacent), then skip to Monster Attack:
4020 IF RA < 1.3 THEN 4500
If it's a MIMIC within a distance of 3, it won't do anything and skip to the next monster.
4025 IF MM = 8 AND RA < 3 THEN 4999
Calculate the direction the player is in from the monster:
4030 X1 = SGN (PX - ML%(MM,0)):Y1 = SGN (PY - ML%(MM,1))
If the HIT POINTS is less than the dungeon level * the player difficulty level, flip the directions (i.e. plan to move away from the player):
4031 IF MZ%(MM,1) < IN * LP THEN X1 = - X1:Y1 = - Y1
If the the player is due east or west (i.e. the change in Y
is 0), skip the next part.
4035 IF Y1 = 0 THEN 4045
(the player isn't due east or west so we're going to need to move north or south) Look at what's in the north/south direction of desired travel. If it's a WALL (1) or another monster or TRAP (2), skip to 4045.
4040 D = DNG%(ML%(MM,0),(ML%(MM,1) + Y1 + .5)): IF D = 1 OR D > 9 OR D = 2 THEN 4045
But if the monster can move in that north/south direction, set X1
to 0 (as it won't be moving east/west this turn).
4042 X1 = 0: GOTO 4050
We get here if the player is due east/west or if something it blocking the monster from going north/south. As the monster won't be moving north/south this turn, we set Y1
to 0. If the player is still due north/south, though, we skip the next line and go to 4050.
4045 Y1 = 0: IF X1 = 0 THEN 4050
The monster desires to go east/west. It looks to see what's in the desired direction. If it's a WALL (1) or another monster or a TRAP (2), then set X1
to 0 (as it can't move in that direction) and go to 4081.
4046 D = DN%((ML%(MM,0) + X1 + .5),ML%(MM,1)): IF D = 1 OR D > 9 OR D = 2 THEN X1 = 0: GOTO 4081
We remove the monster from its existing place in DNG%()
:
4050 DNG%(ML%(MM,0),ML%(MM,1)) = DNG%(ML%(MM,0),ML%(MM,1)) - 10 * MM
If the move would place the monster on the place as the player, we skip the rest and go to the next monster (note they would leave it removed it from DNG%()
, though):
4055 IF ML%(MM,0) + X1 = PX AND ML%(MM,1) + Y1 = PY THEN 4999
Otherwise, we move the monster:
4060 ML%(MM,0) = ML%(MM,0) + X1:ML%(MM,1) = ML%(MM,1) + Y1
and update the DNG%()
to include it at its new location:
4080 DNG%(ML%(MM,0),ML%(MM,1)) = (DNG%(ML%(MM,0),ML%(MM,1)) + 10 * MM + .5)
(not sure what the + .5
here and above is for).
If we get here and there was a movement, we skip to the next monster:
4081 IF X1 < > 0 OR Y1 < > 0 THEN 4999
If the HIT POINTS is less than the dungeon level * player difficulty level and the player is adjacent to the monster, go to Monster Attack:
4082 IF MZ%(MM,1) < IN * LP AND RA < 1.3 THEN 4500
Otherwise, if the HIT POINTS is less than the dungeon level * player difficulty level, the HIT POINTS rise by monster level + dungeon level (healing):
4083 IF MZ%(MM,1) < IN * LP THEN MZ%(MM,1) = MZ%(MM,1) + MM + IN
And we move to the next monster:
4499 GOTO 4999
Monster Attack
If the monster is a THIEF or a GREMLIN, it steals rather than attacks to we skip to Monster Theft:
4500 IF MM = 2 OR MM = 7 THEN 4600
We tell the player what they are being attacked by:
4509 PRINT "YOU ARE BEING ATTACKED": PRINT "BY A ";M$(MM)
We determine whether they hit based on whether the player has a shield, the player stamina, the monster level and the dungeon level:
4510 IF RND (1) * 20 - SGN (PW(3)) - C(3) + MM + IN < 0 THEN PRINT "MISSED": GOTO 4525
If it's a hit, the HIT POINTS lost is base on monster level + dungeon level:
4520 PRINT "HIT":C(0) = C(0) - INT ( RND (1) * MM + IN)
If PAUSE is ON we wait for the player to hit CR:
4525 IF PA = 1 THEN PRINT "-CR- TO CONT. ";: INPUT Q$
Then go to the next monster to move:
4530 GOTO 4999
Monster Theft
There is a 50% chance the thief or gremlin will just attack anyway:
4600 IF RND (1) < .5 THEN 4509
But if they steal...
If they are a gremlin, half the food will be gone:
4610 IF MM = 7 THEN PW(0) = INT (PW(0) / 2): PRINT "A GREMLIN STOLE SOME FOOD": GOTO 4525
Otherwise (i.e. they are a thief), one of the possessions the player has will be stolen at random:
4620 ZZ = INT ( RND (1) * 6): IF PW(ZZ) < 1 THEN 4620
4630 PRINT "A THIEF STOLE A ";W$(ZZ):PW(ZZ) = PW(ZZ) - 1: GOTO 4525
And finally we go to the next monster then return:
4999 NEXT : RETURN
Drawing Terrain
The terrain drawing routine appears to be lines 100–190.
As we've already seen, the terrain is stored in TER%()
and we know from the outside movement commands that TX
and TY
store the location of the player.
We further more know that the values of TER%()
are:
1
= MOUNTAINS3
= TOWN (SHOP)4
= DUNGEON5
= LORD BRITISH'S CASTLE
I presume therefore that 2
must be the little squares (never knew what they were supposed to be).
We switch to hires graphics mode and loop over [-1, 0, 1] x [-1, 0, 1] to draw the terrain tile the player is on and the eight surrounding.
100 HGR : FOR Y = - 1 TO 1: FOR X = - 1 TO 1
We next draw the crosshairs indicated the player's location:
105 HPLOT 138,75 TO 142,75: HPLOT 140,73 TO 140,77
We retrieve the terrain type and put it in ZZ
then work out the top left corner of the tile to draw. The tiles are 50 x 50 and start at (65, 0).
110 ZZ = TER%(TX + X,TY + Y):X1 = 65 + (X + 1) * 50:Y1 = (Y + 1) * 50
"Little Square" (2)
120 IF ZZ = 2 THEN HPLOT X1 + 20,Y1 + 20 TO X1 + 30,Y1 + 20 TO X1 + 30,Y1 + 30 TO X1 + 20,Y1 + 30 TO X1 + 20,Y1 + 20
Town/Shop (3)
130 IF ZZ = 3 THEN HPLOT X1 + 10,Y1 + 10 TO X1 + 20,Y1 + 10 TO X1 + 20,Y1 + 40 TO X1 + 10,Y1 + 40 TO X1 + 10,Y1 + 30 TO X1 + 40,Y1 + 30 TO X1 + 40,Y1 + 40 TO X1 + 30,Y1 + 40 TO X1 + 30,Y1 + 10 TO X1 + 40,Y1 + 10 TO X1 + 40,Y1 + 20 TO X1 + 10,Y1 + 20 TO X1 + 10,Y1 + 10
Dungeon (4)
140 IF ZZ = 4 THEN HPLOT X1 + 20,Y1 + 20 TO X1 + 30,Y1 + 30: HPLOT X1 + 20,Y1 + 30 TO X1 + 30,Y1 + 20
Lord British's Castle (5)
150 IF ZZ = 5 THEN HPLOT X1,Y1 TO X1 + 50,Y1 TO X1 + 50,Y1 + 50 TO X1,Y1 + 50 TO X1,Y1: HPLOT X1 + 10,Y1 + 10 TO X1 + 10,Y1 + 40 TO X1 + 40,Y1 + 40 TO X1 + 40,Y1 + 10 TO X1 + 10,Y1 + 10 TO X1 + 40,Y1 + 40: HPLOT X1 + 10,Y1 + 40 TO X1 + 40,Y1 + 10
Mountains (1)
160 IF ZZ = 1 THEN HPLOT X1 + 10,Y1 + 50 TO X1 + 10,Y1 + 40 TO X1 + 20,Y1 + 30 TO X1 + 40,Y1 + 30 TO X1 + 40,Y1 + 50: HPLOT X1,Y1 + 10 TO X1 + 10,Y1 + 10: HPLOT X1 + 50,Y1 + 10 TO X1 + 40,Y1 + 10: HPLOT X1,Y1 + 40 TO X1 + 10,Y1 + 40: HPLOT X1 + 40,Y1 + 40 TO X1 + 50,Y1 + 40
170 IF ZZ = 1 THEN HPLOT X1 + 10,Y1 TO X1 + 10,Y1 + 20 TO X1 + 20,Y1 + 20 TO X1 + 20,Y1 + 30 TO X1 + 30,Y1 + 30 TO X1 + 30,Y1 + 10 TO X1 + 40,Y1 + 10 TO X1 + 40,Y1
Then we loop back for the next tile and then return when all 9 are done:
190 NEXT : NEXT : RETURN
Initialization
It's about time we got to the first 30 lines of the program.
In case of error...
0 ONERR GOTO 4
1 REM
Reset input / output
4 PR # 0: IN # 0
Set HIMEM to $BFFF (which I guess would effectively remove DOS)
5 HIMEM: 49151
Clear all variables and GOSUB to the Character Creation routine.
7 CLEAR : GOSUB 60000
Get a random number based on the player's lucky number.
8 ZZ = RND ( - ABS (LN))
Can't fine where a variable LE
is used anywhere.
9 LEVEL = 0
Switch to text mode and print the welcome message:
10 TEXT : HOME : NORMAL : VTAB (12): PRINT " WELCOME TO AKALABETH, WORLD OF DOOM!"
Initialize the arrays:
20 DIM DN%(10,10),TE%(20,20),XX%(10),YY%(10),PER%(10,3),LD%(10,5),CD%(10,3),FT%(10,5),LAD%(10,3)
DN%(10,10)
is for dungeon level mapsTE%(20,20)
is for terrain mapXX%(10)
unknownYY%(10)
unknownPER%(10,3)
unknownLD%(10,5)
unknownCD%(10,3)
unknownFT%(10,5)
unknownLAD%(10,3)
unknown
Given we haven't seen any of the unknown ones anywhere else yet, they must be related to drawing the dungeon images.
Randomize the Terrain Map
Set the outsides to mountains:
30 FOR X = 0 TO 20:TE%(X,0) = 1:TE%(0,X) = 1:TE%(X,20) = 1:TE%(20,X) = 1: NEXT
Warn the player that this next bit could take a while...
35 : VTAB (23): PRINT " (PLEASE WAIT)";
Completely randomize the inside 19 x 19 terrain tiles (except for Lord British's Castle). I'm still not sure what the ; 5 * 4.5
in the INT
would do:
40 FOR X = 1 TO 19: FOR Y = 1 TO 19:TE%(X,Y) = INT ( RND (1) ; 5 * 4.5)
However, if we roll a town/shop, there's a 50% chance it will be changed to empty land:
41 IF TE%(X,Y) = 3 AND RND (1) > .5 THEN TE%(X,Y) = 0
42 NEXT : PRINT ".";: NEXT
We pick one place to put Lord British's Castle. We then randomly pick a starting location and make that position a town/shop.
50 TE%( INT ( RND (1) * 19 + 1), INT ( RND (1) * 19 + 1)) = 5:TX = INT ( RND (1) * 19 + 1):TY = INT ( RND (1) * 19 + 1):TE%(TX,TY) = 3
I guess that makes it possible (1 in 361 chance) that there'll be no Lord British in a game because the starting location (and hence the starting town) overwrote Lord British's Castle.
Lines 51–62 seem to be initializing some lookup tables for the dungeon drawing so we'll get to those in a bit. The rest of the initialization code is...
Clear the screen and set the graphics color to white:
68 HOME : HCOLOR= 3
Then set the top edge and the width of the text area to be 20 and 29 and go to the top of that area:
69 POKE 34,20: POKE 33,29: HOME
Finally we draw the surrounding terrain and go to the command loop:
70 GOSUB 100: GOTO 1000
So, we just have the following left:
- an explanation of
XX%(10)
,YY%(10)
,PER%(10,3)
,LD%(10,5)
,CD%(10,3)
,FT%(10,5)
,LAD%(10,3)
- the initialization in lines 51–62
- the routines in lines 200–491 and 3087–3089
all of which are almost certainly to do with drawing the dungeons.
Because it's hard to understand what is being initialized in lines 51–62 without seeing how it's later used, let's take a look into the routines starting at line 200.
We enter hires graphics mode, set DIS
(distance?) to zero and set the plot colour to white:
200 HGR :DIS = 0: HCOLOR= 3
Next we calculate CENT
, LEFT
and RIGH
(RIGHT
). Recall that PX
and PY
are the player's location and DX
and DY
give the direction they are looking.
202 CENT = DNG%(PX + DX * DIS,PY + DY * DIS):LEFT = DNG%(PX + DX * DIS + DY,PY + DY * DIS - DX):RIGH = DNG%(PX + DX * DIS - DY,PY + DY * DIS + DX)
We'll come back to line 204 .
Now CENT
, LEFT
and RIGHT
contain what is in front and to the left and right. The tens column is each case is used for the monster (if any) and the ones column for the tile. We put into MC
the monster in front and change CENT
to just be the tile. Similarly we change LEFT
and RIGHT
to just contain the tile (with no monster info):
205 CENT = INT (CENT):LEFT = INT (LEFT):RIGHT = INT (RIGHT)
206 MC = INT (CENT / 10):CENT = CENT - MC * 10:LEFT = INT ((LEFT / 10 - INT (LEF / 10)) * 10 + .1):RIGH = INT ((RIGH / 10 - INT (RIG / 10)) * 10 + .1)
If DIS
is zero then we skip to 216...
208 IF DIS = 0 THEN 216
Otherwise...
We're still not sure what 3
or 4
correspond to (doors?) but if we have a WALL (1
) or a 3
or 4
we draw a line from (L1
,T1
) to (R1
,T1
) to (R1
,B1
) to (L1
,B1
) to (L1
,T1
).
210 IF CENT = 1 OR CENT = 3 OR CENT = 4 THEN HPLOT L1,T1 TO R1,T1 TO R1,B1 TO L1,B1 TO L1,T1
And if it's a WALL or 3
, that's it, we go on to line 260.
212 IF CENT = 1 OR CENT = 3 THEN EN = 1: GOTO 260
If it is a 4
we additionally draw 4 more lines.
214 IF CENT = 4 THEN HPLOT CD%(DIS,0),CD%(DIS,3) TO CD%(DIS,0),CD%(DIS,2) TO CD%(DIS,1),CD%(DIS,2) TO CD%(DIS,1),CD%(DIS,3):EN = 1: GOTO 260
So does this tell us anything about these variables?
L, T, R and B almost certainly stand for left, top, right and bottom. L1
, T1
, R1
and B1
must be coordinates of the rectangle for a wall straight in front of the player.
They were initialized as:
204 L1 = PER%(DIS,0):R1 = PER%(DIS,1):T1 = PER%(DIS,2):B1 = PER%(DIS,3):L2 = PER%(DIS + 1,0):R2 = PER%(DIS + 1,1):T2 = PER%(DIS + 1,2):B2 = PER%(DIS + 1,3)
So we seem to have unlocked what PER%(10,3)
is for. PER%(d, n)
tells us the n
-th coordinate used to draw a wall a distance d
away. I suspect PER
may be short for "perspective".
Let's take a look at the initialization:
Into XX%(0)
and YY%(0)
we put our vanishing point (roughly center of the screen):
51 XX%(0) = 139:YY%(0) = 79
Next we step 2, 4, 6, ... 20 and calculate XX%(X/2)
and YY%(X/2)
(i.e. 1, 2, ... 10):
52 FOR X = 2 TO 20 STEP 2:XX%(X / 2) = INT ( ATN (1 / X) / ATN (1) * 140 + .5):YY%(X / 2) = INT (XX%(X / 2) * 4 / 7)
I'll explain the trigonometry in a little bit but note here that the 4 / 7
is just because that's the ratio of the viewport height to the viewport width and so once we've worked out the XX
for a given distance, we can just get the YY
by multiplying by this.
This enables us to calculate our perspective. 0 is left, 1 is right, 2 is top, 3 is bottom.
53 PER%(X / 2,0) = 139 - XX%(X / 2):PER%(X / 2,1) = 139 + XX%(X / 2):PER%(X / 2,2) = 79 - YY%(X / 2):PER%(X / 2,3) = 79 + YY%(X / 2): NEXT
And finally we set our zero-distance coordinates to be the edge of the screen:
54 PER%(0,0) = 0:PER%(0,1) = 279:PER%(0,2) = 0:PE%(0,3) = 159
And what about CD%(10,3)
seen in line 214?
It is initialized on line 55:
55 FOR X = 1 TO 10:CD%(X,0) = 139 - XX%(X) / 3:CD%(X,1) = 139 + XX%(X) / 3:CD%(X,2) = 79 - YY%(X) * .7:CD%(X,3) = 79 + YY%(X): NEXT : PRINT ".";
This is very similar to PER but (X,0)
and (X,1)
are scaled by 1/3, and (X,2)
by 0.7.
The only thing CD%()
is used for is line 214 (drawing tile 4
).
If there's a wall on the left, we draw the part where it hits the ceiling and the floor:
216 IF LEFT = 1 OR LEFT = 3 OR LEFT = 4 THEN HPLOT L1,T1 TO L2,T2: HPLOT L1,B1 TO L2,B2
And likewise the right:
218 IF RIGH = 1 OR RIGH = 3 OR RIGH = 4 THEN HPLOT R1,T1 TO R2,T2: HPLOT R1,B1 TO R2,B2
If there's a 4
on the left (we still don't know what 4
is) we draw most based on LD%
:
220 IF LEFT = 4 AND DIS > 0 THEN HPLOT LD%(DIS,0),LD%(DIS,4) TO LD%(DIS,0),LD%(DIS,2) TO LD%(DIS,1),LD%(DIS,3) TO LD%(DIS,1),LD%(DIS,5)
222 IF LEFT = 4 AND DIS = 0 THEN HPLOT 0,LD%(DIS,2) - 3 TO LD%(DIS,1),LD%(DIS,3) TO LD%(DIS,1),LD%(DIS,5)
And similarly on right, but mirror image:
224 IF RIGH = 4 AND DIS > 0 THEN HPLOT 279 - LD%(DIS,0),LD%(DIS,4) TO 279 - LD%(DIS,0),LD%(DIS,2) TO 279 - LD%(DIS,1),LD%(DIS,3) TO 279 - LD%(DIS,1),LD%(DIS,5)
226 IF RIGH = 4 AND DIS = 0 THEN HPLOT 279,LD%(DIS,2) - 3 TO 279 - LD%(DIS,1),LD%(DIS,3) TO 279 - LD%(DIS,1),LD%(DIS,5)
We need to draw some extra lines if we're not a WALL (or 3
or 4
):
228 IF LEFT = 3 OR LEFT = 1 OR LEFT = 4 THEN 234
If the distance we're drawing is not zero, we draw a vertical line on the left:
230 IF DIS < > 0 THEN HPLOT L1,T1 TO L1,B1
and regardless of distance we draw another trapezoid:
232 HPLOT L1,T2 TO L2,T2 TO L2,B2 TO L1,B2
and we do the same for the right:
234 IF RIGH = 3 OR RIGH = 1 OR RIGH = 4 THEN 240
236 IF DIS < > 0 THEN HPLOT R1,T1 TO R1,B1
238 HPLOT R1,T2 TO R2,T2 TO R2,B2 TO R1,B2