├── requirements-win.txt ├── images ├── EZUIcPkWoAMG0nK.png └── EZUIm_7WAAA0QpL.png ├── LICENSE ├── PyDungeon.sln ├── PyDungeon.pyproj ├── README.md ├── .gitattributes ├── cursor15-dungeon-unedited-basic.txt ├── .gitignore ├── dungeon-memory-sim.py ├── cursor15-dungeon-annotated-basic.txt └── pydungeon.py /requirements-win.txt: -------------------------------------------------------------------------------- 1 | windows-curses 2 | -------------------------------------------------------------------------------- /images/EZUIcPkWoAMG0nK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chgowiz/PyDungeon/HEAD/images/EZUIcPkWoAMG0nK.png -------------------------------------------------------------------------------- /images/EZUIm_7WAAA0QpL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chgowiz/PyDungeon/HEAD/images/EZUIm_7WAAA0QpL.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michael Shorten (chgowiz) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PyDungeon.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30104.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "PyDungeon", "PyDungeon.pyproj", "{B6894364-5405-4815-A74F-0A5336597418}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DA54300F-2340-4B65-9123-2BEBB815500C}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {B6894364-5405-4815-A74F-0A5336597418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {B6894364-5405-4815-A74F-0A5336597418}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(SolutionProperties) = preSolution 23 | HideSolutionNode = FALSE 24 | EndGlobalSection 25 | GlobalSection(ExtensibilityGlobals) = postSolution 26 | SolutionGuid = {8991B57B-3B81-485A-9450-DA40B54F43F3} 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /PyDungeon.pyproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Debug 4 | 2.0 5 | b6894364-5405-4815-a74f-0a5336597418 6 | . 7 | pydungeon.py 8 | 9 | 10 | . 11 | . 12 | PyDungeon 13 | PyDungeon 14 | 15 | 16 | true 17 | false 18 | 19 | 20 | true 21 | false 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyDungeon 2 | 3 | UPDATE: 6/5/20 - With this last checkin and merge to master branch, I've achieved all my goals. 4 | - understanding the 1979 Commodore BASIC code 5 | - implemented (mostly) as-is in Python 6 | - improve code to more modern approach 7 | - fix bugs, make small tweaks. 8 | 9 | If I started to do more, like add more features, I might as well make a rogue clone. And that's not really the purposes of this. If you're checking out my code, I hope you enjoy! If you have comments/questions, you can get ahold of me at chgowiz@gmail.com 10 | 11 | ------ 12 | 13 | This is a project to recreate DUNGEON - a graphical dungeon crawler developed by Brian Sawyer for Cursor Magazine, issue #15. It was my very first computer D&D game, at a time when I had just started reading and playing the tabletop roleplaying game. DUNGEON was an eye-opener to me at the time (1979/1980). 14 | 15 | I'm attempting to recreate the game based on the source code, implemented in Python. Obviously, some things will be very different - the Commodore PET implemented a crude, but effective graphics mode which DUNGEON took advantage of. No such luck in console-based Python! I have vague dreams of figuring out the basic algorithms and then implementing using something a bit more graphical. We'll see.. 16 | 17 | The source code from the original game is in the repo, and my comments on both an annotated copy of the source and the Python code should hopefully give some insights into what Messr. Sawyer had to do in order to implement this game on the old PET! I hope you enjoy this as much as I do. 18 | 19 | ## Prerequisites 20 | 21 | ### Unix (Linux, OS X) 22 | 23 | The game should run out of the box. Just run the game as described below. 24 | 25 | ### Windows 26 | 27 | - [Python 3](https://www.python.org/) is needed 28 | - The Curses library for windows is needed. Run `pip install -r requirements-win.txt` once before the first start of the game. 29 | 30 | ## To run: 31 | 32 | - Under Unix: `python3 ./pydungeon.py` 33 | - Under Windows should be `.\pydungeon.py` enough 34 | 35 | |CBM PET DUNGEON|PyDungeon (dungeon memory map dump)| 36 | :--------------:|:-----------------: 37 | | 38 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /cursor15-dungeon-unedited-basic.txt: -------------------------------------------------------------------------------- 1 | 0 CLR:POKE59468,12 2 | 1 REM DUNGEON COPYRIGHT (C) 1979 BRIAN SAWYER 3 | 2 REM 1310 DOVER HILL ROAD 4 | 3 REM SANTA BARBARA, CA. 93103 5 | 4 : 6 | 5 REM CURSOR #15, NOV/DEC 1979 7 | 6 REM BOX 550, GOLETA, CA. 93017 8 | 7 REM LINES 61000-65000 (C) 1979 CURSOR MAGAZINE 9 | 8 : 10 | 10 REM AS OF 12/29/79 11 | 90 PG$="DUNGEON":NM$="15":GOSUB62000 12 | 100 RS=23:CS=40:SZ=RS*CS:BL=(25-RS)*40:RS=RS-1:CS=CS-1 13 | 130 REM TRICK: DG$() STRINGS GO AT END OF MEMORY! 14 | 140 DIMDG$(24):E$=" " 15 | 150 FORI=0TO24:DG$(I)=E$+"":NEXTI 16 | 160 ER$=CHR$(19)+CHR$(17)+" "+CHR$(19)+CHR$(17) 17 | 170 E2$=CHR$(19)+" "+CHR$(19) 18 | 180 PRINTCHR$(147);"SETTING UP..." 19 | 190 TS=PEEK(QM)+256*PEEK(QM+1)-SZ:AX=32768 20 | 200 FORI=TS+40TOTS+SZ-41:POKEI,32:NEXTI 21 | 210 HP=50:MG=0:EX=0:PX=0:HG=0:Z=0:FG=0:K1=0:E=0:S=0:W=160:ET=160 22 | 220 TI$="000000":TM=TI+3600 23 | 230 GOSUB380 24 | 240 TS=TS-BL 25 | 250 L=INT(RND(1)*SZ+TS):IFPEEK(L)<>160THEN250 26 | 260 TM=0:GOSUB1410:L=L+AX-TS:W=PEEK(L):GOSUB600:POKEL,209:W=160 27 | 270 POKEQK,0:POKEQP,255:GOSUB1240 28 | 280 HP=HP-.15-2*SX:IFHP<0THEN1190 29 | 290 Q=VAL(MID$("808182404142000102",A*2-1,2))-41 30 | 300 IF(PEEK(L+Q)=32)AND(SX<>1)THEN270 31 | 310 IFPEEK(L+Q)=127THEN270 32 | 320 POKEL,W:L=L+Q:W=PEEK(L):POKEL,209:GOSUB600:POKEL,209 33 | 330 IFW=135THENGOSUB1200 34 | 340 IFW>=214ANDW<=219THENGOSUB1000 35 | 350 IFE>0THENS=S+1 36 | 360 IFS>1THENGOSUB830 37 | 370 GOTO270 38 | 380 PRINTINT((TM-TI)/60);CHR$(157);" ";CHR$(145) 39 | 400 W=INT(RND(1)*9+2):L=INT(RND(1)*9+2) 40 | 410 R0=INT(RND(1)*(RS-L-1))+1:C0=INT(RND(1)*(CS-W-1))+1:P=TS+40*R0+C0 41 | 420 IFP+40*L+W>=TS+SZTHEN530 42 | 430 FORN=0TOL+1:FORN1=0TOW+1:IFPEEK(P+(N*40)+N1)<>32THEN530 43 | 440 NEXTN1,N 44 | 450 FORN=1TOL:FORN1=1TOW:POKEP+(N*40)+N1,160:NEXTN1,N 45 | 460 FORN=P+42+(L*40)TOTS+999STEP40 46 | 470 IFPEEK(N)=160THENFORN1=P+42TONSTEP40:POKEN1,160:NEXT:POKEN1-80,102:GOTO490 47 | 480 NEXTN 48 | 490 FORN=P+81+WTOP+121+W:IF(N-TS)/40=INT((N-TS)/40)THEN520 49 | 500 IFPEEK(N)=160THENFORN1=P+81TON-1:POKEN1,160:NEXT:POKEN1-1,102:GOTO520 50 | 510 NEXT 51 | 520 S=INT(RND(1)*L)+1:S1=INT(RND(1)*W+1):POKEP+S1+S*40,INT(RND(1)*6+214) 52 | 530 IFTI160THEN550 55 | 560 POKEU,135:HG=HG+1:NEXT 56 | 570 FORR0=0TORS:POKETS+40*R0,127:POKETS+40*R0+CS,127:NEXTR0 57 | 580 FORC0=0TOCS:POKETS+C0,127:POKETS+C0+40*RS,127:NEXTC0 58 | 590 RETURN 59 | 600 K=-40:J=3:M=40:R=3:GN=0 60 | 610 IFSM=1THENK=-80:J=5:M=80:R=4:SM=0 61 | 620 O=L-32767-R 62 | 630 IFO+32811>33768THENM=0 63 | 640 FORN=-40TO40STEP40:FORN1=1TO3:IFN=0ANDN1=2THEN820 64 | 650 Y=O+N+N1:V=PEEK(Y+TS):POKEY+AX,V 65 | 660 IFV<135ORV=160THEN820 66 | 670 V=V-128:IFV<>7THEN710 67 | 680 K1=1+K1+INT((MG+1)*(RND(1))) 68 | 690 GN=GN+1:IFGN>FGTHENGOSUB1410:PRINT"GOLD IS NEAR!":GOSUB1430:FG=HG+1 69 | 700 GOTO820 70 | 710 V1=V+128:S=0:POKEY+TS,160 71 | 720 IFV=86THENE$="SPIDER":I=3 72 | 730 IFV=87THENE$="GRUE":I=7 73 | 740 IFV=88THENE$="DRAGON":I=1 74 | 750 IFV=89THENE$="SNAKE":I=2 75 | 760 IFV=90THENE$="NUIBUS":I=9 76 | 770 IFV=91THENE$="WYVERN":I=5 77 | 780 I=INT(RND(1)*HP+(PX/I)+HP/4) 78 | 790 IFE>0THENPOKETS+E,QQ 79 | 800 QQ=V+128:E=Y 80 | 810 GOSUB1410:PRINT"A "E$;" WITH";I;"POINTS IS NEAR.":GOSUB1430:CC=I 81 | 820 NEXTN1:NEXTN:FG=GN:RETURN 82 | 830 O1=0:A=0:E1=E+AX:IFABS(E1+40-L)128)THENO1=O1+A 85 | 860 IFABS(E1-1-L)128)THENO1=O1+A 88 | 890 A=O1:IFE1+A=LTHEN960 89 | 900 IFE1+A1THEN1160 109 | 1100 GOSUB1410:W=160:S=0:E=0:POKEL,209:PRINT"THE "E$" IS DEAD!":GOSUB1430 110 | 1110 EX=EX+I:Z=Z+1 111 | 1120 IFEX0THENRETURN 122 | 1230 GOTO1350 123 | 1240 IFIU=0THENGOSUB1430 124 | 1250 GOSUB1340 125 | 1260 IFIUTHENIFTI>TMTHENGOSUB1410:PRINT"YOU MAY MOVE." 126 | 1270 GETL$:IFL$=""THEN1260 127 | 1280 A=ASC(L$):SX=ABS(A>127):A=AAND127 128 | 1290 IFA=ASC("5")THENHP=HP+1+SQR(EX/HP) 129 | 1300 IFA>48ANDA<58THENA=A-48:TM=0:GOSUB1410:RETURN 130 | 1310 IFL$="S"THENSM=1:HP=HP-2 131 | 1320 IFL$="Q"THEN1350 132 | 1330 GOTO1250 133 | 1340 PRINTE2$;"HIT PTS.";INT(HP+.5);CHR$(157);" EXP.";EX;CHR$(157);" GOLD";MG;" ":RETURN 134 | 1350 GOSUB1410:PRINTE2$;"GOLD:";MG;" EXP:";EX;" KILLED";Z;"BEASTS" 135 | 1360 FORN=BLTOSZ-1+BL:A=PEEK(TS+N):POKEAX+N,A:NEXT 136 | 1375 GETL$:IFL$<>""THEN1375 137 | 1380 GOSUB1410:PRINT"WANT TO PLAY AGAIN"; 138 | 1390 GOSUB1500:IFL$<>"N"THEN180 139 | 1400 TM=0:GOSUB1410:PRINTCHR$(145);:END 140 | 1410 IFIUTHENIFTI""THEN1550 147 | 1520 IFTI>ZTTHENPRINTMID$("? ",ZC,1);CHR$(157);:ZT=TI+30:ZC=3-ZC 148 | 1530 GOTO1510 149 | 1550 PRINT"? ";L$:RETURN 150 | 60300 PRINTCHR$(147):CLR:GOSUB60400:GOTO100 151 | 60400 QK=525:QM=134:QP=515:CR$=CHR$(13) 152 | 60410 IFPEEK(50000)=0THENRETURN 153 | 60420 QK=158:QM=52:QP=151 154 | 60430 RETURN 155 | 60500 FORI=1TO10:PRINTCHR$(192);CHR$(192);CHR$(192);CHR$(192);:NEXTI:RETURN 156 | 62000 PRINTCHR$(147);CHR$(17);CHR$(17);TAB(9);"CURSOR #";NM$;" ";PG$ 157 | 62010 PRINTCHR$(17);" COPYRIGHT (C) 1979 BY BRIAN SAWYER";CHR$(17) 158 | 62020 GOSUB60500 159 | 62030 PRINTCHR$(17);"SEARCH FOR GOLD IN THE ANCIENT RUINS" 160 | 62080 PRINTCHR$(17);CHR$(17);CHR$(17);"PRESS ";CHR$(18);"RETURN";CHR$(146);" TO BEGIN" 161 | 62090 GETT$:IFT$=""THEN62090 162 | 62100 GOTO60300 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb -------------------------------------------------------------------------------- /dungeon-memory-sim.py: -------------------------------------------------------------------------------- 1 | # Simulating what goes on in the memory structure 2 | # of DUNGEON 3 | 4 | from random import random, randint, choice 5 | import sys 6 | from os import system, name 7 | 8 | WIDTH = 40 9 | HEIGHT = 25 10 | RM_GEN_RETRIES = 50 11 | DESIRED_RMS = 8 12 | ASCII_a = ord("a") 13 | t_vars = {} 14 | MONSTERS = ["R","G","D","S","N","W"] 15 | FLOOR = "." 16 | DOOR = "+" 17 | GOLD = "g" 18 | BORDER = "*" 19 | RS=23; CS=40; SZ=RS*CS; BL=(25-RS)*40 ; RS=RS-1; CS=CS-1 20 | 21 | def draw_memory(data, vars_dict, step=""): 22 | cls() # Works only if run from true terminal, not from IDE 23 | 24 | # Draw the board data structure 25 | margin = " " 26 | tens_line = margin + " " # Initial space for left side of board 27 | for i in range(1,(WIDTH//10)): 28 | tens_line += (" " * 9) + str(i) 29 | digits_line = margin + ("0123456789" * (WIDTH//10)) 30 | if step != "": 31 | print("=== {} ===".format(step)) 32 | print(tens_line) 33 | print(digits_line) 34 | 35 | for row in range(HEIGHT): 36 | # single digit numbers are padded with extra space 37 | if row < 10: 38 | extra_space = " " 39 | else: 40 | extra_space = "" 41 | 42 | print("{}{}| ".format(extra_space, row), end="") 43 | for col in range(WIDTH): 44 | print(data[row][col], end="") 45 | print(" |{}{}".format(extra_space,row)) 46 | print(digits_line) 47 | print(tens_line) 48 | get_continue(vars_dict) 49 | 50 | 51 | def init_memory(): 52 | # Create new data structure 53 | # Returns: list of [WIDTH][HEIGHT] elements 54 | board = [] 55 | for row in range(HEIGHT): 56 | board.append([]) 57 | for col in range(WIDTH): 58 | board[row].append("~") 59 | 60 | return board 61 | 62 | 63 | def get_continue(vars_dict): 64 | var_str = "" 65 | for k,v in vars_dict.items(): 66 | var_str += "{}={}, ".format(k,v) 67 | print(var_str) 68 | input("Press return to continue...") 69 | 70 | 71 | def POKE(memory, location, value): 72 | memory[(location//WIDTH)][(location%WIDTH)] = value 73 | 74 | 75 | def PEEK(memory, location): 76 | return memory[(location//WIDTH)][(location%WIDTH)] 77 | 78 | 79 | def cls(): 80 | if name == "nt": 81 | _ = system("cls") 82 | else: 83 | _ = system("clear") 84 | 85 | 86 | def gen_room_loc(TS): 87 | W = int(random()*9+2); L = int(random()*9+2) 88 | R0=int(random()*(RS-L-1))+1; C0=int(random()*(CS-W-1))+1; P=TS+40*R0+C0 89 | return W, L, R0, C0, P 90 | 91 | 92 | def gen_dungeon(TS, SZ): 93 | # We return a list data structure that represents a map of 40 columns, 94 | # 25 rows. The dungeon is generated inside this structure and serves 95 | # to feed what will be seen on the screen. In a sense, we'll have two 96 | # structures - the full map, and then the map that the player reveals 97 | # and what gets painted to the screen. 98 | mem = [] 99 | mem = init_memory() 100 | 101 | # 200 FOR I= TS + 40 TO TS + SZ-41:POKEI,32:NEXTI 102 | for i in range(TS+40, (TS+SZ-41)+1): 103 | # POKE treats this as a continuous area of data, not divided 104 | # into an x,y list like we have here. So, create a function POKEds that will 105 | # take a number, and then map to our row/col coordinate system 106 | # Also, BASIC FOR is inclusive of start, stop, so we have to add 1 107 | # because Python for is exclusive of stop 108 | POKE(mem, i, " ") 109 | # t_vars["i"] = i 110 | 111 | # draw_memory(mem, t_vars, "INIT") 112 | 113 | retries = 0 114 | rooms_generated = 0 115 | # Keep generating rooms until we've hit a limit of DESIRED_RMS rooms or we've maxed out with 116 | # retries of so many times (RM_GEN_RETRIES) . 117 | while rooms_generated < DESIRED_RMS and retries < RM_GEN_RETRIES: 118 | W, L, R0, C0, P = gen_room_loc(TS) 119 | 120 | # 400 W=INT(RND(1)*9+2):L=INT(RND(1)*9+2) 121 | # 410 R0=INT(RND(1)*(RS-L-1))+1:C0=INT(RND(1)*(CS-W-1))+1:P=TS+40*R0+C0 122 | # 420 IFP+40*L+W>=TS+SZTHEN530 123 | 124 | # This was a check to see if the room in the data structure would go over 125 | # the boundaries (end point) of the area of memory allocated. (TS+SZ) 126 | if P+40*L+W >= TS+SZ: 127 | fail_on_size += 1 128 | continue 129 | 130 | # t_vars["W"] = W; t_vars["L"] = L 131 | # t_vars["R0"] = R0; t_vars["C0"] = C0; t_vars["P"] = P 132 | 133 | #430 FORN=0TOL+1:FORN1=0TOW+1:IFPEEK(P+(N*40)+N1)<>32THEN530 134 | #440 NEXTN1,N 135 | # This looks to see if the room overlaps other rooms. 136 | # I know the -1 looks strange, but the game should keep rooms at least 1 137 | # space apart. Since I'm 0 index and BASIC was 1 indexed, this is how 138 | # it will work. 139 | failed_check = False 140 | for N in range(-1,(L+1)+1): 141 | for N1 in range(-1, (W+1)+1): 142 | if PEEK(mem, P+(N*40+N1)) != " ": 143 | failed_check = True 144 | break 145 | 146 | if failed_check: 147 | retries += 1 148 | break 149 | 150 | if not failed_check: 151 | #450 FORN=1TOL:FORN1=1TOW:POKEP+(N*40)+N1,160:NEXTN1,N 152 | # We have a good room! 153 | # This fills the room space. For now, I'll use # to denote room space. 154 | rooms_generated += 1 155 | for N in range(0,L+1): 156 | for N1 in range(0, W+1): 157 | POKE(mem, P+(N*40+N1), FLOOR) 158 | 159 | # Generate vertical passages from generated room down. 160 | # 460 FOR N=P+42+(L*40) TO TS+999 STEP 40 161 | # 470 IFPEEK(N)=160 THEN FOR N1=P+42 TO N STEP 40:POKE N1,160 : NEXT: POKEN1-80,102: GOTO490 162 | # 480 NEXT N 163 | for N in range(P+42+(L*40), TS+999+1, 40): 164 | if PEEK(mem,N) == FLOOR: 165 | for N1 in range(P+42, N+1, 40): 166 | POKE(mem, N1, FLOOR) 167 | # I think this used to be N1-80 because FOR NEXT in PET would increment, check 168 | # and leave the value at the incremented, which meant you'd have to back up 169 | # two rows. It seems that Python doesn't do that, so if I only back up 40, the 170 | # door symbol (in memory map, remember) is in the right spot. 171 | POKE(mem, N1-40, DOOR) 172 | break 173 | 174 | # draw_memory(mem, t_vars, "END VPASS GEN") 175 | 176 | # Generate horizontal passages from generated room right. 177 | # 490 FOR N= P+81+W TO P+121+W:IF(N-TS)/40=INT((N-TS)/40)THEN520 178 | # N = 32303 + 81 + 6 (32390) TO 32303 + 121 + 6(32430) 179 | # IF (32390-31848)/40 = INT((32390-31848)/40) then no more passage/break; 180 | # 500 IFPEEK(N)=160THENFORN1=P+81TON-1:POKEN1,160:NEXT:POKEN1-1,102:GOTO520 181 | # 510 NEXT 182 | start = P+81+W; end = P+121+W 183 | for N in range(start, end+1): 184 | if (N-TS)/40 == int((N-TS)/40): 185 | break 186 | 187 | if PEEK(mem, N) == FLOOR: 188 | for N1 in range(P+81, (N-1)+1): 189 | POKE(mem, N1, FLOOR) 190 | POKE(mem, N1, DOOR) 191 | break 192 | 193 | # draw_memory(mem, t_vars, "END HPASS GEN") 194 | # Generate a monster in the room. Every room has a monster! 195 | # 520 S=INT(RND(1)*L)+1:S1=INT(RND(1)*W+1):POKEP+S1+S*40,INT(RND(1)*6+214) 196 | S = int(random()*L)+1; S1 = int(random()*W+1) 197 | POKE(mem, P+S1+S*40, choice(MONSTERS)) 198 | 199 | # Distribute 11 gold around the dungeon 200 | # 540 FORN=1TO11 201 | # 550 U=INT(RND(1)*SZ)+TS:IFPEEK(U)<>160THEN550 202 | # 560 POKEU,135:HG=HG+1:NEXT 203 | for N in range(0,11): 204 | is_room = False 205 | while not is_room: 206 | U = int(random()*SZ)+TS 207 | is_room = (PEEK(mem,U) == FLOOR) 208 | POKE(mem, U, GOLD) 209 | # HG +=1 210 | 211 | # Generate borders (I think) 212 | # 570 FORR0=0TORS:POKETS+40*R0,127:POKETS+40*R0+CS,127:NEXTR0 213 | # 580 FORC0=0TOCS:POKETS+C0,127:POKETS+C0+40*RS,127:NEXTC0 214 | for R0 in range(0,RS+1): 215 | POKE(mem, TS+40*R0, BORDER) 216 | POKE(mem, TS+40*R0+CS, BORDER) 217 | 218 | for C0 in range(0, CS+1): 219 | POKE(mem, TS+C0, BORDER) 220 | POKE(mem, TS+C0+40*RS, BORDER) 221 | 222 | return mem 223 | 224 | # Simulating the values from Dungeon 225 | # 100 RS=23: CS=40: SZ=RS*CS: BL=(25-RS)*40:RS=RS-1:CS=CS-1 226 | # SZ = 920 - the number of spaces in a 23*40 data space 227 | 228 | 229 | #t_vars["RS"] = RS 230 | #t_vars["CS"] = CS 231 | #t_vars["SZ"] = SZ 232 | #t_vars["BL"] = BL 233 | 234 | # 190 TS=PEEK(QM)+256*PEEK(QM+1)-SZ:AX=32768 235 | TS = 0 # In PET/Dungeon, this is 31848 236 | # but it doesn't matter here. 237 | # We'll assume the starting point is 0 238 | 239 | AX = TS + 920 # Since TS and AX start off at the same point, 240 | # and then TS is backed off by 920, we'll reverse 241 | # that for now. 242 | 243 | # In PETs, 32768 was start of screen RAM! POKEing to this location put 244 | # something at 0 row, 0 col on the screen! 245 | 246 | # t_vars["TS"] = TS 247 | # t_vars["AX"] = AX 248 | 249 | # 210 HP=50:MG=0:EX=0:PX=0:HG=0:Z=0:FG=0:K1=0:E=0:S=0:W=160:ET=160 250 | 251 | # For giggles/grins, generate a whole bunch of maps 252 | for dngmap in range(1,11): 253 | dungeon_map = gen_dungeon(TS, SZ) 254 | draw_memory(dungeon_map, t_vars, "MAP #{} GENERATED".format(dngmap)) 255 | 256 | -------------------------------------------------------------------------------- /cursor15-dungeon-annotated-basic.txt: -------------------------------------------------------------------------------- 1 | 0 POKE59468,12 # set computer into graphics mode 2 | 1 REM DUNGEON COPYRIGHT (C) 1979 BRIAN SAWYER 3 | 2 REM 1310 DOVER HILL ROAD 4 | 3 REM SANTA BARBARA, CA. 93103 5 | 4 : 6 | 5 REM CURSOR #15, NOV/DEC 1979 7 | 6 REM BOX 550, GOLETA, CA. 93017 8 | 7 REM LINES 61000-65000 (C) 1979 CURSOR MAGAZINE 9 | 10 | # display related - program name for std CURSOR display 11 | # Cursor issue # 12 | 90 PG$="DUNGEON":NM$="15":GOSUB62000 13 | 14 | ==> GOSUB 62000 15 | # Print mag info 16 | ====> GOSUB 60500 17 | # print a line of 40 graphic chars (CHR$192) 18 | <==== RETURN TO 62030 19 | ++++> GOTO 60300 20 | # Clear variables and screen 21 | ======> GOSUB 60400 22 | # Determine which version of BASIC we have and set key vars 23 | # to get sys related info later on. 24 | <====== RETURN TO 60300 25 | ++++> GOTO 100 26 | # What happens to the stack w/the original gosub back in line 90? 27 | # The CLR in 60300 zaps the stack. So here we are... 28 | 29 | # Key variables used for screen size and amount of memory needed 30 | # for the in-memory dungeon map. 31 | 100 RS=23: CS=40: SZ=RS*CS: BL=(25-RS)*40:RS=RS-1:CS=CS-1 32 | $ SZ will equal 920 33 | $ BL = 80 34 | $ RS = 22 35 | $ CS = 39 36 | 37 | # In PET, strings are stored at end of memory and pointers are stored lower 38 | # (closer to beginning) to those strings. 39 | # Brian intends on storing the in-memory map as far down in memory as he 40 | # can. 41 | # Not quite sure why he did this... as the "end of memory" is set 42 | # later on line 200. Maybe he forgot he did one or the other? 43 | 130 REM TRICK: DG$() STRINGS GO AT END OF MEMORY! 44 | 140 DIMDG$(24):E$=" " 45 | 150 FORI=0TO24:DG$(I)=E$+"":NEXTI 46 | 47 | # Setting up 2 status bars lines and set screen. 48 | 160 ER$="{HOME}{DOWN} {HOME}{DOWN}" 49 | 170 E2$="{HOME} {HOME}" 50 | 180 PRINT"{CLEAR}SETTING UP..." 51 | 52 | # AX is assumed screen ram location 53 | # This seems to look at the point for "Top of memory" which is also 54 | # where screen ram starts, then backs off 920. 55 | # running this on emulator gives me 31848 56 | 190 TS=PEEK(QM)+256*PEEK(QM+1)-SZ:AX=32768 57 | $ TS=PEEK(52)+256*PEEK(52+1)-920:AX=32768 58 | $ TS = 31848 59 | 60 | # Putting spaces into this memory - perhaps to clear the in-mem map? 61 | 200 FOR I= TS + 40 TO TS + SZ-41:POKEI,32:NEXTI 62 | $ FOR I= 31848 + 40 TO 31848 + 920 - 41:POKEI,32:NEXTI 63 | 64 | 210 HP=50:MG=0:EX=0:PX=0:HG=0:Z=0:FG=0:K1=0:E=0:S=0:W=160:ET=160 65 | #* HP is player hit points 66 | #* MG is gold recovered 67 | #* EX is experience earned 68 | #* HG is hidden gold (max of 11) 69 | #* Z is number of monsters killed 70 | #* W is for what was in the space that the player just moved 71 | #* K1 is how much gold has been uncovered 72 | #* FG is found gold flag - used for showing "Gold is near" text 73 | # E is current monster location 74 | # PX is value needed for player to level up 75 | # S is a delay counter - if the player just spotted the monster, 76 | # then this is used elsewhere to give them a chance to retreat! 77 | # ET is used to hold what the monster moves over. 78 | # There are other values sprinkled around that are initialized and 79 | # used globally later, mostly for monster stats. 80 | 81 | # 220 is related to timer and how long of a pause for printing messages or 82 | # getting input. TI was a magic BASIC variable that gave number of "jiffies" 83 | # See Commodore documentation on that lovely concept. 84 | 220 TI$="000000":TM=TI+3600 85 | 86 | # Build the Dungeon!! 87 | 230 GOSUB 380 88 | 89 | ==>380 90 | Generate the dungeon 91 | <== RETURN FROM 380 <== 92 | 93 | 240 TS = TS - BL 94 | $ 31848 = 31848 - 80 95 | $ TS = 31768 # Now 1000 away from AX/32768 96 | 97 | #* Where to put the player? L is (L)ocation 98 | 250 L=INT(RND(1)*SZ+TS):IFPEEK(L)<>160THEN250 99 | 100 | #* Put player onto the screen and reset W to be 160 101 | 260 TM=0:GOSUB1410:L=L+AX-TS:W=PEEK(L):GOSUB600:POKEL,209:W=160 102 | # AX is start of screen mem. 103 | # Look at the screen/player map to see what is at L. 104 | # W is what is on the map prior to 'gosub 600', which is the player map 105 | # display/movement loop. Once we've done all that, put the player 106 | # token down, which is a dot (but ends up being a reversed dot?) 107 | # and then W will be 160 (floor) again. 108 | 109 | ==> GOSUB 1410 110 | Timer to slow things down 111 | <== RETURN FROM 1410 112 | 113 | ==> GOSUB 600 114 | # Compute effect of putting player on screen... what can they see around 115 | # them initially? 116 | <== RETURN from 600 back to 260 117 | 118 | #* Start of input loop 119 | # These two pokes set the keyboard buffer count to 0 120 | # and set the "key pressed" to be nothing (FF) 121 | 270 POKEQK,0:POKEQP,255:GOSUB1240 122 | $ QK = 158 123 | $ QP = 151 124 | 125 | ==> GOSUB 1240 126 | GET PLAYER MOVE, direction move stored in A. Also, SX might be set to 1 127 | SX is "shift mode", allowing players to move through the blank spaces 128 | <== RETURN FROM 1240 129 | 130 | #* Movement costs HP! Moreso if moving shifted through the dark spaces 131 | # If HP<0, then you're dead! 1190 is death sequence. 132 | 280 HP=HP-.15-2*SX:IFHP<0THEN1190 133 | 134 | #* Are we moving? If so, are we moving into a space between floors? If so, 135 | # are we in shift mode? If not, ignore the entry. 136 | # Did we hit an impassable border (around the map)? 137 | # If so, ignore the entry 138 | # going back to 270 is back to beginning of input loop 139 | # Here's the deal about line 290 140 | # It takes your 1-9 input and through this little trick, converts it 141 | # into the number of memory positions you're moving. If we go back to thinking 142 | # of this as a grid 40cols x 25rows display - then hitting 6 moves you right (+1) 143 | # position. Hitting 1 (diagonal down/left) moves you +39 positions. It's very 144 | # clever! 145 | 290 Q=VAL(MID$("808182404142000102",A*2-1,2))-41 146 | 300 IF(PEEK(L+Q)=32)AND(SX<>1)THEN270 147 | 310 IFPEEK(L+Q)=127THEN270 148 | 149 | #*Move the player 150 | 320 POKEL,W:L=L+Q:W=PEEK(L):POKEL,209:GOSUB600:POKEL,209 151 | 152 | ==> GOSUB 600 153 | # What does the player see? Activate the last monster revealed, 154 | # set up the random gold value to be found. 155 | <== RETURN FROM 600 156 | 157 | #* If we found gold.. 158 | 330 IFW=135THENGOSUB1200 159 | 160 | ==> GOSUB 1200 161 | # We found gold sub 162 | <== RETURN 1200 to 330 163 | 164 | # If we move onto a monster - attack routine! 165 | 340 IFW>=214ANDW<=219THENGOSUB1000 166 | 167 | ==> GOSUB 1000 168 | # A fight! Who will win? 169 | <== RETURN FROM 1000 170 | 171 | # If there's an active monster (E is set w/location) 172 | # then increment S, and if S is > 1, move the monster. 173 | # This gives the player time to escape. If they start out 174 | # next to the monster, S is 0. E is set to monster location. 175 | # This code will run, S increments to one, S>1 is false, so the 176 | # game loop continues to the next player move. THEN, with monster 177 | # still on screen, E>0, S will increment, then the condition is met 178 | # and the monster will move. 179 | 350 IFE>0THENS=S+1 180 | 360 IFS>1THENGOSUB830 181 | 182 | ==> GOSUB 830 183 | # Finally, the monsters move, if they've seen the player... 184 | <== RETURN FROM 830 185 | 186 | # Continue game! 187 | 370 GOTO270 188 | # == End of game loop 189 | 190 | 191 | #* SUB - Build the dungeon 192 | 380 PRINT INT((TM-TI)/60);"{LEFT} {UP}" #print the countdown 193 | 194 | #* Generate width/length of a room 195 | 400 W=INT(RND(1)*9+2):L=INT(RND(1)*9+2) 196 | #* RND(1) = 0.999 - .999 * 9 + 2 = 10 - so rooms no more than 10x10? perhaps? 197 | 198 | # Calculate where on map room will be 199 | 410 R0=INT(RND(1)*(RS-L-1))+1:C0=INT(RND(1)*(CS-W-1))+1:P=TS+40*R0+C0 200 | #* RS is 22, CS is 39 201 | # example R0 = 11, C0 = 15, P=31848+40*R0+C0 202 | $ R0 = 11 203 | $ C0 = 15 204 | $ P = 32303 205 | 206 | # If room size will take us beyond map limit, don't use this room 207 | 420 IFP+40*L+W>=TS+SZTHEN530 208 | # IF 32303+40*4+6 >= TS+SZ THEN (GOTO) 530 209 | 210 | # The GOTO 530 checks to see if we've run out of time 211 | # to build the dungeon. So the computer will keep 212 | # building until the countdown is over! 213 | 214 | #* This checks to see if this space is taken up. If it is, then we goto 530... 215 | 430 FORN=0TOL+1:FORN1=0TOW+1:IFPEEK(P+(N*40)+N1)<>32THEN530 216 | 440 NEXTN1,N 217 | 218 | #* This puts char160, which is a filled in solid block, into the room area. 219 | 450 FORN=1TOL:FORN1=1TOW:POKEP+(N*40)+N1,160:NEXTN1,N 220 | 221 | #* Generate vertical passages from generated room down. 222 | 460 FORN=P+42+(L*40)TOTS+999STEP40 223 | # FOR N = (P=start of room in memory) + 42 + (Length*40) to (TS = 31848)+999=32847 - Step every 40 224 | 470 IFPEEK(N)=160THENFORN1=P+42TONSTEP40:POKEN1,160:NEXT:POKEN1-80,102:GOTO490 225 | 480 NEXTN 226 | 227 | #* Generate horizontal passages from generate room right. 228 | 490 FORN=P+81+WTOP+121+W:IF(N-TS)/40=INT((N-TS)/40)THEN520 229 | 500 IFPEEK(N)=160THENFORN1=P+81TON-1:POKEN1,160:NEXT:POKEN1-1,102:GOTO520 230 | 510 NEXT 231 | 232 | #* Generate a monster in the room. Every room has a monster! 233 | 520 S=INT(RND(1)*L)+1:S1=INT(RND(1)*W+1):POKEP+S1+S*40,INT(RND(1)*6+214) 234 | 235 | # Timer check - if we've enough time, continue building rooms. 236 | 530 IFTI160THEN550 241 | 560 POKEU,135:HG=HG+1:NEXT 242 | 243 | #* Generate borders 244 | 570 FORR0=0TORS:POKETS+40*R0,127:POKETS+40*R0+CS,127:NEXTR0 245 | 580 FORC0=0TOCS:POKETS+C0,127:POKETS+C0+40*RS,127:NEXTC0 246 | 590 RETURN 247 | # === END SUB Generate dungeon 248 | 249 | 250 | # SUB == evaluate player move and what they see as a result 251 | # NOTE: K, J, M are not used! I think they're leftovers from when perhaps 252 | # Brian intended on implementing SM mode / SEE further mode! 253 | # That's why S costs HP - you were then supposed to see 2 squares in each direction. 254 | # He just never finished it, apparently. 255 | 600 K=-40:J=3:M=40:R=3:GN=0 256 | 610 IFSM=1THENK=-80:J=5:M=80:R=4:SM=0 257 | 258 | # O is meant to give the location to view from, taking into account where memory stops/starts. 259 | 620 O=L-32767-R 260 | 630 IFO+32811>33768THENM=0 261 | 262 | # Look at what is around from the dungeon map and put it on the screen 263 | 640 FORN=-40TO40STEP40:FORN1=1TO3:IFN=0ANDN1=2THEN820 264 | 650 Y=O+N+N1:V=PEEK(Y+TS):POKEY+AX,V 265 | 266 | # If we've only revealed a floor or space, continue with the N/N1 loop 267 | 660 IFV<135ORV=160THEN820 268 | 269 | # If we've revealed a monster, then go to that code (710) 270 | 670 V=V-128:IFV<>7THEN710 271 | 272 | # We've revealed gold - how much? Here's a funny by-product of this. Because we redo 273 | # this "what's seen" code, each time we repaint the gold, we recalculate K1! So it's 274 | # truly random as to what we might find, and that value will always change! Later on, 275 | # we use the latest value of K1 when we actually touch gold. (and repaint the screen.) 276 | # Cute and clever, Brian... you don't have to track the amount when you see it. Just 277 | # use the last value... 278 | 680 K1=1+K1+INT((MG+1)*(RND(1))) 279 | # GN and FG are about how often the message "GOLD IS NEAR" is displayed. 280 | # It's an odd way to set up a "we've seen this already" True/False flag. 281 | 690 GN=GN+1:IFGN>FGTHENGOSUB1410:PRINT"GOLD IS NEAR!":GOSUB1430:FG=HG+1 282 | 700 GOTO820 283 | 284 | # Revealed a monster - put a floor in its place on the dungeon map 285 | 710 V1=V+128:S=0:POKEY+TS,160 286 | # V is the character representing monster type. I is its HP. 287 | 720 IFV=86THENE$="SPIDER":I=3 288 | 730 IFV=87THENE$="GRUE":I=7 289 | 740 IFV=88THENE$="DRAGON":I=1 290 | 750 IFV=89THENE$="SNAKE":I=2 291 | 760 IFV=90THENE$="NUIBUS":I=9 292 | 770 IFV=91THENE$="WYVERN":I=5 293 | #Generate monster hp 294 | 780 I=INT(RND(1)*{Player HP}+(PX/I)+{Player HP}/4) 295 | # if we've generated a previous monster, E holds its position. QQ holds 296 | # what type of monster it is. Put it back on the dungeon map. 297 | # This means that only one monster at a time can be 'active' 298 | 790 IFE>0THENPOKETS+E,QQ 299 | 800 QQ=V+128:E=Y 300 | 810 GOSUB1410:PRINT"A "E$;" WITH";I;"POINTS IS NEAR.":GOSUB1430:CC=I 301 | 820 NEXTN1:NEXTN:FG=GN:RETURN 302 | # === END move/what do you see sub 303 | 304 | 305 | # SUB == Monster reaction/movement AI 306 | # The math here is a little baffling and there's a bug. The 307 | # bottom line is that it looks at player position vs. 308 | # monster position and tries to calculate the best move. 309 | # It's tracking itself across the screen, since it's been 310 | # "removed" from the dungeon memory map. 311 | 830 O1=0:A=0:E1=E+AX:IFABS(E1+40-L)128)THENO1=O1+A 314 | 860 IFABS(E1-1-L)128)THENO1=O1+A 317 | 890 A=O1:IFE1+A=LTHEN960 318 | # The following lines 900-950 baffle me. I'm not sure if Brian 319 | # programmed in some dumbness, but the overall math is wonky and 320 | # doesn't quite work. This is probably one of the areas I'd like to 321 | # "improve" at some point. 322 | 900 IFE1+A GOSUB 1500 355 | # Input routine - makes ? blink, puts value in L$ 356 | <== RETURN FROM 1500 357 | 358 | #* If yes, then monster disappears! 359 | 1080 IFL$="Y"THENMG=MG-MG/2:W=160:E=0:S=0:POKEL,209:RETURN 360 | 361 | #* if monster is still alive. 362 | 1090 IFCC>1THEN1160 363 | 364 | #* if monsters is dead 365 | 1100 GOSUB1410:W=160:S=0:E=0:POKEL,209:PRINT"THE "E$" IS DEAD!":GOSUB1430 366 | # Monsters HP is experience reward. Increment monster kill counter. 367 | 1110 EX=EX+I:Z=Z+1 368 | # If we haven't doubled our experience, then return 369 | 1120 IFEX