I think I'll use child streams to separately explore the **preamble files** and **main game file**. ---- ## 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 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 has * `PW()` seems to contain the count of various items the player has The index of `PW()` seems to range over: * `0` = FOOD * `1` = RAPIER * `2` = AXE * `3` = SHIELD * `4` = BOW * `5` = AMULET On the screen: * `(16, 10)` seems to hold the amount of gold * `(5 + z, 25 - len(PW(z)))` holds `PW(z)` ---- 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 (include `5` = GOLD) * `C$()` seems to contain the names of the stats * `PW()` seems to contain the count of various items the player has * `W$() ` seems to contain the names of the items ---- 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 values * `W$()` weapon/item names * `PW()` player weapon/item counts * `M$` monster names * `ML%` don't know yet * `MZ%` don't know yet * `PT$` player type/class (`F` for Fighter, `M` for Mage) * `LN` lucky number (used in generation of `ZZ`) * `ZZ` don't know yet ---- ## Stats / Qualities * `0` = HIT POINTS * `1` = STRENGTH * `2` = DEXTERITY * `3` = STAMINA * `4` = WISDOM * `5` = GOLD ---- ## Weapons/Items * `0` = FOOD * `1 `= RAPIER * `2 `= AXE * `3 `= SHIELD * `4` = BOW AND ARROWS * `5` = AMULET ---- ## Monsters * `1` = SKELETON * `2` = THIEF * `3` = GIANT RAT * `4` = ORC * `5` = VIPER * `6` = CARRION CRAWLER * `7` = GREMLIN * `8` = MIMIC * `9` = DAEMON * `10` = BALROG ---- ## Monsters * `1` = SKELETON * `2` = THIEF * `3` = GIANT RAT * `4` = ORC * `5` = VIPER * `6` = CARRION CRAWLER * `7` = GREMLIN * `8` = MIMIC * `9` = DAEMON * `10` = 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 ????? ---- 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 name * `LP` difficulty level being played at * `TASK` if positive, the monster the player is on a quest to kill, if negative then `ABS(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 " "; 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 1150 * `RIGHT ARROW` (149) EAST 1200 or TURN RIGHT 1250 * `LEFT ARROW` (136) WEST 1300 or TURN LEFT 1350 * `/` (175) SOUTH 1400 or TURN AROUND 1450 * `X` (216) GO 1500 or 1550 * `A` or `27` (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 location * `DNG%()` contains what is at a particular location in a dungeon * `TX` and `TY` are the player's location outside * `PX` and `PY` are the player's location inside * `DX` and `DY` are how `PX` and `PY` 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` = MOUNTAINS * `3` = TOWN (SHOP) * `4` = DUNGEON * `5` = LORD BRITISH'S CASTLE Values of `DNG%()` are: * `1` = WALL? * `2` = TRAP * `5` = CHEST * `7` or `9` = DOWN LADDER * `8` = 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%()` (or `DN%()`) stores the monster type/level in the tens column. So `37` 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 type `MN` at this dungeon level * `ML%(MN,0), ML%(MN,1)` location of monster of type `MN` at this dungeon level * `LK` 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` = MOUNTAINS * `3` = TOWN (SHOP) * `4` = DUNGEON * `5` = 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 maps * `TE%(20,20)` is for terrain map * `XX%(10)` unknown * `YY%(10)` unknown * `PER%(10,3)` unknown * `LD%(10,5)` unknown * `CD%(10,3)` unknown * `FT%(10,5)` unknown * `LAD%(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