(The original first post in this thread follows).
CITADEL ITEM RANDOMISER
Going Rogue
INTRODUCTION
Item randomiser utilities are well known to aficionados of non-linear console games, being particularly common amongst Zelda and Metroid titles. They aim to breathe new life into well-worn classics by randomising the positions of items within the game, so a new and novel route needs to be found by the player in order to complete it.
Basic randomisers simply shuffle item locations brainlessly, which can lead to deadlocked situations in which the player cannot know for sure if a particular layout can be solved. The most simple example is when a key ends up stuck behind a door that requires that same key to open it. More advanced randomisers (including this one) incorporate solvers, which are able to check whether or not a particular permutation may be completed.
Originally written as a proof-of-concept in Ruby, this Citadel randomiser has been ported to BBC BASIC so it can be run directly on original hardware or from within an emulator.
SEEDS
Each supported permutation is represented by a single integer or "seed" in the range 1 to 999,999,999. The randomiser can either generate a solvable seed automatically, or the user may supply his or her own. Please note that since permutations are generated using BASIC's RND() keyword, seeds may not be compatible between different versions of BBC BASIC.
QUIRKS
Two items in the game are not randomised and are always available from their original locations. The first is the crystal obtained from the sarcophagus in The Pyramid, since this is not an item that appears on an item pad but rather is traded directly into the player's inventory. The second unrandomised item is the figurine on the alien planet. Once the Star Port has been destroyed, it is impossible to return to the alien planet to pick up any items that may have been left there. Choose your route wisely!
There is a "warp" in this game which involves taking a trampoline to The Ocean on the east side of The Temple (where the Egyptian statuette is usually found), and using it to climb up into the game's title screen. By falling off the right side of the title screen and down one more screen past The Ocean, then holding left as you fall further, it is possible to land on the Central Tower battlements and skip solving the puzzle that normally would require the bucket. The solver is aware of this trick, and may be configured to either accept or reject seeds that use this alternative to collecting the bucket. (This glitch may also be used to skip collecting the East Tower key, but currently the solver does not acknowledge this).
No special status is granted to the three secret rooms that normally contain the crowns. The player is expected to know where these are, and any item may appear in them. Additionally, players do not win £100 for collecting the three crowns.
Not all seeds will yield the maximum 99 points; however it should be possible to destroy the Star Port and prevent the alien invasion on any verified seed.
LIMITATIONS
This software was tested with the original BBC Micro Model B disc version Stairway To Hell disc image of Citadel (disc image "Citadel.ssd" having an MD5 of c03f242992408838157844070f08a6a4). Electron and BBC tape versions are not supported at this time, and it has not been tested on the "Play It Again, Sam" BBC version, either. At this time it has only been tried under emulation, and not on vintage hardware.
At present, this tool works by patching the CITAM file on the disc for each new seed, and as such requires write access to the disc. *** DO NOT USE IT ON AN ORIGINAL COPY OF THE GAME ***. Emulators will need to be configured to allow writes to the disc image. The software does have a facility for restoring the CITAM file to its original state (answer "N" to the "Randomise?" question). In-memory patching of the binary would be a neater solution, but this software is not currently capable of it.
The solver is written in BASIC, uses poor quality algorithms and is slow. On a standard BBC B, it takes several seconds to check each possible seed for solvability, and so in unlucky cases it may take a minute or two for it to find a suitable seed.
DISC IMAGE
This disc image contains a copy of the game with the randomiser utility added to the root directory:
Type CHAIN "RANDO" to start the randomiser. Once it has modified the CITAM file, you can CHAIN "CITADEL" to launch the game as normal. To re-randomise with a new seed or restore CITAM to its original state, simply press BREAK and then CHAIN "RANDO" again.
SOURCECode: Select all
10 MODE 7 20 CLEAR:RZ%=FALSE:AS%=FALSE:SD%=-1:IW%=TRUE:DIM ROOMS%(36):DIM DTA%(38):DIM INV%(38) 30 PRINT "Citadel Randomiser v0.1":PRINT "by 'Diminished', January 2018":PRINT 40 PROCfill 50 RZ%=FNyn("Randomise"):IF NOT RZ% PRINT "Restoring default items ...":GOTO 180 60 AS%=FNyn("Automatic randomisation seed"):IF AS% THEN 130 70 REPEAT:INPUT "Enter seed: " SD%:IF SD%<=0 PRINT "Seed must be a positive integer." 80 UNTIL SD%>0 90 PRINT:PROCshuffle(SD%) 100 IF FNsolve THEN 180 110 PRINT:PRINT CHR$(129);"Warning: This seed looks unsolvable!" 120 IW%=TRUE:IF NOT FNyn("Are you sure") THEN PRINT "Aborting.":END ELSE 180 130 IW%=FNyn("Allow island trampoline warp") 140 PRINT:REPEAT 150 GI%=RND(-TIME) 160 IF RZ% PROCshuffle(RND(999999999)) 170 UNTIL FNsolve 180 PROCsave 190 END 200 DEF FNyn(M$) 210 PRINT M$;" (Y/N)? "; 220 LOCAL A$ 230 REPEAT:A$=GET$:UNTIL A$="y" OR A$="n" OR A$="Y" OR A$="N" 240 PRINT A$ 250 =(A$="Y" OR A$="y") 260 DEF PROCdebug 270 PRINT 280 LOCAL LI%, LJ% 290 LJ%=0 300 FOR LI%=0 TO 37 310 PRINT ;LI%;" => ";INV%(LI%);" "; 320 IF LI% MOD 4=3 PRINT 330 IF INV%(LI%) LJ%=LJ%+1 340 NEXT 350 PRINT "------" 360 ENDPROC 370 DEF FNibr(RID%) 380 LOCAL LI%, LJ% 390 LJ%=-1 400 FOR LI%=0 TO 37 410 IF RID%=DTA%(LI%) LJ%=LI%:LI%=38 420 NEXT 430 =LJ% 440 DEF PROCfill 450 LOCAL LI%, LJ% 460 FOR LI%=0 TO 37 470 READ LJ% 480 DTA%(LI%)=LJ% 490 IF LI%<27 ROOMS%(LI%)=LJ% 500 IF LI%=28 ROOMS%(LI%-1)=LJ% 510 IF LI%>29 ROOMS%(LI%-2)=LJ% 520 NEXT 530 ENDPROC 540 DEF PROCshuffle(SEED%) 550 PRINT "Trying seed ";SEED%;" ... "; 560 LOCAL LI%, LJ% 570 LI%=RND(-SEED%) 580 FOR LI%=0 TO 37:DTA%(LI%)=-1:NEXT 590 FOR LI%=0 TO 35 600 REPEAT:LJ%=RND(36)-1:UNTIL DTA%(LJ%)<0 610 DTA%(LJ%)=ROOMS%(LI%) 620 NEXT 630 FOR LI%=37 TO 30 STEP -1 640 DTA%(LI%)=DTA%(LI%-2) 650 NEXT 660 DTA%(28)=DTA%(27) 670 DTA%(27)=&17 680 DTA%(29)=&E1 690 ENDPROC 700 DEF FNsolve 710 LOCAL AT%, LI% 720 AT%=0 730 FOR LI%=0 TO 37:INV%(LI%)=FALSE:NEXT 740 INV%(FNibr(&5E))=TRUE:INV%(FNibr(&84))=TRUE:INV%(FNibr(&81))=TRUE 750 INV%(FNibr(&6C))=TRUE:INV%(FNibr(&87))=TRUE:INV%(FNibr(&85))=TRUE 760 INV%(FNibr(&5C))=TRUE:INV%(FNibr(&82))=TRUE:INV%(FNibr(&70))=TRUE 770 INV%(FNibr(&58))=TRUE:INV%(FNibr(&83))=TRUE:INV%(FNibr(&5A))=TRUE 780 FOR AT%=0 TO 6 790 IF NOT INV%(17) THEN 810 800 INV%(FNibr(&86))=TRUE:IF INV%(24) AND INV%(30) INV%(FNibr(&72))=TRUE 810 IF NOT INV%(14) THEN 840 820 INV%(FNibr(&D5))=TRUE:IF INV%(2) INV%(FNibr(&D6))=TRUE 830 IF INV%(26) AND INV%(21) INV%(FNibr(&C4))=TRUE 840 IF NOT INV%(11) THEN 890 850 INV%(FNibr(&EA))=TRUE:INV%(FNibr(&68))=TRUE:INV%(FNibr(&64))=TRUE:INV%(FNibr(&1))=TRUE 860 IF INV%(0) OR INV%(1) INV%(FNibr(&29))=TRUE 870 IF INV%(0) AND INV%(1) AND INV%(25) AND INV%(7) AND INV%(8) AND INV%(9) AND INV%(10) AND INV%(3) INV%(FNibr(&5))=TRUE:GOTO 990 880 IF INV%(23) OR INV%(24) OR INV%(30) INV%(FNibr(&90))=TRUE:IF INV%(4) AND INV%(5) AND INV%(6) INV%(FNibr(&A6))=TRUE 890 IF NOT INV%(16) THEN 910 900 INV%(FNibr(&95))=TRUE:INV%(FNibr(&97))=TRUE:IF INV%(24) OR INV%(30) INV%(FNibr(&94))=TRUE 910 IF INV%(12) AND INV%(2) INV%(FNibr(&36))=TRUE 920 IF INV%(13) INV%(FNibr(&1C))=TRUE 930 IF INV%(15) INV%(FNibr(&6E))=TRUE:INV%(FNibr(&6F))=TRUE 940 IF INV%(20) INV%(FNibr(&B))=TRUE 950 IF IW% AND INV%(3) AND (INV%(24) OR INV%(30)) INV%(FNibr(&B))=TRUE 960 IF INV%(3) INV%(FNibr(&A3))=TRUE:INV%(FNibr(&CB))=TRUE:INV%(FNibr(&E4))=TRUE 970 NEXT 980 PRINT "No solution.":=FALSE 990 PRINT "OK!":=TRUE 1000 DEF PROCsave 1010 PRINT:IF NOT FNyn("OK to modify CITAM file") PRINT "Aborting.":END 1020 LOCAL LI%,FP% 1030 FP%=OPENUP("CITAM") 1040 IF FP%=0 PRINT 'CHR$(129);"ERROR:";CHR$(135);"CITAM file was not found.":PRINT '"Aborting.":END 1050 PTR#FP%=&480 1060 FOR LI%=0 TO 38 1070 IF EOF#FP% PRINT 'CHR$(129);"ERROR:";CHR$(135);"EOF while writing CITAM.":PRINT '"Aborting.":CLOSE#FP%:END 1080 BPUT#FP%,DTA%(LI%) 1090 NEXT 1100 CLOSE#FP% 1110 PRINT '"All done!" 1120 ENDPROC 1130 DATA &72,&D5,&97,&6E,&01,&5E,&1C,&6F,&0B,&A6,&C4,&84,&68,&81,&36,&6C 1140 DATA &87,&95,&05,&86,&85,&5C,&64,&82,&CB,&E4,&EA,&17,&70,&E1,&58,&83 1150 DATA &A3,&29,&5A,&90,&94,&D6