├── .github ├── FUNDING.yml ├── build └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bios.z80 ├── c ├── globals.h ├── handlers.c ├── inventory.c ├── items.c ├── main.c ├── util.c └── world.c ├── encrypt.c ├── game.z80 ├── prn.z80 └── version.z80 /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: skx 3 | custom: https://steve.fi/donate/ 4 | -------------------------------------------------------------------------------- /.github/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | apt-get update 4 | 5 | which pasmo || apt-get install -y pasmo 6 | which gcc || apt-get install -y build-essential 7 | which make || apt-get install -y make 8 | 9 | make release game-spectrum 10 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: release 2 | name: Handle Release 3 | jobs: 4 | upload: 5 | name: Upload release 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@master 9 | - name: Generate the artifacts 10 | uses: skx/github-action-build@master 11 | - name: Upload 12 | uses: skx/github-action-publish-binaries@master 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | with: 16 | args: "*.com *.tap" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lighthouse 2 | encrypt 3 | *.tap 4 | *.com 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Our version comes from the GITHUB_REF environmental variable, 2 | # if not set then we're running from "unreleased-git" 3 | VERSION := $(or ${GITHUB_REF},${GITHUB_REF},"unreleased-git") 4 | 5 | 6 | all: lighthouse game-cpm game-spectrum 7 | 8 | 9 | # Build the C version 10 | lighthouse: c/handlers.c c/inventory.c c/items.c c/main.c c/world.c c/util.c c/globals.h 11 | gcc -o lighthouse -Os -Wall -Wextra -Werror c/handlers.c c/inventory.c c/items.c c/main.c c/world.c c/util.c 12 | 13 | 14 | # Clean our generated output 15 | clean: 16 | rm -f lighthouse *.com *.tap encrypt || true 17 | 18 | 19 | # Build the encryption helper 20 | encrypt: encrypt.c 21 | gcc -o encrypt -Wall -Werror encrypt.c 22 | 23 | 24 | # Format our C-code 25 | format: 26 | astyle --style=allman -A1 --indent=spaces=4 --break-blocks --pad-oper --pad-header --unpad-paren --max-code-length=200 c/*.c c/*.h 27 | 28 | 29 | # version.z80 will contain the last part of the string $VERSION 30 | version: 31 | echo "DB \"$$(echo ${VERSION} | awk -F/ '{print $$NF}' )\"" > version.z80 32 | 33 | 34 | 35 | # build the game for CP/M 36 | game-cpm: game.z80 bios.z80 version Makefile 37 | pasmo --equ ENTRYPOINT=100h --equ ENCRYPT_STRINGS=0 --equ SPECTRUM=0 game.z80 lihouse.com 38 | 39 | # build the game for CP/M with encrypted strings/code 40 | game-cpm-encrypted: game.z80 bios.z80 version Makefile encrypt 41 | pasmo --equ ENTRYPOINT=100h --equ ENCRYPT_STRINGS=1 --equ SPECTRUM=0 game.z80 lihouse.com 42 | ./encrypt lihouse.com lihousex.com 43 | rm lihouse.com 44 | 45 | 46 | 47 | # build the game for the ZX Spectrum 48 | game-spectrum: game.z80 bios.z80 version Makefile 49 | pasmo --tapbas --equ ENTRYPOINT=32768 --equ ENCRYPT_STRINGS=0 --equ SPECTRUM=1 game.z80 lihouse.tap 50 | 51 | # build the game for the ZX Spectrum with encrypted strings 52 | game-spectrum-encrypted: game.z80 bios.z80 version Makefile encrypt 53 | pasmo --tapbas --equ ENTRYPOINT=32768 --equ ENCRYPT_STRINGS=1 --equ SPECTRUM=1 game.z80 lihouse.tap 54 | ./encrypt -crc lihouse.tap lihousex.tap 55 | rm lihouse.tap 56 | 57 | 58 | # Build the game for release - with strings encrypted 59 | release: version 60 | make game-cpm-encrypted 61 | make game-cpm 62 | make game-spectrum-encrypted 63 | make game-spectrum 64 | 65 | 66 | # Run the CP/M version via runcpm 67 | run-cpm: game-cpm 68 | ~/cpm/cpm lihouse 69 | 70 | # Run the CP/M version via runcpm, with the encrypted version of the code 71 | run-cpm-encrypted: game-cpm-encrypted 72 | ~/cpm/cpm lihouseX 73 | 74 | # Run the spectrum version 75 | run-spectrum: game-spectrum 76 | xspect -quick-load -load-immed -tap lihouse.tap 77 | 78 | # Run the spectrum version, with strings encrypted 79 | run-spectrum-encrypted: game-spectrum-encrypted 80 | xspect -quick-load -load-immed -tap lihousex.tap 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Lighthouse of Doom 2 | 3 | This repository contain a simple text-based adventure game, implemented 4 | twice, once in portable C, and once in Z80 assembly language. 5 | 6 | The Z80 version of the game will run under the CP/M operating system, and the humble 48k ZX Spectrum. 7 | 8 | My intention was to write a simple text-based adventure game to run under CP/M. Starting large projects in Z80 assembly language from scratch is a bit of a daunting prospect, so I decided to code the game in C first, so that I could get the design right, and avoid getting stuck in too many low-level details initially. Later I ported to the Spectrum, because it seemed like a fun challenge for myself! 9 | 10 | Quick links within this README file: 11 | 12 | * [Play Online](#play-online) 13 | * [Plot of the game](#game-plot) 14 | * [C Implementation](#c-implementation) 15 | * [Z80 Implementation](#z80-implementation) 16 | * [Z80 Changes](#z80-changes) 17 | * [Memory Map](#memory-map) 18 | * [Compiling & running it](#compiling--running-it) 19 | * [Downloading It](#downloading-it) 20 | * [Bugs?](#bugs) 21 | 22 | 23 | 24 | ## Play Online 25 | 26 | Thanks to the excellent [jsspeccy](https://github.com/gasman/jsspeccy3) ZX Spectrum emulator you can play this game with your browser here:

27 | 28 | * [https://steve.fi/software/lighthouse/](https://steve.fi/software/lighthouse/) 29 | 30 | 31 | 32 | ## Game Plot 33 | 34 | * You're inside a lighthouse (trapped? doesn't really matter I guess). 35 | * You see a boat on the horizon, heading your way. 36 | * But "oh no!", the lighthouse is dark. 37 | * The boat will surely crash if you don't turn on the main light. 38 | 39 | The game is over, when you either fix the light, or find another solution. 40 | 41 | If you do not achieve victory within a turn-limit the boat runs aground, and 42 | death will consume you all. (It is a _very_ big boat!) 43 | 44 | 45 | 46 | ## C Implementation 47 | 48 | The implementation is mostly concerned with creating the correct series of 49 | data-structures, which are essentially arrays of objects. Because if we 50 | can make the game table-based we simplify the coding that needs to be 51 | done - we don't need to write per-object handlers anywhere, we can just 52 | add pointers to tables/structures. 53 | 54 | The C implementation defines most of the important things in the file [globals.h](c/globals.h) such as: 55 | 56 | * The structure to define a location. 57 | * The structure to define an object. 58 | 59 | The game-state itself is stored in a couple of global variables, there isn't 60 | too much state to care about: 61 | 62 | * The current location (i.e. index into location-table). 63 | * A list of any items you're carrying. 64 | * The number of turns you've taken. 65 | * Incremented by one each time you enter a command, be it recognized or not. 66 | * Whether you won/lost. 67 | 68 | 69 | 70 | ## Z80 Implementation 71 | 72 | The Z80 implementation is based upon [the C-implementation](c/), with a few small changes. 73 | 74 | The implementation uses a simple set of structures: 75 | 76 | * A command-table to map input-commands to handlers. 77 | * An item-table to store details about each object in the game. 78 | * A person table to store telephone messages. 79 | 80 | The main implementation can be found in the file [game.z80](game.z80), 81 | but because we support two targets (CP/M 2.x and the ZX Spectrum) there 82 | is a small amount of platform-specific code found in [bios.z80](bios.z80). 83 | 84 | The `Makefile` should build everything appropriately for both systems, 85 | defining `SPECTRUM`, and `ENTRYPOINT` as appropriate. 86 | 87 | 88 | ### Z80 Changes 89 | 90 | * Along the way I realized that having fixed inventory slots made the coding more of a challenge, so I made the location of each object a property of the object itself. 91 | * The Z80 version has more easter-eggs (Try typing "`xyzzy`" a few times). 92 | * There is rudimentary support for text-wrapping. 93 | * Enter `WRAP 80` to wrap output around column 80. 94 | * Enter `WRAP` to view the current wrapping value. 95 | * There are __two__ victory conditions. 96 | * The game can be built with the text-strings, and game code, protected by simple XOR encryption: 97 | * This stops users from looking through the binary for hints. 98 | * Run `make release` to build both "normal" and "protected" versions of the release. 99 | * The encrypted versions of the games have an X suffix in their filenames. 100 | 101 | 102 | ### Memory Map 103 | 104 | Roughly speaking the game consists of two parts: 105 | 106 | * The driver/game which is about 3k of code. 107 | * The text of the game along with the state of the world (flags, items carried, etc), which is approximately 9k. 108 | 109 | All told the binaries for both CP/M and the ZX Spectrum are approximately 14k. 110 | 111 | The layout in memory is basically the same for both variants, however the starting address is different: 112 | 113 | * CP/M 114 | * Game loaded between the range 256-14000 115 | * [0x0100-0x3500] 116 | * Copy of state made to 53248 / 0xD000 117 | * ZX Spectrum 118 | * Game loaded between the range 32768-46000 119 | * [0x5000-0xB400] 120 | * Copy of state made to 53248 - 0xD000 121 | 122 | When the game starts a copy of the "state" is made to `0xD000` before anything has been modified, and then when the game is started that state is copied back to where it is used. This consists of all content between `per_game_state_start` and the end of the file. This has the location, flags, and similar, as well as all the static-text which will not change between runs. However copying this region was easier than just copying, or otherwise resetting, the state that changes during play. 123 | 124 | No doubt things will change over time, however the (undocumented) `BIOS` command will show you rough sizes for the various parts of the game. 125 | 126 | 127 | 128 | ## Compiling & Running It 129 | 130 | Ensure you have the `pasmo` assembler installed, and then use the supplied Makefile to compile the game. 131 | 132 | Running `make` will generate the default targets, if you wish to build only individual things then : 133 | 134 | * `make game-cpm` to build a normal CP/M version. 135 | * `make game-cpm-encrypted` to build an encrypted CP/M version. 136 | * `make game-spectrum` to build the ZX Spectrum version. 137 | * `make game-spectrum-encrypted` to build the encrypted ZX Spectrum version. 138 | * `make lighthouse` will build the C-game for Linux 139 | * `make release` will build both versions of the CP/M and ZX Spectrum release. 140 | 141 | If you have a working golang system you can run the game under this trivial CP/M emulator: 142 | 143 | * https://github.com/skx/cpmulator 144 | 145 | 146 | 147 | ## Downloading It 148 | 149 | If you look on our [release page](https://github.com/skx/lighthouse-of-doom/releases/) you can find the latest stable build. 150 | 151 | * For CP/M download `lihouse.com` to your system, and then run `LIHOUSE` to launch it. 152 | * `lihousex.com` is the encrypted version. 153 | * For the ZX Spectrum download `lihouse.tap` to your system, and then launch in your favourite emulator. 154 | * `lihousex.tap` is the encrypted version. 155 | 156 | 157 | 158 | ## Bugs? 159 | 160 | Report any bugs as you see them: 161 | 162 | * A crash of the game is a bug. 163 | * Bad spelling, grammar, or broken punctuation are also bugs. 164 | * Getting into a zombie-state where winning or losing are impossible is a bug. 165 | 166 | 167 | 168 | Steve 169 | -------------------------------------------------------------------------------- /bios.z80: -------------------------------------------------------------------------------- 1 | ; bios.z80 2 | ; 3 | ; This file contains the common primitives we use for interfacing 4 | ; with CP/M or the ZX Spectrum (48k). 5 | ; 6 | ; We don't use many primitives, but we do need some facilities from 7 | ; the operating system, or the Spectrum ROM: 8 | ; 9 | ; Pause for a keypress. 10 | ; 11 | ; Clear the screen. 12 | ; 13 | ; Output a string. 14 | ; 15 | ; Allow the user to enter input 16 | ; 17 | ; Pause for a "small delay" 18 | ; 19 | ; The routines here will work for both systems, and contain the only 20 | ; platform-specific code we need. 21 | ; 22 | 23 | IF SPECTRUM 24 | MAX_INPUT_LENGTH: EQU 30 25 | ELSE 26 | MAX_INPUT_LENGTH: EQU 78 27 | ENDIF 28 | 29 | ; 30 | ; Initialization routine for the BIOS, if needed 31 | ; {{ 32 | bios_init: 33 | 34 | IF SPECTRUM 35 | ld a,2 36 | call 5633 ; ROM_OPEN_CHANNEL 37 | call c_chan 38 | ld a,4 39 | call 5633 ; ROM_OPEN_CHANNEL 40 | 41 | ENDIF 42 | ret 43 | ; }} 44 | 45 | ; 46 | ; Delay for "a short while". 47 | ; 48 | ; This is used for showing "dots" in the SLEEP command, or when the 49 | ; grue is threatening you.. 50 | ; 51 | ; {{ 52 | bios_delay: 53 | 54 | IF SPECTRUM 55 | PUSH_ALL 56 | ld b,4 57 | wait: 58 | halt 59 | djnz wait 60 | POP_ALL 61 | ret 62 | 63 | ELSE 64 | ; Random values found by experimentation with my own system 65 | ld hl,0xffff / 8 66 | hl_delay_loop: 67 | inc hl 68 | ld de, 0xffff - 10 69 | de_delay_loop: 70 | inc de 71 | ld a, d 72 | or e 73 | jr nz, de_delay_loop 74 | ld a,h 75 | or l 76 | jr nz, hl_delay_loop 77 | ret 78 | 79 | ENDIF 80 | ;; }} 81 | 82 | 83 | 84 | ; 85 | ; Clear the screen. 86 | ; 87 | ; {{ 88 | bios_clear_screen: 89 | 90 | IF SPECTRUM 91 | PUSH_ALL 92 | ld a,2 93 | call 0x1601 ; ROM_OPEN_CHANNEL 94 | call 0x0DAF ; ROM_CLS 95 | 96 | ; 97 | ; The code above will clear the screen, however we use a custom 98 | ; printing routine which has its own notion of the X,Y / AT 99 | ; coordinates to use. 100 | ; 101 | ; We explicitly reset those here, otherwise the screen will be clear, 102 | ; but printing will start at whatever spot it finished the previous 103 | ; time something was output. 104 | ; 105 | xor a 106 | ld hl,atflg 107 | ld (hl),a 108 | ld hl, row 109 | ld (hl),a 110 | ld hl, col 111 | ld (hl),a 112 | POP_ALL 113 | ret 114 | 115 | ELSE 116 | ld de, cpm_clear_screen_msg 117 | call bios_output_string 118 | ret 119 | 120 | cpm_clear_screen_msg: 121 | db 27, "[2J" ; "clear" 122 | db 27, "[H" ; "home" 123 | db "$" 124 | ENDIF 125 | ; }} 126 | 127 | ; 128 | ; Await a keypress. The pressed-key will be returned in the A-register. 129 | ; 130 | ; {{ 131 | bios_await_keypress: 132 | 133 | IF SPECTRUM 134 | push hl 135 | ld hl, 0x5C08 ; LASTK - Set by the Spectrum ROM 136 | ld a,255 137 | ld (hl),a 138 | await_key: 139 | cp (hl) 140 | jr z,await_key 141 | 142 | ; get the key, and return it. 143 | ld a,(hl) 144 | pop hl 145 | ret 146 | ELSE 147 | ld c, 0x01 148 | call 0x0005 149 | ret 150 | ENDIF 151 | ; }} 152 | 153 | 154 | ; 155 | ; Output a single character, stored in the E register. 156 | ; 157 | ; {{ 158 | bios_output_character: 159 | PUSH_ALL 160 | IF SPECTRUM 161 | ; output the character 162 | ld a,e 163 | RST 0x10 164 | ELSE 165 | ld c, 0x02 166 | call 0x005 167 | ENDIF 168 | POP_ALL 169 | ret 170 | ; }} 171 | 172 | ; 173 | ; Output a string, terminated by "$". 174 | ; 175 | ; The address of the string is stored in DE. 176 | ; 177 | ; To handle wrapping we process the string character by character, 178 | ; this is inefficient, but works fine for this use-case. 179 | ; 180 | ; {{ 181 | bios_output_string: 182 | 183 | IF SPECTRUM 184 | PUSH_ALL 185 | ld a,4 186 | call 5633 ; ROM_OPEN_CHANNEL 187 | POP_ALL 188 | ENDIF 189 | 190 | PUSH_ALL 191 | 192 | ; DE has the string 193 | ; We'll work on HL, and use BC to keep track of the width 194 | ; of the string we've printed thus-far 195 | push de 196 | pop hl 197 | 198 | ; length will be stored here 199 | print_reset_line: 200 | ld bc, 0 201 | 202 | print_char_loop: 203 | ; get a character 204 | ld a,(hl) 205 | inc hl 206 | inc bc 207 | 208 | ; end of printing? 209 | cp '$' 210 | jr z, end_of_print 211 | 212 | ; linefeed? Reset our printing width 213 | cp 0x0d 214 | jr nz, not_linefeed 215 | ld e, 0x0d 216 | call bios_output_character 217 | IF SPECTRUM 218 | ELSE 219 | ld e, 0x0a 220 | call bios_output_character 221 | ENDIF 222 | jr print_reset_line 223 | 224 | not_linefeed: 225 | ; We'll ignore the newline 226 | cp 0x0a 227 | jr z, print_char_loop 228 | 229 | ; is it a space? 230 | cp ' ' 231 | jr nz, print_continues 232 | 233 | ; if our length is bigger than the width we force 234 | ; a newline, otherwise we keep going 235 | push af 236 | push hl 237 | ld hl, WRAP_LOCATION 238 | ld a,(hl) 239 | pop hl 240 | cp c 241 | jr c, too_wide 242 | pop af 243 | 244 | print_continues: 245 | ld e,a 246 | call bios_output_character 247 | jr print_char_loop 248 | 249 | too_wide: 250 | pop af 251 | 252 | PUSH_ALL 253 | ld e, 0x0d 254 | call bios_output_character 255 | IF SPECTRUM 256 | ELSE 257 | ld e, 0x0a 258 | call bios_output_character 259 | ENDIF 260 | POP_ALL 261 | jr print_reset_line 262 | end_of_print: 263 | POP_ALL 264 | ret 265 | ; }} 266 | 267 | ; 268 | ; Prompt the user for a line of input. 269 | ; 270 | ; The way this works is you pass the address of a region of memory, 271 | ; in the DE register. The first byte is the length of the buffer. 272 | ; 273 | ; On return the second byte of the buffer will be populated by the 274 | ; amount of text which was read, then the input itself: 275 | ; 276 | ; DE points to the memory buffer; 277 | ; 278 | ; 0x00 - buffer size 279 | ; 0x01 - read-length 280 | ; 0x02 ... char.. 281 | ; {{ 282 | bios_read_input: 283 | IF SPECTRUM 284 | PUSH_ALL 285 | 286 | ; Move the cursor to the bottom of the screen, and display the prompt 287 | PUSH_ALL 288 | call move_to_bottom 289 | ld e, ">" 290 | call bios_output_character 291 | POP_ALL 292 | 293 | push de 294 | pop hl ; buffer -> hl 295 | 296 | inc hl ; skip to the returned size 297 | inc de 298 | 299 | xor a 300 | ld (hl),a ; size is now zero 301 | inc hl ; point to the start of the character buffer 302 | 303 | read_input_again: 304 | ; read a char 305 | call bios_await_keypress 306 | 307 | ; return? Then we're done 308 | cp 13 309 | jp z, read_input_over 310 | 311 | ; delete? remove the last character 312 | cp 12 313 | jr z, backspace 314 | 315 | ; escape? trash the input and start again. 316 | cp 7 317 | jr z, reset_line 318 | 319 | ; too many characters? 320 | push de 321 | push af 322 | ld a,(de) ; get the count of characters we've entered 323 | cp MAX_INPUT_LENGTH ; ignore input if too long 324 | jr nc,too_many_characters 325 | jr z, too_many_characters 326 | pop af 327 | pop de 328 | jr continue_input 329 | 330 | too_many_characters: 331 | ; too many characters, cleanup stack and ignore the character 332 | pop af 333 | pop de 334 | jr read_input_again 335 | 336 | continue_input: 337 | 338 | ; display the new character 339 | push de 340 | ld e,a 341 | call bios_output_character 342 | pop de 343 | 344 | ; add the character to the buffer 345 | ld (hl),a 346 | inc hl 347 | 348 | ; increase the input count 349 | ld a,(de) 350 | inc a 351 | ld (de),a 352 | 353 | ; repeat 354 | jr read_input_again 355 | 356 | backspace: 357 | ; remove size of the line by one 358 | ; unless it was empty 359 | ld a,(de) 360 | cp 0 361 | jr z, read_input_again 362 | dec a 363 | ld (de),a 364 | 365 | ; remove character 366 | dec hl 367 | ld (hl), 0x00 368 | 369 | PUSH_ALL 370 | 371 | ; move cursor to start 372 | call move_to_bottom 373 | 374 | ; output spaces - to overwrite what was previously input 375 | ; 376 | ; NOTE: We assume our input was a single line. 377 | ld b, MAX_INPUT_LENGTH+1 378 | ld e, " " 379 | spaces: 380 | call bios_output_character 381 | djnz spaces 382 | 383 | ; move to the bottom, and show the prompt again 384 | call move_to_bottom 385 | 386 | ; show the prompt 387 | ld e, ">" 388 | call bios_output_character 389 | POP_ALL 390 | 391 | ; save registers 392 | push hl 393 | push de 394 | push bc 395 | 396 | ; is the input line empty? If so return, 397 | ; cleaning up the stack. 398 | ld a,(de) 399 | cp 0 400 | jr nz, non_empty 401 | pop bc 402 | pop de 403 | pop hl 404 | jr read_input_again 405 | non_empty: 406 | ; OK we're gonna show the input the user 407 | ; entered. 408 | ld b,a 409 | push de 410 | pop hl 411 | inc hl 412 | loopy: 413 | ld e,(hl) 414 | call bios_output_character 415 | inc hl 416 | djnz loopy 417 | pop bc 418 | pop de 419 | pop hl 420 | jp read_input_again 421 | 422 | reset_line: 423 | ; clear the screen, and restart the input process 424 | call bios_clear_screen 425 | POP_ALL 426 | jp bios_read_input 427 | 428 | read_input_over: 429 | ; move the cursor to the top of the screen, and 430 | ; prepare for output again. 431 | call bios_clear_screen 432 | 433 | POP_ALL 434 | ret 435 | 436 | move_to_bottom: 437 | ld a,22 ; AT code. 438 | rst 16 ; Set the cursor-coord 439 | ld a,21 ; vertical coord. 440 | rst 16 ; set the vertical coord 441 | xor a ; horizontal position. 442 | rst 16 ; set the horizontal coord. 443 | ret 444 | ELSE 445 | push de 446 | ld de, prompt_message 447 | call bios_output_string 448 | pop de 449 | ld c, 0x0A 450 | call 0x005 451 | ret 452 | prompt_message: 453 | db 0x0a, 0x0d,">$" 454 | ENDIF 455 | ; }} 456 | -------------------------------------------------------------------------------- /c/globals.h: -------------------------------------------------------------------------------- 1 | #ifndef _GLOBALS 2 | #define _GLOBALS 3 | 4 | // Maximum named items in a room 5 | #define MAX_ITEMS_PER_ROOM 5 6 | 7 | // The maximum number of items you can carry 8 | #define MAX_INV 5 9 | 10 | // The number of rooms 11 | #define MAX_ROOMS 4 12 | 13 | // Unused function argument 14 | #define UNUSED(x) (void)(x) 15 | 16 | // Handler for an input/command function. 17 | typedef void (*actionPtr)(char *input); 18 | 19 | // Handler for "using" an object 20 | typedef void (*usePtr)(char *item); 21 | 22 | // Handler to use to "take" an object 23 | typedef void (*takePtr)(int id); 24 | 25 | // Handler to use to "drop" an object 26 | typedef void (*dropPtr)(int id); 27 | 28 | // This structure contains the details of a single location. 29 | // 30 | // Our "game world" is made up of a collection of these locations. 31 | typedef struct location 32 | { 33 | // one-line description of the location. 34 | char desc[100]; 35 | 36 | // extended description of the location. 37 | char edesc[512]; 38 | 39 | // have we seen this? If so we show the short-description 40 | // of the location when we enter it. 41 | int seen; 42 | 43 | // Items present in this location 44 | int items[MAX_ITEMS_PER_ROOM]; 45 | 46 | } location_t; 47 | 48 | 49 | 50 | // This structure contains all the properties and details of a single item. 51 | typedef struct object 52 | { 53 | // The name of the item 54 | char name[17]; 55 | 56 | // A one-line description of the item, shown when a room is entered. 57 | char desc[100]; 58 | 59 | // Extended description, used for the output of "EXAMINE XXX". 60 | char edesc[255]; 61 | 62 | // function to call when this item is used. 63 | usePtr use; 64 | 65 | // function to call when this item is used, while in the player's 66 | // possession. (`use` is only when an item is used in the same location.) 67 | usePtr use_carried; 68 | 69 | // Function to call when the play picks up this object. 70 | takePtr get_fn; 71 | 72 | // Function to call when the play drops up this object. 73 | dropPtr drop_fn; 74 | 75 | } object_t; 76 | 77 | 78 | 79 | // Word contains a command the user can type 80 | typedef struct word 81 | { 82 | // The name of the command the user will enter 83 | char name[10]; 84 | 85 | // Help information for this command, if present. 86 | char txt[32]; 87 | 88 | // Is this hidden? If so it is not shown in the output 89 | // of the help-command. 90 | int hidden; 91 | 92 | // The handler to invoke when that is typed 93 | actionPtr ptr; 94 | } word_t; 95 | 96 | 97 | // Game state 98 | enum gameState { Playing, Won, Dead }; 99 | 100 | // The player's location 101 | extern int location; 102 | 103 | // Count of elapsed turns 104 | extern int turn; 105 | 106 | // The player's inventory 107 | extern int inv[MAX_INV]; 108 | 109 | // Dead? Won? 110 | extern enum gameState state; 111 | 112 | // world locations 113 | extern location_t world[]; 114 | 115 | // World items 116 | extern object_t items[]; 117 | 118 | // Built-in commands 119 | extern word_t dictionary[]; 120 | 121 | 122 | // Forward declarations 123 | 124 | // utils.c 125 | char *object_from_input(char *input); 126 | 127 | // handlers.c 128 | void cls_fn(char *input); 129 | void call_fn(char *input); 130 | void down_fn(char *input); 131 | void drop_fn(char *input); 132 | void examine_fn(char *input); 133 | void get_fn(char *input); 134 | void help_fn(char *input); 135 | void inventory_fn(char *input); 136 | void language_fn(char *input); 137 | void look_fn(char *input); 138 | void magic_fn(char *input); 139 | void open_fn(char *input); 140 | void quit_fn(char *input); 141 | void up_fn(char *input); 142 | void use_fn(char *input); 143 | 144 | 145 | // items.c 146 | int is_object_present(char *name); 147 | void location_add_item(char *name, int location); 148 | void location_remove_item(char *name, int location); 149 | void get_book(int id); 150 | void get_torch(int id); 151 | void get_torch_lit(int id); 152 | void use_torch(char *txt); 153 | void use_torch_carried(char *txt); 154 | void get_mirror(int id); 155 | void get_mirror_broken(int id); 156 | void get_rug(int id); 157 | void use_mirror(char *txt); 158 | void use_mirror_broken(char *txt); 159 | void get_generator(int id); 160 | void use_generator(char *txt); 161 | void use_generator_carried(char *txt); 162 | void use_telephone(char *txt); 163 | void drop_book(int id); 164 | void drop_mirror(int id) ; 165 | void drop_mirror_broken(int id) ; 166 | void drop_torch(int id) ; 167 | void drop_torch_lit(int id) ; 168 | void drop_generator(int id); 169 | 170 | // inventory.c 171 | int inventory_has_item(char *name); 172 | void inventory_take_item(char *name); 173 | void inventory_drop_item(char *name); 174 | 175 | #endif 176 | -------------------------------------------------------------------------------- /c/handlers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "globals.h" 6 | 7 | 8 | 9 | // Word will have a term the user can enter, and a function-pointer 10 | // of code to invoke. 11 | // 12 | // If a word is "hidden:1" it is not shown in the output of help. 13 | word_t dictionary[] = 14 | { 15 | { name: "DOWN", txt: "Descend the stairs", ptr: down_fn }, 16 | { name: "DROP", txt: "Drop an item", ptr: drop_fn }, 17 | { name: "EXAMINE", txt: "Examine an object or item", ptr: examine_fn }, 18 | { name: "GET", txt: "Take an item", ptr: get_fn }, 19 | { name: "HELP", txt: "Show some help", ptr: help_fn }, 20 | { name: "INVENTORY", txt: "See what you're carrying", ptr: inventory_fn }, 21 | { name: "LOOK", txt: "Look at your surroundings", ptr: look_fn }, 22 | { name: "OPEN", txt: "Open an item", ptr: open_fn }, 23 | { name: "QUIT", txt: "Quit the game", ptr: quit_fn }, 24 | { name: "UP", txt: "Climb the stairs", ptr: up_fn }, 25 | { name: "USE", txt: "Use an item", ptr: use_fn }, 26 | 27 | // Synonyms 28 | { name: "TAKE", hidden: 1, ptr: get_fn }, 29 | { name: "PICKUP", hidden: 1, ptr: get_fn }, 30 | { name: "READ", hidden: 1, ptr: examine_fn }, 31 | { name: "EXIT", hidden: 1, ptr: quit_fn }, 32 | { name: "BYE", hidden: 1, ptr: quit_fn }, 33 | 34 | // Hidden-commands 35 | { name: "CLS", hidden: 1, ptr: cls_fn}, 36 | 37 | // Easter-Eggs 38 | { name: "CALL", hidden: 1, ptr: call_fn}, 39 | { name: "DIAL", hidden: 1, ptr: call_fn}, 40 | { name: "PHONE", hidden: 1, ptr: call_fn}, 41 | { name: "FUCK", hidden: 1, ptr: language_fn}, 42 | { name: "SHIT", hidden: 1, ptr: language_fn}, 43 | { name: "XYZZY", hidden: 1, ptr: magic_fn}, 44 | 45 | // Abbreviations 46 | { name: "L", hidden: 1, ptr: look_fn }, 47 | { name: "INV", hidden: 1, ptr: inventory_fn }, 48 | { name: "I", hidden: 1, ptr: inventory_fn }, 49 | { name: "U", hidden: 1, ptr: up_fn }, 50 | { name: "D", hidden: 1, ptr: down_fn }, 51 | { name: "", ptr: NULL }, 52 | }; 53 | 54 | 55 | void cls_fn(char *input) 56 | { 57 | UNUSED(input); 58 | 59 | printf("\e[1;1H\e[2J"); 60 | } 61 | 62 | void call_fn(char *input) 63 | { 64 | if (!is_object_present("telephone")) 65 | { 66 | printf("There is no telephone here!\n"); 67 | return; 68 | } 69 | 70 | if ((strstr(input, "999") != NULL) || 71 | (strstr(input, "911") != NULL) || 72 | (strstr(input, "POLICE") != NULL)) 73 | { 74 | printf("Trying to call the police was a smart move, but Adventure Bay\n" 75 | "does not have a well-funded police-service.\n"); 76 | return; 77 | } 78 | 79 | if ((strstr(input, "PAW") != NULL) && 80 | (strstr(input, "PATROL") != NULL)) 81 | { 82 | printf("The Paw Patrol are already on a roll, and cannot be disturbed.\n"); 83 | return; 84 | } 85 | 86 | } 87 | 88 | void inventory_fn(char *input) 89 | { 90 | UNUSED(input); 91 | 92 | int found = 0; 93 | 94 | for (int i = 0; i < MAX_INV; i++) 95 | { 96 | 97 | // this slot is not empty? 98 | if (inv[i] != -1) 99 | { 100 | found = 1; 101 | } 102 | } 103 | 104 | if (found) 105 | { 106 | printf("You are carrying:\n"); 107 | 108 | for (int i = 0; i < MAX_INV; i++) 109 | { 110 | 111 | // this slot is not empty? 112 | int item = inv[i]; 113 | 114 | if (item != -1) 115 | { 116 | printf("\t%s\n", items[item].desc); 117 | } 118 | } 119 | } 120 | else 121 | { 122 | printf("You are not carrying anything.\n"); 123 | } 124 | } 125 | 126 | void language_fn(char *input) 127 | { 128 | UNUSED(input); 129 | 130 | printf("Such bad language!\n"); 131 | } 132 | 133 | void magic_fn(char *input) 134 | { 135 | UNUSED(input); 136 | 137 | static int count = 0; 138 | count++; 139 | 140 | if (count == 1) 141 | { 142 | printf("Magic happens.\n"); 143 | } 144 | else if (count == 2) 145 | { 146 | printf("Magic intensifies.\n"); 147 | } 148 | else if (count == 3) 149 | { 150 | printf("The sensation of magic screaming through your veins gives you a heady rush.\n"); 151 | } 152 | else if (count == 4) 153 | { 154 | printf("You couldn't draw the line, could you?\n"); 155 | printf("The magic flooding your body is too powerful, and you're finding it impossible\n" 156 | "to breath. With a wail of frustration you topple backwards.\n" 157 | "\nYou're dead.\n"); 158 | state = Dead; 159 | } 160 | } 161 | 162 | void look_fn(char *input) 163 | { 164 | printf("You are in %s\n\n", world[location].desc); 165 | 166 | // First time we enter a room we show the full 167 | // description. 168 | // 169 | // Or if the user types "LOOK" 170 | int full = 0; 171 | 172 | if (world[location].seen == 0 || 173 | strcmp(input, "LOOK") == 0) 174 | { 175 | full = 1; 176 | } 177 | 178 | // We've shown the short description, return unless 179 | // we're showing everything 180 | if (!full) 181 | return; 182 | 183 | printf("%s\n", world[location].edesc); 184 | 185 | int found = 0; 186 | 187 | for (int i = 0; i < MAX_ITEMS_PER_ROOM; i++) 188 | { 189 | if (world[location].items[i] != -1) 190 | { 191 | found = 1; 192 | } 193 | } 194 | 195 | if (found) 196 | { 197 | printf("You see:\n"); 198 | 199 | for (int i = 0; i < MAX_ITEMS_PER_ROOM; i++) 200 | { 201 | int id = world[location].items[i]; 202 | 203 | if (id != -1) 204 | { 205 | if (strlen(items[id].desc) > 0) 206 | printf("\t%s\n", items[id].desc); 207 | } 208 | } 209 | } 210 | 211 | world[location].seen = 1; 212 | } 213 | 214 | void quit_fn(char *input) 215 | { 216 | UNUSED(input); 217 | 218 | printf("Game over man, game over!\n"); 219 | exit(0); 220 | } 221 | 222 | void help_fn(char *input) 223 | { 224 | UNUSED(input); 225 | 226 | int dict = 0; 227 | printf("The following commands are available:\n"); 228 | 229 | while (strlen(dictionary[dict].name) > 0) 230 | { 231 | if (dictionary[dict].hidden != 1) 232 | { 233 | printf("\t%s\n\t %s\n", dictionary[dict].name, dictionary[dict].txt); 234 | } 235 | 236 | dict++; 237 | } 238 | } 239 | 240 | void up_fn(char *input) 241 | { 242 | 243 | // basement - no torch 244 | if (location == 4) 245 | { 246 | 247 | if ((rand() % 10) >= 6) 248 | { 249 | printf("You cannot see where you're going, but it seems like the\n" 250 | "smell is getting stronger\n"); 251 | return; 252 | } 253 | else 254 | { 255 | printf("The smell is definitely closer now.\n"); 256 | printf("You panic, and fall to the ground.\n"); 257 | printf("The darkness is a mercy, as the grue attacks..\n"); 258 | printf("Too bad the ship will crash; more food for the grue though.\n"); 259 | state = Dead; 260 | } 261 | } 262 | 263 | // basement - with torch 264 | if (location == 3) 265 | { 266 | location = 2; 267 | look_fn(input); 268 | return; 269 | } 270 | 271 | // ground 272 | if (location == 2) 273 | { 274 | location = 1; 275 | look_fn(input); 276 | return; 277 | } 278 | 279 | // middle 280 | if (location == 1) 281 | { 282 | location = 0; 283 | look_fn(input); 284 | return; 285 | } 286 | 287 | // top 288 | printf("You cannot go up from here!\n"); 289 | } 290 | 291 | void down_fn(char *input) 292 | { 293 | if (location == 0) 294 | { 295 | location = 1; 296 | look_fn(input); 297 | return; 298 | } 299 | 300 | if (location == 1) 301 | { 302 | location = 2; 303 | look_fn(input); 304 | return; 305 | } 306 | 307 | if (location == 2) 308 | { 309 | 310 | // we're on the ground floor 311 | // if the trapdoor is open you can go down 312 | if (is_object_present("trapdoor-open")) 313 | { 314 | 315 | if (inventory_has_item("torch-lit")) 316 | { 317 | location = 3; 318 | look_fn(input); 319 | } 320 | else 321 | { 322 | location = 4; 323 | look_fn(input); 324 | } 325 | 326 | return; 327 | 328 | } 329 | } 330 | 331 | if (location == 4) 332 | { 333 | if ((rand() % 10) >= 6) 334 | { 335 | printf("You cannot see where you're going, but it seems like the\n" 336 | "smell is getting stronger\n"); 337 | return; 338 | } 339 | else 340 | { 341 | printf("The smell is definitely closer now.\n"); 342 | printf("You panic, and fall to the ground.\n"); 343 | printf("The darkness is a mercy, as the grue attacks..\n"); 344 | printf("Too bad the ship will crash; more food for the grue though.\n"); 345 | state = Dead; 346 | } 347 | } 348 | 349 | printf("You cannot go down from here!\n"); 350 | } 351 | 352 | // examine an object 353 | void examine_fn(char *input) 354 | { 355 | // We'll be called with "EXAMINE XXXX" - so we need to find 356 | // the item. 357 | char *itm = object_from_input(input); 358 | 359 | if (itm == NULL) 360 | { 361 | printf("You need to tell me what to examine!\n"); 362 | return; 363 | } 364 | 365 | // 366 | // Right see if this item is in the user's possession 367 | // 368 | for (int i = 0; i < MAX_INV; i++) 369 | { 370 | 371 | // The item 372 | int carried = inv[i]; 373 | 374 | // Is the item present? With the same name? 375 | // 376 | // Here we cap the comparison length to allow "mirror" to 377 | // match both "mirror" and "mirror-broken". 378 | // 379 | if (carried != -1 && strncmp(itm, items[carried].name, strlen(itm)) == 0) 380 | { 381 | if (strlen(items[carried].edesc) > 0) 382 | { 383 | printf("%s\n", items[carried].edesc); 384 | } 385 | else 386 | { 387 | printf("You see nothing special.\n"); 388 | } 389 | 390 | free(itm); 391 | return; 392 | } 393 | } 394 | 395 | // 396 | // Look for the item in the environment 397 | // 398 | for (int i = 0; i < MAX_ITEMS_PER_ROOM; i++) 399 | { 400 | 401 | // The item 402 | int present = world[location].items[i]; 403 | 404 | // Is this slot full of something? 405 | if (present != -1) 406 | { 407 | // Here we cap the comparison length to allow "mirror" to 408 | // match both "mirror" and "mirror-broken". 409 | if (strncmp(itm, items[present].name, strlen(itm)) == 0) 410 | { 411 | if (strlen(items[present].edesc) > 0) 412 | { 413 | printf("%s\n", items[present].edesc); 414 | } 415 | else 416 | { 417 | printf("You see nothing special.\n"); 418 | } 419 | 420 | free(itm); 421 | return; 422 | } 423 | } 424 | } 425 | 426 | printf("It doesn't look like that item is present, or in your inventory!\n"); 427 | free(itm); 428 | return; 429 | 430 | } 431 | 432 | // get 433 | void get_fn(char *input) 434 | { 435 | 436 | // Get the object the user wants to take 437 | char *itm = object_from_input(input); 438 | 439 | if (itm == NULL) 440 | { 441 | printf("You need to tell me what to take!\n"); 442 | return; 443 | } 444 | 445 | // 446 | // Right we need to look over all the items in the location, 447 | // and see if there is a match. 448 | // 449 | // We need to match "TORCH" with "TORCH" and "TORCH-LIT", for example 450 | // 451 | for (int i = 0; i < MAX_ITEMS_PER_ROOM; i++) 452 | { 453 | int item = world[location].items[i]; 454 | 455 | // Does the name match? 456 | // 457 | // torch matches "torch" and "torch-lit" 458 | // mirror matches "mirror" and "mirror-broken" 459 | if (strncmp(items[item].name, itm, strlen(itm)) == 0) 460 | { 461 | // Is the item collectible? If so call the handler 462 | if (items[item].get_fn != NULL) 463 | { 464 | 465 | // Call the handler. 466 | (*items[item].get_fn)(item); 467 | 468 | // add to inventory 469 | inventory_take_item(items[item].name); 470 | 471 | // remove from world 472 | location_remove_item(items[item].name, location); 473 | } 474 | else 475 | { 476 | printf("You cannot take that!\n"); 477 | } 478 | 479 | free(itm); 480 | return; 481 | } 482 | } 483 | 484 | printf("That item doesn't seem to be here.\n"); 485 | } 486 | 487 | // drop 488 | void drop_fn(char *input) 489 | { 490 | // Get the object the user wants to drop 491 | char *itm = object_from_input(input); 492 | 493 | if (itm == NULL) 494 | { 495 | printf("You need to tell me what to drop!\n"); 496 | return; 497 | } 498 | 499 | // 500 | // Right we need to look over all the items in the users' 501 | // inventory and see if there is a match. 502 | // 503 | // We need to match "TORCH" with "TORCH" and "TORCH-LIT", for example 504 | // 505 | for (int i = 0; i < MAX_INV; i++) 506 | { 507 | int item = inv[i]; 508 | 509 | // Does the name match? 510 | // 511 | // torch matches "torch" and "torch-lit" 512 | // mirror matches "mirror" and "mirror-broken" 513 | if (strncmp(items[item].name, itm, strlen(itm)) == 0) 514 | { 515 | // Is the item droppable? If so call the handler 516 | if (items[item].drop_fn != NULL) 517 | { 518 | // Call the handler. 519 | (*items[item].drop_fn)(item); 520 | } 521 | else 522 | { 523 | printf("You cannot drop that!\n"); 524 | } 525 | 526 | free(itm); 527 | return; 528 | } 529 | } 530 | 531 | free(itm); 532 | printf("You're not carrying that!\n"); 533 | } 534 | 535 | void open_fn(char *input) 536 | { 537 | 538 | if (strstr(input, "TRAPDOOR") != NULL) 539 | { 540 | 541 | if (is_object_present("trapdoor-closed")) 542 | { 543 | 544 | printf("The trapdoor opens, and you see a shadowy set of stairs\n" 545 | "leading downward into what is obviously a basement.\n"); 546 | 547 | location_add_item("trapdoor-open", location); 548 | location_remove_item("trapdoor-closed", location); 549 | } 550 | else 551 | { 552 | printf("I see no trapdoor here.\n"); 553 | } 554 | 555 | return; 556 | } 557 | 558 | printf("Opening that doesn't make sense!\n"); 559 | } 560 | 561 | void use_fn(char *input) 562 | { 563 | // 564 | // OK when this is called we'll be "USE XXXX" 565 | // 566 | // We want to find the item, if it exists. 567 | // 568 | // If the item is not found we say "We can't use XXXX" 569 | // 570 | // If the item is found it either needs to be: 571 | // 572 | // A. IN the location 573 | // 574 | // B. In the inventory 575 | // 576 | // We'll be called with "USE XXXX" - so we need to find 577 | // the item. 578 | char *itm = object_from_input(input); 579 | 580 | if (itm == NULL) 581 | { 582 | printf("You need to tell me what to use!\n"); 583 | return; 584 | } 585 | 586 | // 587 | // Right see if this item is in the user's possession 588 | // 589 | for (int i = 0; i < MAX_INV; i++) 590 | { 591 | 592 | // The item 593 | int carried = inv[i]; 594 | 595 | // Is the item present? With the same name? 596 | // 597 | // Here we cap the comparison length to allow "mirror" to 598 | // match both "mirror" and "mirror-broken". 599 | // 600 | if (carried != -1 && strncmp(itm, items[carried].name, strlen(itm)) == 0) 601 | { 602 | if (items[carried].use_carried != NULL) 603 | { 604 | (*items[carried].use_carried)(itm); 605 | } 606 | else 607 | { 608 | printf("Nothing happens\n"); 609 | } 610 | 611 | free(itm); 612 | return; 613 | } 614 | } 615 | 616 | // 617 | // Look for the item in the environment 618 | // 619 | for (int i = 0; i < MAX_ITEMS_PER_ROOM; i++) 620 | { 621 | 622 | // The item 623 | int present = world[location].items[i]; 624 | 625 | // Is this slot full of something? 626 | if (present != -1) 627 | { 628 | // Here we cap the comparison length to allow "mirror" to 629 | // match both "mirror" and "mirror-broken". 630 | if (strncmp(itm, items[present].name, strlen(itm)) == 0) 631 | { 632 | if (items[present].use != NULL) 633 | { 634 | (*items[present].use)(itm); 635 | } 636 | else 637 | { 638 | printf("Nothing happens\n"); 639 | } 640 | 641 | free(itm); 642 | return; 643 | } 644 | } 645 | } 646 | 647 | printf("It doesn't look like that item is present, or in your inventory!\n"); 648 | free(itm); 649 | return; 650 | } 651 | -------------------------------------------------------------------------------- /c/inventory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "globals.h" 6 | 7 | //****************************** 8 | // 9 | // Inventory Management 10 | // 11 | 12 | // does the user have this item in their inventory? 13 | int inventory_has_item(char *name) 14 | { 15 | for (int i = 0; i < MAX_INV ; i++) 16 | { 17 | int id = inv[i]; 18 | 19 | if (id != -1) 20 | { 21 | if (strcmp(items[id].name, name) == 0) 22 | { 23 | return 1; 24 | } 25 | } 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | // add the item to the inventory 32 | // return 0 if we can take it 33 | void inventory_take_item(char *name) 34 | { 35 | 36 | for (int slot = 0 ; slot < MAX_INV; slot ++) 37 | { 38 | 39 | // found an empty slot? 40 | if (inv[slot] == -1) 41 | { 42 | 43 | // find the item by name 44 | int id = 0; 45 | 46 | while (strlen(items[id].name) > 0) 47 | { 48 | if (strcmp(items[id].name, name) == 0) 49 | { 50 | 51 | // take it 52 | inv[slot] = id; 53 | return; 54 | } 55 | 56 | id++; 57 | } 58 | 59 | printf("Failed to find item to take:%s\n", name); 60 | } 61 | } 62 | 63 | printf("You're carrying too much.\n"); 64 | } 65 | 66 | // remove item from the inventory 67 | void inventory_drop_item(char *name) 68 | { 69 | 70 | // for each item we could carry 71 | for (int i = 0; i < MAX_INV ; i++) 72 | { 73 | 74 | // get the item 75 | int id = inv[i]; 76 | 77 | if (id != -1) 78 | { 79 | 80 | // if it matches - drop it 81 | if (strcmp(items[id].name, name) == 0) 82 | { 83 | inv[i] = -1; 84 | return ; 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /c/items.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Items.c - List of global objects 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "globals.h" 9 | 10 | object_t items[] = 11 | { 12 | {name: "generator", desc: "A small generator.", use: use_generator, use_carried: use_generator_carried, get_fn: get_generator, drop_fn: drop_generator}, 13 | { 14 | name: "mirror" 15 | , desc: "A small mirror." 16 | , 17 | edesc: "The mirror doesn't seem to be anything special.\n" 18 | "But your reflection? It looks fabulous.\n" 19 | , 20 | use: 21 | use_mirror, use_carried: 22 | use_mirror, get_fn: 23 | get_mirror, drop_fn: 24 | drop_mirror 25 | }, 26 | { 27 | name: "mirror-broken" 28 | , desc: "A small mirror, which is cracked and broken." 29 | , edesc: "The mirror looks like it was once small and delicate.\n" 30 | "But now it shows a distorted reflection of yourself,\n" 31 | "which is oddly unsettling.\n" 32 | , 33 | use: 34 | use_mirror_broken, use_carried: 35 | use_mirror_broken, get_fn: 36 | get_mirror_broken, drop_fn: 37 | drop_mirror_broken 38 | }, 39 | {name: "rug", desc: "A small rug.", get_fn: get_rug}, 40 | { 41 | name: "book" 42 | , desc: "A small black book." 43 | , edesc: 44 | "The little black book seems to contain names and phone numbers:\n\n" 45 | "\t Police - 999\n" 46 | "\tAmbulance - 999\n" 47 | "\tFire Service - 999\n" 48 | "\tPaw Patrol - 999\n" 49 | "\nToo bad there are no instructions on powering the main light.\n" 50 | , 51 | get_fn: 52 | get_book, drop_fn: 53 | drop_book 54 | }, 55 | {name: "desk", desc: "", edesc: "The desk looks solid, but old."}, 56 | {name: "painting", desc: "", edesc: "The painting shows a teenager with spiky hair, surrounded by a group of dogs."}, 57 | {name: "telephone", desc: "A telephone, wired to the wall.", use: use_telephone }, 58 | {name: "torch", desc: "A small torch.", use: use_torch, use_carried: use_torch_carried, get_fn: get_torch, drop_fn: drop_torch }, 59 | 60 | {name: "torch-lit", desc: "A small torch, which is lit.", get_fn: get_torch_lit, drop_fn: drop_torch_lit }, 61 | {name: "trapdoor-closed", desc: "A closed trapdoor.", edesc: "You cannot see anything special about the trapdoor.\nPerhaps you should open it to explore further?"}, 62 | {name: "trapdoor-open", desc: "An open trapdoor.", edesc: "You cannot see anything special about the open trapdoor.\n"}, 63 | {name: "", desc: ""}, 64 | }; 65 | 66 | 67 | 68 | //****************************** 69 | // 70 | // Add/Remove/Test for items in a location 71 | // 72 | 73 | // is the named object present? 74 | int is_object_present(char *name) 75 | { 76 | 77 | for (int i = 0; i < MAX_ITEMS_PER_ROOM; i++) 78 | { 79 | 80 | int item = world[location].items[i]; 81 | 82 | if ((item != -1) && 83 | (strcmp(items[item].name, name) == 0)) 84 | { 85 | return 1; 86 | } 87 | } 88 | 89 | return 0; 90 | } 91 | 92 | // add a named item to the given location 93 | void location_add_item(char *name, int location) 94 | { 95 | int id = 0; 96 | 97 | while (strlen(items[id].name) > 0) 98 | { 99 | 100 | if (strcmp(items[id].name, name) == 0) 101 | { 102 | 103 | // found the item 104 | for (int i = 0; i < MAX_ITEMS_PER_ROOM; i++) 105 | { 106 | if (world[location].items[i] == -1) 107 | { 108 | world[location].items[i] = id; 109 | return; 110 | } 111 | } 112 | 113 | printf("FAILED TO ADD %s to location - it is full\n", name); 114 | } 115 | 116 | id++; 117 | } 118 | 119 | printf("Failed to find item %s\n", name); 120 | } 121 | 122 | // remove the named item to the given location 123 | void location_remove_item(char *name, int location) 124 | { 125 | 126 | // find the item-id 127 | int id = 0; 128 | 129 | while (strlen(items[id].name) > 0) 130 | { 131 | 132 | if (strcmp(items[id].name, name) == 0) 133 | { 134 | 135 | // found the item - with ID: "id" 136 | // remove it 137 | for (int i = 0; i < MAX_ITEMS_PER_ROOM; i++) 138 | { 139 | if (world[location].items[i] == id) 140 | { 141 | world[location].items[i] = -1; 142 | return; 143 | } 144 | } 145 | 146 | printf("FAILED TO ADD %s to location - it is full\n", name); 147 | } 148 | 149 | id++; 150 | } 151 | 152 | printf("Failed to find item to remove %s\n", name); 153 | } 154 | 155 | 156 | void drop_book(int id) 157 | { 158 | printf("You drop the small black book.\n"); 159 | inventory_drop_item(items[id].name); 160 | location_add_item(items[id].name, location); 161 | } 162 | 163 | void drop_mirror(int id) 164 | { 165 | printf("You drop the mirror, which cracks and breaks.\n"); 166 | inventory_drop_item(items[id].name); 167 | location_add_item("mirror-broken", location); 168 | } 169 | void drop_mirror_broken(int id) 170 | { 171 | printf("You drop the broken mirror, more carefully this time.\n"); 172 | inventory_drop_item(items[id].name); 173 | location_add_item(items[id].name, location); 174 | } 175 | void drop_torch(int id) 176 | { 177 | printf("You drop the torch.\n"); 178 | inventory_drop_item(items[id].name); 179 | location_add_item(items[id].name, location); 180 | } 181 | void drop_torch_lit(int id) 182 | { 183 | printf("You drop the torch.\n"); 184 | inventory_drop_item(items[id].name); 185 | location_add_item(items[id].name, location); 186 | } 187 | void drop_generator(int id) 188 | { 189 | printf("You place the generator down on the ground.\n"); 190 | inventory_drop_item(items[id].name); 191 | location_add_item(items[id].name, location); 192 | } 193 | 194 | 195 | void get_book(int id) 196 | { 197 | UNUSED(id); 198 | 199 | printf("You pickup the book.\n"); 200 | } 201 | void get_torch(int id) 202 | { 203 | UNUSED(id); 204 | 205 | printf("You pickup the torch.\n"); 206 | } 207 | void get_torch_lit(int id) 208 | { 209 | UNUSED(id); 210 | 211 | printf("You pickup the lit torch.\n"); 212 | } 213 | 214 | void get_rug(int id) 215 | { 216 | UNUSED(id); 217 | 218 | printf("You pickup the rug, and as you do so you notice a trapdoor underneath it.\n"); 219 | location_add_item("trapdoor-closed", location); 220 | } 221 | 222 | // When the user "USES TORCH" it becomes lit 223 | void use_torch(char *txt) 224 | { 225 | UNUSED(txt); 226 | 227 | location_remove_item("torch", location); 228 | location_add_item("torch-lit", location); 229 | 230 | printf("The torch turns on.\n"); 231 | } 232 | 233 | // This is invoked when the user "USE TORCH", when carrying the torch. 234 | void use_torch_carried(char *txt) 235 | { 236 | inventory_drop_item("torch"); 237 | inventory_take_item("torch-lit"); 238 | 239 | printf("The torch turns on.\n"); 240 | 241 | // If we're in the dark room move to the lit basement 242 | if (location == 4) 243 | { 244 | location = 3; 245 | look_fn(txt); 246 | } 247 | } 248 | 249 | void get_generator(int id) 250 | { 251 | UNUSED(id); 252 | 253 | printf("You take the generator, struggling under the weight.\n"); 254 | } 255 | void use_generator(char *txt) 256 | { 257 | UNUSED(txt); 258 | 259 | printf("You study the diagram drawn on the side of the generator,\n" 260 | "and connect it to the side of the lighting console.\n"); 261 | printf("With a mighty pull of the starting mechanism the machine\n" 262 | "comes to life, and power returns to the console\n\n"); 263 | printf("The lighthouse beam of light starts to rotate, and in the\n" 264 | "distance you hear a horn from the approaching boat.\n"); 265 | printf("It obviously sees you, and starts to turn. It looks like\n" 266 | "everything is going to be OK!\n"); 267 | state = Won; 268 | } 269 | 270 | void use_generator_carried(char *txt) 271 | { 272 | UNUSED(txt); 273 | 274 | printf("You try to use the generator, but that seems impossible while\n" 275 | "you're still carrying it."); 276 | } 277 | 278 | void get_mirror(int id) 279 | { 280 | UNUSED(id); 281 | 282 | printf("You pickup the mirror.\n"); 283 | } 284 | void get_mirror_broken(int id) 285 | { 286 | UNUSED(id); 287 | 288 | printf("You pickup the broken mirror.\n"); 289 | } 290 | void use_mirror(char *txt) 291 | { 292 | UNUSED(txt); 293 | 294 | printf("The mirror doesn't seem to be anything special.\n" 295 | "But your reflection? It looks fabulous.\n"); 296 | } 297 | void use_mirror_broken(char *txt) 298 | { 299 | UNUSED(txt); 300 | 301 | printf("The mirror looks like it was once small and delicate.\n" 302 | "But now it shows a distorted reflection of yourself,\n" 303 | "which is oddly unsettling.\n"); 304 | 305 | } 306 | 307 | void use_telephone(char *txt) 308 | { 309 | UNUSED(txt); 310 | 311 | printf("To use the telephone you must CALL somebody!\n"); 312 | } 313 | -------------------------------------------------------------------------------- /c/main.c: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "globals.h" 10 | 11 | 12 | // The player's location 13 | int location = 0; 14 | 15 | // Count of elapsed turns 16 | int turn = 0; 17 | 18 | // The game-state 19 | enum gameState state; 20 | 21 | // The player's inventory 22 | int inv[MAX_INV] = {0}; 23 | 24 | 25 | 26 | int main(int argc, char *argv[]) 27 | { 28 | UNUSED(argc); 29 | UNUSED(argv); 30 | 31 | srand(time(NULL)); 32 | char line[1024]; 33 | 34 | // all rooms are empty 35 | for (int r = 0 ; r <= MAX_ROOMS; r++) 36 | { 37 | for (int i = 0; i < MAX_ITEMS_PER_ROOM; i++) 38 | { 39 | world[r].items[i] = -1; 40 | } 41 | } 42 | 43 | // The play is carrying nothing 44 | for (int i = 0; i < MAX_INV; i++) 45 | { 46 | inv[i] = -1; 47 | } 48 | 49 | // add items to the world, in the appropriate location. 50 | location_add_item("torch", 0); 51 | location_add_item("mirror", 1); 52 | location_add_item("desk", 1); 53 | location_add_item("painting", 1); 54 | location_add_item("telephone", 1); 55 | location_add_item("book", 1); 56 | location_add_item("rug", 2); 57 | location_add_item("generator", 3); 58 | 59 | // Show the opening location 60 | look_fn("LOOK"); 61 | 62 | while (state == Playing) 63 | { 64 | 65 | // show prompt 66 | printf(">"); 67 | fflush(stdout); 68 | 69 | // read line 70 | if (fgets(line, sizeof(line) - 1, stdin) == NULL) 71 | { 72 | printf("Ctrl-D pressed; aborting\n"); 73 | return 0; 74 | } 75 | 76 | // strip newlines 77 | for (size_t i = 0; i < sizeof(line) - 1; i++) 78 | { 79 | if (line[i] == '\n' || 80 | line[i] == '\r') 81 | { 82 | line[i] = '\0'; 83 | } 84 | } 85 | 86 | if (strlen(line) < 1) 87 | { 88 | goto ok; 89 | } 90 | 91 | // upper-case for consistency 92 | size_t len = strlen(line); 93 | 94 | for (size_t i = 0; i < len; i++) 95 | { 96 | line[i] = toupper(line[i]); 97 | } 98 | 99 | // Can we split this into a word? 100 | int dict = 0; 101 | 102 | while (strlen(dictionary[dict].name) > 0) 103 | { 104 | 105 | // If we got a match on this word 106 | if (strncmp(dictionary[dict].name, line, strlen(dictionary[dict].name)) == 0) 107 | { 108 | // Call the handler 109 | (*dictionary[dict].ptr)(line); 110 | 111 | // And prepare for more input 112 | goto end; 113 | } 114 | 115 | dict++; 116 | } 117 | 118 | // handlers now .. 119 | printf("I'm sorry, I did not understand: %s\n", line); 120 | end: 121 | 122 | turn ++; 123 | 124 | if (turn % 10 == 0) 125 | { 126 | printf("\nThe ship is coming closer, you had best hurry up!\n\n"); 127 | } 128 | 129 | printf("\n"); 130 | ok: 131 | memset(line, '\0', sizeof(line)); 132 | } 133 | 134 | printf("Game over in %d turns\n", turn); 135 | } 136 | -------------------------------------------------------------------------------- /c/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * util.c - Utility functions 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | // Get the name of the object from input such as: 10 | // 11 | // USE TORCH 12 | // CALL RYDER 13 | // DROP CAKE 14 | // 15 | char *object_from_input(char *input) 16 | { 17 | 18 | char seps[] = " \t"; 19 | char *token; 20 | char *obj = NULL; 21 | 22 | /* Establish string and get the first token: */ 23 | token = strtok(input, seps); 24 | 25 | while (token != NULL) 26 | { 27 | if (obj) 28 | free(obj); 29 | 30 | obj = strdup(token); 31 | 32 | /* Get next token: */ 33 | token = strtok(NULL, seps); 34 | } 35 | 36 | if (obj != NULL) 37 | { 38 | for (size_t i = 0; i < strlen(obj); i++) 39 | { 40 | obj[i] = tolower(obj[i]); 41 | } 42 | } 43 | 44 | return obj; 45 | } 46 | -------------------------------------------------------------------------------- /c/world.c: -------------------------------------------------------------------------------- 1 | /* 2 | * World.c - List of locations 3 | */ 4 | 5 | #include "globals.h" 6 | 7 | 8 | /* 9 | * The world 10 | */ 11 | location_t world[] = 12 | { 13 | // 0 14 | { 15 | desc: "the top floor of the lighthouse." 16 | , 17 | edesc: 18 | "The lighthouse has a spiral staircase which runs from top to bottom.\n" 19 | "Through the window you can see the lights of an approach ship, and\n" 20 | "you know that it will crash upon the rocks if you can't signal it away.\n" 21 | "Too bad the lighthouse light doesn't seem to be working, it looks like\n" 22 | "there's a problem with the power.\n" 23 | "" 24 | , 25 | }, 26 | 27 | // 1 28 | { 29 | desc: "the middle floor of the lighthouse." 30 | , 31 | edesc: 32 | "The middle floor of the lighthouse seems to be a relaxation-room,\n" 33 | "you see some comfy chairs, a work-desk, as well as various odds and\n" 34 | "ends.\n\n" 35 | "An impressive painting hangs over the desk.\n" 36 | , 37 | }, 38 | 39 | // 2 40 | { 41 | desc: "the ground floor of the lighthouse." 42 | , 43 | edesc: "The ground floor seems very crowded, with most of the room\n" 44 | "taken up by a coat-rack, boots, and similar things\n" 45 | , 46 | }, 47 | 48 | // 3 49 | { 50 | desc: "the lighthouse basement." 51 | , 52 | edesc: "This seems to be a graveyard for discarded machinery, and\n" 53 | "oddly enough old children's toys.\n" 54 | , 55 | }, 56 | 57 | // 4 58 | { 59 | desc: "a dark place." 60 | , 61 | edesc: "You cannot see anything, but you can hear a snarling.\n" 62 | "There is also a strong smell, a feral smell, could it be that this\n" 63 | "is the haunt of a grue?" 64 | , 65 | }, 66 | }; 67 | -------------------------------------------------------------------------------- /encrypt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Generate an encrypted version of the specified input file. 3 | * 4 | * Optionally update the CRC check of the file - this is necessary 5 | * for the .TAP version we generate for the ZX Spectrum. 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | typedef unsigned char byte; 14 | 15 | 16 | // calculate the checksum for the given region 17 | byte crc(byte *start, int length) 18 | { 19 | byte tcrc = 0; 20 | 21 | for (int i = 0; i < length; i++) 22 | tcrc = tcrc ^ start[i]; 23 | 24 | return tcrc; 25 | } 26 | 27 | int main(int argc, char *argv[]) 28 | { 29 | // check argument count 30 | if (argc != 3 && argc != 4) 31 | { 32 | printf("Usage: encrypt [-crc] input output\n"); 33 | return 1; 34 | } 35 | 36 | // flags / arguments 37 | int do_crc = 0; 38 | char *input = NULL; 39 | char *output = NULL; 40 | 41 | // look for flags 42 | if (argc == 4) 43 | { 44 | // Should we run a CRC update? 45 | for (int i = 1; i < argc ; i++) 46 | { 47 | if (strcmp(argv[i], "-crc") == 0) 48 | { 49 | do_crc = 1; 50 | } 51 | } 52 | } 53 | 54 | // look for input/output names. 55 | for (int i = 1; i < argc ; i++) 56 | { 57 | // Ignore flags 58 | if (strcmp(argv[i], "-crc") == 0) 59 | { 60 | continue; 61 | } 62 | 63 | // input file goes first 64 | if (input == NULL) 65 | { 66 | input = argv[i]; 67 | continue; 68 | } 69 | 70 | // output file goes second. 71 | if (output == NULL) 72 | { 73 | output = argv[i]; 74 | continue; 75 | } 76 | } 77 | 78 | printf("Input file: %s\n", input); 79 | printf("Output file: %s\n", output); 80 | 81 | if (do_crc) 82 | { 83 | printf("CRC will be updated\n"); 84 | } 85 | else 86 | { 87 | printf("No CRC update\n"); 88 | } 89 | 90 | // Open the file for reading 91 | FILE *f = fopen(input, "r"); 92 | 93 | if (f == NULL) 94 | { 95 | printf("Failed to open input file: %s\n", input); 96 | return 1; 97 | } 98 | 99 | // get file size 100 | fseek(f, 0L, SEEK_END); 101 | size_t sz = ftell(f); 102 | 103 | printf("File size is %ld bytes\n", sz); 104 | 105 | // get back to start 106 | fseek(f, 0L, SEEK_SET); 107 | 108 | // allocate memory 109 | unsigned char *buf = malloc(sz); 110 | 111 | if (buf == NULL) 112 | { 113 | printf("failed to allocate memory\n"); 114 | return 1; 115 | } 116 | 117 | // read the file 118 | int n = fread(buf, sizeof(unsigned char), sz, f); 119 | 120 | if (ferror(f) != 0) 121 | { 122 | printf("error reading\n"); 123 | return 1; 124 | } 125 | 126 | if (n != sz) 127 | { 128 | printf("short read\n"); 129 | return 1; 130 | } 131 | 132 | fclose(f); 133 | 134 | // Look for our flag 135 | for (int i = 0; i < sz; i++) 136 | { 137 | if ((buf[i] == 'S') && 138 | (buf[i + 1] == 'K') && 139 | (buf[i + 2] == 'X')) 140 | { 141 | 142 | printf("Found start of section to encrypt.\n"); 143 | 144 | // starting key 145 | char k = '%'; 146 | 147 | // XOR with our key, and increase that key 148 | for (int pos = i ; pos < sz; pos++) 149 | { 150 | buf[pos] ^= k; 151 | k++; 152 | } 153 | 154 | if (do_crc) 155 | { 156 | // Now we have to fixup the CRC 157 | unsigned char *pos = buf; 158 | 159 | for (int i = 1; i < sz; i++) 160 | { 161 | size_t blocksize = pos[0] | (pos[1] << 8); 162 | 163 | if ((pos + blocksize + 1) < (buf + sz)) 164 | { 165 | if (blocksize > 1) 166 | { 167 | char ccrc = crc(&pos[2], blocksize - 1); 168 | 169 | if (pos[blocksize + 1] != ccrc) 170 | { 171 | printf("Updated CRC\n"); 172 | pos[blocksize + 1] = ccrc; 173 | } 174 | } 175 | } 176 | 177 | 178 | pos += blocksize + 2; 179 | } 180 | } 181 | 182 | // Write 183 | FILE *nw = fopen(output, "w"); 184 | 185 | if (nw == NULL) 186 | { 187 | printf("Failed to open file for writing: %s\n", output); 188 | } 189 | 190 | fwrite(buf, sz, 1, nw); 191 | fclose(nw); 192 | 193 | printf("All done\n"); 194 | return 0; 195 | 196 | } 197 | } 198 | 199 | printf("Failed to find encryption start position\n"); 200 | return 1; 201 | } 202 | -------------------------------------------------------------------------------- /game.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; LIGHTHOUSE OF DOOM!!! 3 | ; 4 | ; Steve Kemp 5 | ; - 6 | ; https://steve.kemp.fi/ 7 | ; https://github.com/skx/lighthouse-of-doom 8 | ; 9 | ; 10 | ; This is a port of the lighthouse-of-doom game from C to Z80 assembly, such 11 | ; that it will run under CP/M 2.x and the 48k ZX Spectrum. 12 | ; 13 | ; Although the game has been ported that process has resulted in some changes 14 | ; to the text, and the command-handlers. If you've played the C game, and 15 | ; completed it, there will be no significant new surprises. 16 | ; 17 | ; Rather than having a global player-inventory store, and a place 18 | ; in each location for object storage we've instead make the location 19 | ; a property of each item. 20 | ; 21 | ; 22 | ; Known deficiencies / bugs 23 | ; ------------------------- 24 | ; 25 | ; HELP command doesn't show description next to command. 26 | ; 27 | ; 28 | ; Naming / Code Layout 29 | ; -------------------- 30 | ; 31 | ; I've tried to be consistent with naming-schemes, but of course some 32 | ; code was added in an ad-hoc fashion: 33 | ; 34 | ; * All functions with a _command suffix are command-handlers 35 | ; These are invoked as a result of the user typing the appropriate 36 | ; string. e.g. User types "EXAMINE" we run "examine_function" 37 | ; 38 | ; * All object-specific functions have a _fn suffix. 39 | ; e.g. Custom code runs when you "TAKE RUG" is take_rug_fn 40 | ; 41 | ; If you're using Emacs, or similar editor, you should be able to fold 42 | ; or collapse sections of the code via the markers "; {" and "; }" 43 | ; 44 | 45 | 46 | 47 | ; Game over if you don't win in this many turns 48 | MAX_TURN_LIMIT: EQU 100 49 | 50 | ; How many turns before the ship warning 51 | REMINDER_TURN_COUNT: EQU 10 52 | 53 | ; Maximum number of turns you can survive being near a grue 54 | MAX_GRUE_EXPOSURE: EQU 3 55 | 56 | ; Later we have an object-table, containing entries for all of the 57 | ; objects which are present in the game. Some of these objects are 58 | ; real in the sense that they can be used/taken. Others exist only 59 | ; so they may be examined. 60 | ; 61 | ; Each object has a description, a state, and a location, along with 62 | ; a collection of dedicated handler-functions. 63 | ; 64 | ; Here we define some state attributes for a couple of special objects 65 | ; which eases maintenance. 66 | ; 67 | ; 1. Torch may be lit or unlit. 68 | TORCH_STATE_UNLIT: EQU 0 69 | TORCH_STATE_LIT: EQU 1 70 | 71 | ; 2. The trapdoor may be invisible, open, or closed. 72 | ; [The objects have an invisible state, but we can't use that here.] 73 | TRAPDOOR_STATE_INVISIBLE: EQU 0 74 | TRAPDOOR_STATE_CLOSED: EQU 1 75 | TRAPDOOR_STATE_OPEN: EQU 2 76 | 77 | ; 3. The mirror may be normal, or broken 78 | MIRROR_OK: EQU 0 79 | MIRROR_BROKEN: EQU 1 80 | 81 | ; 82 | ; Each item also contains a byte which holds the "location" (i.e. index 83 | ; into the location-map). Two locations are special: 84 | ; 85 | ; - A location of 0xff means the player is carrying the item. 86 | ; 87 | ; - A location of 0xfe means the item is available, but invisible. 88 | ; 89 | ; These are defined here for clarity. 90 | ; 91 | ITEM_CARRIED: EQU 0xff 92 | ITEM_INVISIBLE: EQU 0xfe 93 | 94 | 95 | 96 | ;******************************************************************** 97 | ; Macros 98 | ;******************************************************************** 99 | ; {{ 100 | 101 | ; 102 | ; Simple macro to inject "L" into our input buffer, and call the LOOK handler. 103 | ; 104 | MACRO FAKE_LOOK 105 | ld hl, INPUT_BUFFER+2 106 | ld (hl), 'L' 107 | call look_function 108 | ENDM 109 | 110 | 111 | ; 112 | ; Simple macro to push all (important) registers. 113 | ; 114 | MACRO PUSH_ALL 115 | push af 116 | push bc 117 | push de 118 | push hl 119 | ENDM 120 | 121 | 122 | ; 123 | ; Simple macro to pop all (important) registers. 124 | ; 125 | MACRO POP_ALL 126 | pop hl 127 | pop de 128 | pop bc 129 | pop af 130 | ENDM 131 | ; }} 132 | 133 | 134 | ;******************************************************************** 135 | ; Entry-point and initial setup 136 | ;******************************************************************** 137 | ; {{ 138 | 139 | ; 140 | ; Entry-point of CP/M binaries is 0x100, as the zero-page, or PSP, 141 | ; is located before that: 142 | ; 143 | ; https://en.wikipedia.org/wiki/Zero_page_(CP/M) 144 | ; 145 | ; Entry-point for the ZX Spectrum port is 32768, chosen randomly. 146 | ; 147 | ORG ENTRYPOINT 148 | 149 | ; 150 | ; Our game can be compiled with optional "encryption", which uses 151 | ; a simple scheme to hide the strings in our binary. 152 | ; 153 | ; If this support is used we'll have to restore our contents, or 154 | ; the game won't be playable. 155 | ; 156 | ; To compile the binary with encryption you need to define the 157 | ; `ENCRYPT_STRINGS` symbol, and then post-process the binary 158 | ; with `encrypt.c`. 159 | ; 160 | IF ENCRYPT_STRINGS 161 | ld hl, ENC ; start of region to unmangle 162 | ld de, ( end_of_source - ENC ) ; length to unmangle. 163 | ld c, '%' ; starting scrambling key (XOR) 164 | enc_loop: 165 | ld a,(hl) ; get the byte 166 | xor c ; modify it 167 | ld (hl),a ; store it back 168 | inc hl ; point to the next byte 169 | dec de ; decrease count of remaining bytes to process. 170 | inc c ; bump key 171 | ld a, d ; done? 172 | or e 173 | jr nz,enc_loop ; no, then repeat 174 | 175 | jr enc_end ; Jump over our marker to the start of the game. 176 | 177 | ; 178 | ; Everything after here will be encrypted. 179 | ; 180 | ENC: DB "SKX" 181 | enc_end: 182 | ENDIF 183 | 184 | ; Call system-specific setup routine before we do anything else. 185 | call bios_init 186 | 187 | 188 | ; Here we're going to copy our game-state to the end of RAM 189 | ; 190 | ; We do this so that everything "resets" if the player dies/wins 191 | ; and then chooses to play again when prompted. 192 | ; 193 | ; We don't know for sure how big the data is, but we assume it'll 194 | ; fit from 0xD000.. 195 | ld hl, per_game_state_start 196 | ld de, 0xD000 197 | ld bc, end_of_source - per_game_state_start 198 | ldir 199 | 200 | ;; }} 201 | 202 | 203 | ;******************************************************************** 204 | ; Main game-loop 205 | ;******************************************************************** 206 | ; {{ 207 | 208 | game_start: 209 | 210 | ; Reset the state of the the inventory, the game-world, etc. 211 | ; 212 | ; This is pointless at the start of the game, as nothing is 213 | ; modified, but we jump here if the user chooses to replay the 214 | ; game, so we have it here for that reason. 215 | ld hl, 0xD000 216 | ld de, per_game_state_start 217 | ld bc, end_of_source - per_game_state_start 218 | ldir 219 | 220 | ; Clear the screen 221 | call bios_clear_screen 222 | 223 | ; Present the game intro-text. 224 | ; Set the wrapping to be 60 characters to ensure there's no weirdness 225 | ld hl, WRAP_LOCATION 226 | ld a, (hl) 227 | push af 228 | ld (hl), 60 229 | ld de, usage_message 230 | call bios_output_string 231 | pop af 232 | ld (hl),a 233 | 234 | ; Pause for input here. 235 | call bios_await_keypress 236 | call bios_clear_screen 237 | 238 | ; Show the starting-location. 239 | FAKE_LOOK 240 | 241 | ; The start of our game loop. 242 | ; 243 | ; Here we basically read a line of input, process it, then 244 | ; attempt to call the appropriate handler. This repeats until 245 | ; the game is over, one way or another. 246 | ; 247 | ; We upper-case all input to allow easier matching to our 248 | ; command/dispatch-table. 249 | game_loop: 250 | 251 | ; Have we exceeded the turn-count? 252 | ld hl, TURN_COUNT 253 | ld a, (hl) 254 | cp MAX_TURN_LIMIT 255 | jr c,game_loop_continue 256 | 257 | ld hl, PLAYER_DEAD_FLAG 258 | inc (hl) 259 | 260 | game_loop_continue: 261 | 262 | ; Did the player die of a magic-overdoes 263 | ld hl, PLAYER_MAGIC_OVERDOSE_FLAG 264 | ld a, (hl) 265 | cp 1 266 | jp z, play_again 267 | 268 | ; Is the player dead? 269 | ld hl, PLAYER_DEAD_FLAG 270 | ld a, (hl) 271 | cp 1 272 | jr nz,not_dead 273 | 274 | ; The player is dead. Oops. 275 | ld de, PLAYER_DEAD_MESSAGE 276 | call bios_output_string 277 | call turns_function 278 | jr_play_again: 279 | jp play_again 280 | 281 | not_dead: 282 | ; Did the player win? 283 | ld hl, PLAYER_WON_FLAG 284 | ld a, (hl) 285 | cp 1 286 | jr nz, not_won 287 | 288 | ; The player won. Yay! 289 | ld de, PLAYER_WON_MESSAGE 290 | call bios_output_string 291 | call turns_function 292 | jr jr_play_again 293 | 294 | not_won: 295 | 296 | ; Erase our input buffer. 297 | ld hl, INPUT_BUFFER+2 298 | ld b, MAX_INPUT_LENGTH 299 | call erase_buffer 300 | 301 | ; Read a line of input from the player. 302 | ld de, INPUT_BUFFER 303 | call bios_read_input 304 | 305 | ; Every ten turns we show a message about the ship. 306 | ld hl, SHIP_WARNING 307 | dec (hl) ; decrease 308 | jr nz, ship_warning_over ; nope? Do nothing 309 | 310 | ld (hl), REMINDER_TURN_COUNT ; reset the count 311 | ld de, ship_closer_msg ; show the message 312 | call bios_output_string 313 | call show_dots 314 | call bios_clear_screen 315 | 316 | 317 | ship_warning_over: 318 | ; If the input was zero-length (i.e. User pressed return) ignore it. 319 | ld hl, INPUT_BUFFER+1 320 | ld a,(hl) 321 | cp 0 322 | jr z, not_won 323 | 324 | ; Convert the input to upper-case. 325 | ld b,a 326 | inc hl 327 | call uppercase_buffer 328 | 329 | ; Remove leading whitespace, by shifting input down 330 | call filter_input_leading_whitespace 331 | 332 | ; Remove trailing whitespace, by replacing with nulls. 333 | call filter_input_trailing_whitespace 334 | 335 | ; Take the input from the user, and see if we have a registered 336 | ; command with that name. If we do we can invoke it, but if not we've 337 | ; been given something we don't understand. 338 | ld hl, INPUT_BUFFER+2 339 | 340 | ; We have two kinds of commands: 341 | ; 1. Global 342 | ; 2. Per-Item 343 | ; 344 | ; Per-item command are processed before the global ones, 345 | ; so that they can be invoked first. 346 | ; 347 | call input_second_term 348 | 349 | ; did that fail? 350 | cp 0 351 | jr nz, process_global_command 352 | 353 | ; ok find the item, if that failed then 354 | ; again we continue with the global item. 355 | call get_item_by_name 356 | cp 0 357 | jr nz, process_global_command 358 | 359 | ; now we should find IX points to the item 360 | ; is there a global command handler? 361 | ld e,(IX + ITEM_TABLE_CUSTOM_ACTION_OFFSET) 362 | ld d,(IX + ITEM_TABLE_CUSTOM_ACTION_OFFSET+1) 363 | 364 | ; DE points to the table. If it is null we have 365 | ; no per-item table for this object 366 | ld a,d 367 | or e 368 | jr z, process_global_command 369 | 370 | ; At this point DE points to the table of per-item 371 | ; commands, and HL points to the object. 372 | ; 373 | ; We don't really want the object any more, instead 374 | ; we want to look at the verb at the start of our 375 | ; input buffer. 376 | ld hl, INPUT_BUFFER+2 377 | call find_command_for_custom_table 378 | cp 255 379 | jr z, process_global_command 380 | 381 | ; OK we found an item in the custom handler 382 | ; we're gonna call it. 383 | call JP_HL 384 | 385 | ; since we found a custom command skip the global 386 | ; handling, and proceed to run our regular actions 387 | jr regular_turn_actions 388 | 389 | 390 | process_global_command: 391 | ; Otherwise we look for a global command 392 | ld hl, INPUT_BUFFER+2 393 | call find_command 394 | cp 255 395 | jr z, no_handler 396 | push hl 397 | 398 | ; Call the handler 399 | pop hl 400 | call JP_HL 401 | 402 | regular_turn_actions: 403 | 404 | ; Valid command - increase turn count 405 | ld hl, TURN_COUNT 406 | inc (hl) 407 | 408 | ; Did we get eaten? 409 | call maybe_grue_death 410 | 411 | ; And restart the command-loop 412 | jr jr_game_loop 413 | 414 | 415 | no_handler: 416 | ; Show the user that their command wasn't understood. 417 | ld de, invalid_msg 418 | call bios_output_string 419 | 420 | ; Did we get eaten? 421 | call maybe_grue_death 422 | 423 | ; Return to the start of our game-loop. 424 | jr_game_loop: 425 | jp game_loop 426 | 427 | ; }} 428 | 429 | 430 | 431 | ;******************************************************************** 432 | ; Include our custom printing routine 433 | ;******************************************************************** 434 | include "prn.z80" 435 | 436 | 437 | 438 | ;******************************************************************** 439 | ; Utility functions 440 | ;******************************************************************** 441 | ; {{ 442 | 443 | ; compare strings stored in HL + DE for equality. Length is stored in B 444 | ; Returns NZ if they're not equal. 445 | CompareStringsWithLength: 446 | ld a,(de) 447 | cp (hl) 448 | ret nz 449 | inc hl 450 | inc de 451 | djnz CompareStringsWithLength 452 | xor a 453 | ret 454 | 455 | 456 | ; 457 | ; Helper function, to allow indirection. 458 | ; 459 | JP_HL: 460 | jp (hl) 461 | 462 | 463 | ; 464 | ; Show some significant dots 465 | ; 466 | show_dots: 467 | ; width of the screen 468 | ld b, MAX_INPUT_LENGTH - 1 469 | dot_loop: 470 | PUSH_ALL 471 | call bios_delay 472 | ld e, '.' 473 | call bios_output_character 474 | POP_ALL 475 | djnz dot_loop 476 | ret 477 | 478 | 479 | 480 | 481 | 482 | ; Multiply two 8-bit values together. 483 | ; INPUT: THE VALUES IN REGISTER B EN C 484 | ; OUTPUT: HL = B * C 485 | ; CHANGES: AF,DE,HL,B 486 | ; 487 | multiply: 488 | LD HL,0 489 | LD A,B 490 | OR A 491 | RET Z 492 | LD D,0 493 | LD E,C 494 | LOOP: ADD HL,DE 495 | DJNZ LOOP 496 | RET 497 | 498 | ; 499 | ; Output number helpers - used for reporting the number of turns 500 | ; you have taken, as well as showing the wrap-value via "WRAP". 501 | ; 502 | DispHL: 503 | ld bc,-10000 504 | call Num1 505 | ld bc,-1000 506 | call Num1 507 | ld bc,-100 508 | call Num1 509 | ld c,-10 510 | call Num1 511 | ld c,-1 512 | Num1: ld a,'0'-1 513 | Num2: inc a 514 | add hl,bc 515 | jr c,Num2 516 | sbc hl,bc 517 | 518 | PUSH_ALL 519 | 520 | ld e, a 521 | call bios_output_character 522 | 523 | POP_ALL 524 | ret 525 | 526 | show_a_register: 527 | PUSH_ALL 528 | ld h,0 529 | ld l,a 530 | call DispHL 531 | POP_ALL 532 | ret 533 | 534 | ;Input: 535 | ; DE points to the string 536 | ;Outputs: 537 | ; HL is the result 538 | ; A is the 8-bit value of the number 539 | ; DE points to the byte after the number 540 | ;Destroys: 541 | ; BC 542 | ; if the string is non-empty, BC is HL/10 543 | string_to_uint16: 544 | ld hl,0 545 | string_to_uint16_loop: 546 | ld a,(de) 547 | sub 30h 548 | cp 10 549 | ret nc 550 | inc de 551 | ld b,h 552 | ld c,l 553 | add hl,hl 554 | add hl,hl 555 | add hl,bc 556 | add hl,hl 557 | add a,l 558 | ld l,a 559 | jr nc,string_to_uint16_loop 560 | inc h 561 | jr string_to_uint16_loop 562 | 563 | 564 | ; 565 | ; Helper function, fill a region of memory with zero characters 566 | ; 567 | ; HL will point to the buffer. 568 | ; B will have the length 569 | erase_buffer: 570 | xor a 571 | erase_buffer_loop: 572 | ld (hl),a 573 | inc hl 574 | djnz erase_buffer_loop 575 | ret 576 | 577 | 578 | ; 579 | ; Helper function. Upper-case the given string. 580 | ; 581 | ; HL will point to the buffer. 582 | ; B will have the length 583 | ; 584 | uppercase_buffer: 585 | ld a, (hl) 586 | cp 'a'-1 587 | jr c, uppercase_ok 588 | cp 'z'+1 589 | jr nc, uppercase_ok 590 | sub 32 591 | ld (hl),a 592 | uppercase_ok: 593 | inc hl 594 | djnz uppercase_buffer 595 | ret 596 | 597 | 598 | 599 | ;; INPUT_BUFFER+2 contains the user input 600 | ;; The length is in INPUT_BUFFER+1 601 | ;; 602 | filter_input_leading_whitespace: 603 | ; is the first character a space? 604 | ld a,(INPUT_BUFFER+2) 605 | cp ' ' 606 | 607 | ; nope? We're done 608 | ret nz 609 | 610 | ; get the length of the input into BC 611 | ld a,(INPUT_BUFFER+1) 612 | xor b 613 | ld c, a 614 | 615 | ; Move the buffer down one byte. 616 | ld hl, INPUT_BUFFER+3 617 | ld de, INPUT_BUFFER+2 618 | ldir 619 | 620 | ; repeat until no more leading spaces are found 621 | jr filter_input_leading_whitespace 622 | 623 | ;; INPUT_BUFFER+2 contains the user input 624 | ;; The length is in INPUT_BUFFER+1 625 | ;; 626 | filter_input_trailing_whitespace: 627 | 628 | ;; Get the input length in BC 629 | xor b 630 | ld a,(INPUT_BUFFER+1) 631 | ld c,a 632 | 633 | ;; Add to the start of the buffer, because 634 | ;; we will process the text in reverse. 635 | ld hl, INPUT_BUFFER+1 636 | add hl,bc 637 | 638 | overwrite_space_loop: 639 | ;; get the charactor 640 | ld a, (hl) 641 | 642 | ;; is it a space? If not return 643 | cp ' ' 644 | ret nz 645 | 646 | ;; overwrite with a zero, and work backwards 647 | ld (hl),0 648 | dec hl 649 | jr overwrite_space_loop 650 | 651 | ;******************************************************************** 652 | ; Utility Functions For Game 653 | ;******************************************************************** 654 | ; {{ 655 | 656 | 657 | ; 658 | ; Ask the user if they wish to play again 659 | ; 660 | play_again: 661 | ld de, play_again_msg 662 | call bios_output_string 663 | 664 | call bios_await_keypress 665 | cp 'y' 666 | jp z, game_start 667 | cp 'Y' 668 | jp z, game_start 669 | cp 'n' 670 | jr z, play_again_no 671 | cp 'N' 672 | jr z, play_again_no 673 | call bios_clear_screen 674 | jr play_again 675 | 676 | ; User chose "N" to "play again?" 677 | play_again_no: 678 | ld de, play_again_no_msg 679 | call bios_output_string 680 | IF SPECTRUM 681 | pop de 682 | ret 683 | ELSE 684 | ld c, 0x0 685 | call 0x0005 686 | ret 687 | ENDIF 688 | 689 | 690 | ; This will be a major part of our code so it is perhaps a little more 691 | ; verbose than it wants to be. 692 | ; 693 | ; Find the entry in the command-table. If we find it return the address 694 | ; of the routine to call in `HL`. 695 | ; 696 | ; On entry `HL` should contain the pointer to the input buffer to look 697 | ; for. 698 | ; 699 | ; On exit A contains 0 on success, with HL pointing to the handler to invoke, 700 | ; or A contains 255 on failure. 701 | find_command: 702 | ld de,command_table 703 | find_command_for_custom_table: 704 | ; 705 | ; Entries are arranged like so: 706 | ; $LENGTH "ASCII" $HIDDEN ADDR1 ADDR2 707 | ; 708 | ; Where $LENGTH is the length of the ASCII command-name. 709 | ; 710 | ; So: 711 | ; 712 | ; 4, "QUIT", 0, quit_fn_ptr 713 | ; 3, "CLS", 1, cls_fn_ptr 714 | ; 0, 715 | ; 716 | ; "HIDDEN" is used solely to decide whether the command should be 717 | ; included in the output of "HELP". 718 | ; 719 | find_command_again: 720 | push de 721 | ld a, (de) ; length of string 722 | cp 0x00 ; zero? We've hit the end of the table and not found 723 | jr nz, cont ; a match - return failure 724 | pop de 725 | ld a,255 726 | ret 727 | cont: 728 | ld b, a ; B will contain the length 729 | inc de ; DE point to the string itself 730 | 731 | push hl 732 | call CompareStringsWithLength 733 | jr z,find_command_found 734 | pop hl 735 | 736 | ; ok we didn't find a match with this command-table entry. 737 | 738 | ; restore DE which points to this entry's start 739 | pop de 740 | 741 | ld a, (de) ; get the length of the command-name 742 | inc de ; point to the command-name 743 | ld b,a ; bump the pointer past the ASCII string, in a loop 744 | find_command_skip: 745 | inc de 746 | djnz find_command_skip 747 | inc de ; bump past the hidden-flag 748 | inc de ; bump past the addr1-byte 749 | inc de ; bump past the addr2-byte 750 | jr find_command_again ; loop back to try the next table entry. 751 | 752 | find_command_found: 753 | ; drop hl 754 | pop hl 755 | 756 | ; restore the pointer to the start of this command-table entry. 757 | pop de 758 | 759 | ; Once again we need to bump past the length, and the ASCII string 760 | ; itself. Now we know we've got a match we want to get the address 761 | ; of the handler to invoke. 762 | ld a, (de) ; get the length 763 | inc de ; move to the start of the string 764 | ld b,a 765 | find_command_skip2: ; skip $LENGTH-bytes 766 | inc de 767 | djnz find_command_skip2 768 | inc de ; skip the hidden-flag 769 | ld a, (de) ; set l 770 | ld l,a 771 | inc de 772 | ld a, (de) ; set h 773 | ld h,a 774 | xor a ; indicate success 775 | ret ; return with the function-pointer in HL 776 | 777 | 778 | ; 779 | ; Helper function. Are ANY items present in this room? 780 | ; 781 | ; This is just used to know whether to print the "You can see .." message 782 | ; 783 | ; Return "A == 0" if so. 784 | ; Return "A == FF" if not 785 | ; 786 | items_are_present: 787 | 788 | ; Point to the item-table, and prepare to loop over the items 789 | ld IX, items 790 | ld DE, ITEM_ENTRY_LENGTH 791 | ld b, ITEM_COUNT 792 | 793 | look_item_loop_test: 794 | 795 | ; Get the location of the object in C 796 | ld c, (IX+ITEM_TABLE_LOCATION_OFFSET) 797 | 798 | ; Set A to the current location. 799 | ld hl, CURRENT_LOCATION 800 | ld a,(hl) 801 | 802 | ; Is the item in the room? 803 | cp c 804 | jr nz, item_not_present_loop 805 | 806 | ; Get the description of the object in HL. 807 | ld l, (IX+ITEM_TABLE_DESCRIPTION_OFFSET) 808 | ld h, (IX+ITEM_TABLE_DESCRIPTION_OFFSET+1) 809 | 810 | ; OK we have an item present. Is the description empty? 811 | ; if so that's a hidden object and it doesn't count. 812 | ld a, h 813 | or l 814 | jr z, item_not_present_loop 815 | 816 | xor a 817 | ret 818 | item_not_present_loop: 819 | ; Move to the next entry 820 | add IX,DE 821 | djnz look_item_loop_test 822 | ld a, 0xff 823 | ret 824 | 825 | 826 | ; 827 | ; Helper function to return an item, by name. 828 | ; 829 | ; This is used a lot because the item-entries contain a series of 830 | ; pointers for various purposes - handlers to be invoked for object-specific 831 | ; behaviour, the location of the object within our world-map, and 832 | ; a byte representing per-item state. 833 | ; 834 | ; On entry the name of the item to be found will be stored in HL. 835 | ; 836 | ; On exit A will be 00 if all is OK, and IX will point to the item. 837 | ; On exit A will be ff if there was a failure. 838 | ; 839 | get_item_by_name: 840 | ; erase our temporary buffer, keeping the HL register 841 | ; with the name of the item to find safe. 842 | push hl 843 | ld hl, TMP_BUFFER 844 | ld b, TMP_BUFFER_LEN 845 | call erase_buffer 846 | pop hl 847 | 848 | ; copy the name of the item into the temporary-buffer 849 | ld de, TMP_BUFFER+1 850 | ld b,0 851 | gibn_copy_loop: 852 | ld a,(hl) 853 | cp 0 854 | jr z, gibn_copy_complete 855 | ld (de),a 856 | inc de 857 | inc hl 858 | inc b 859 | jr gibn_copy_loop 860 | 861 | gibn_copy_complete: 862 | ; prefix the temporary buffer with the length of the item 863 | ld hl, TMP_BUFFER 864 | ld (hl),b 865 | 866 | ; Point to our table of items 867 | ld ix, items 868 | ld b, ITEM_COUNT 869 | 870 | gibn_item_loop: 871 | 872 | ; Get the name of the object in DE - that's at the start of the item. 873 | ld e,(IX) 874 | ld d,(IX + 1) 875 | 876 | ; Now get the location of the current object. 877 | ld a, (IX+ITEM_TABLE_LOCATION_OFFSET) 878 | 879 | ; Is this object carried? 880 | cp ITEM_CARRIED 881 | jr z, gibn_item_carried 882 | 883 | ; Is this object available (but hidden)? 884 | cp ITEM_INVISIBLE 885 | jr z, gibn_item_available 886 | 887 | ; Is this object in the current location? 888 | push hl 889 | ld hl, CURRENT_LOCATION 890 | ld c,(hl) 891 | pop hl 892 | 893 | cp c 894 | jr z, gibn_item_present 895 | 896 | gibn_continue: 897 | ; OK we didn't find the object, so we need to try looking at 898 | ; the next table entry 899 | ld DE, ITEM_ENTRY_LENGTH 900 | add ix, DE 901 | djnz gibn_item_loop 902 | 903 | ; At this point we've looked at all objects in the table, and 904 | ; we didn't find a match. 905 | ld a, 0xff 906 | ret 907 | 908 | gibn_item_present: 909 | gibn_item_carried: 910 | gibn_item_available: 911 | push bc ; preserve b for our loop 912 | 913 | ; OK we've found an item that is "present" or "available". 914 | ; 915 | ; Does this object have the name of the item we're actually 916 | ; supposed to be looking for? If so then we've found it 917 | ; and we can return our IX register (which points to the 918 | ; start of the object in the item-table). 919 | ; 920 | ; If the name doesn't match we loop around to examine the 921 | ; next item in the table. 922 | ld hl, TMP_BUFFER ; Get the length from our buffer 923 | ld b, (hl) 924 | inc hl ; Now HL points to the input 925 | ; DE points to the name of the current item. 926 | ; B contains the length 927 | call CompareStringsWithLength 928 | jr z, gibn_found_it 929 | 930 | pop bc 931 | jr gibn_continue 932 | 933 | gibn_found_it: 934 | ; We found the item. Drop the BC register, which we saved 935 | ; to preserve our loop-index. 936 | pop bc 937 | xor a 938 | ret 939 | 940 | 941 | ; 942 | ; Helper function. Are we at risk of grue? 943 | ; 944 | ; If our location is in the dark-place we are at risk of being eaten. 945 | ; 946 | ; In the C implementation there was a random number test, here we decide 947 | ; that if you enter three valid turns within the dark place then you 948 | ; will die 949 | ; 950 | maybe_grue_death: 951 | ; Are we in the dark-place? 952 | ld hl, CURRENT_LOCATION 953 | ld a,(hl) 954 | cp 4 955 | ret nz 956 | 957 | ; Get the grue (!) 958 | ld hl, item_15_name 959 | call get_item_by_name 960 | cp 0 961 | ret nz 962 | 963 | ; Once we've found the item IX will point to the entry 964 | ; 965 | ; Get the item state using that index register, after increasing it. 966 | inc (IX + ITEM_TABLE_STATE_OFFSET) 967 | ld a,(IX + ITEM_TABLE_STATE_OFFSET) 968 | 969 | cp MAX_GRUE_EXPOSURE 970 | jr nc, death_by_grue_grue 971 | 972 | call show_dots 973 | ld de, grue_message 974 | call bios_output_string 975 | ret 976 | 977 | death_by_grue_grue: 978 | ld de, you_were_eaten 979 | call bios_output_string 980 | 981 | ld hl,PLAYER_DEAD_FLAG 982 | inc (hl) 983 | 984 | call turns_function 985 | jp play_again 986 | ret 987 | 988 | ; 989 | ; Helper function. Is the player carrying something? 990 | ; 991 | ; This is used in the inventory function to decide whether to show the list 992 | ; of items, with header, or "You're carrying nothing". 993 | ; 994 | ; Return "A == 0" if so. 995 | ; Return "A == FF" if not. 996 | ; 997 | player_has_objects: 998 | ; Point to the item-table, and prepare to loop over the items 999 | ld IX, items 1000 | ld DE, ITEM_ENTRY_LENGTH 1001 | ld b, ITEM_COUNT 1002 | 1003 | player_has_objects_loop: 1004 | 1005 | ; Get the location of the object in A. 1006 | ld a,(IX+ITEM_TABLE_LOCATION_OFFSET) 1007 | 1008 | ; If the location is the player's possession we're carrying something, 1009 | ; it doesn't matter what. 1010 | cp ITEM_CARRIED 1011 | jr nz, player_not_carrying_loop 1012 | 1013 | ; Report success 1014 | xor a 1015 | ret 1016 | player_not_carrying_loop: 1017 | 1018 | ; Point to the next entry. 1019 | add IX, DE 1020 | 1021 | ; Loop again 1022 | djnz player_has_objects_loop 1023 | ld a, 0xff 1024 | ret 1025 | 1026 | 1027 | 1028 | ; 1029 | ; Helper function - get pointer to second command-line word 1030 | ; 1031 | ; e.g. The user enters "GET TORCH", "EXAMINE BOOK", etc, we want to 1032 | ; get the start of second-word "TORCH", "BOOK" 1033 | ; 1034 | ; But the second term is a bit "vague", because we want to allow 1035 | ; TURN ON TORCH 1036 | ; 1037 | ; Really the second word in this sense is TORCH, so what we do is 1038 | ; start at the back of the input buffer and count backwards until 1039 | ; we find the first space. 1040 | ; 1041 | input_second_term: 1042 | ld hl, INPUT_BUFFER+1 1043 | ld d, 0 1044 | ld e, (hl) ; get the length of entered text in DE 1045 | inc hl 1046 | add hl, de ; Add length to start of text 1047 | 1048 | ld b, e 1049 | ld c, 0 ; prepare to loop backwards for that length 1050 | 1051 | input_second_term_loop1: 1052 | ld a,(hl) 1053 | cp ' ' 1054 | jr z, input_second_term_found_word 1055 | dec hl 1056 | djnz input_second_term_loop1 1057 | 1058 | ; failure 1059 | ld a, 0xff 1060 | ret 1061 | 1062 | input_second_term_found_word: 1063 | inc hl ; we found a space, so increment once to point to the item 1064 | xor a 1065 | ret 1066 | 1067 | ; }} 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | ;******************************************************************** 1074 | ; Command Handlers 1075 | ;******************************************************************** 1076 | ; {{ 1077 | 1078 | 1079 | ; 1080 | ; Command-Handler SWEARING 1081 | ; 1082 | bad_language_function: 1083 | ld de, BAD_LANGUAGE_MSG 1084 | jp bios_output_string 1085 | 1086 | 1087 | 1088 | ; 1089 | ; Command-Handler DOWN 1090 | ; 1091 | ; Change location, this has some tests to cope with the basement-puzzle. 1092 | ; 1093 | down_function: 1094 | ld hl, CURRENT_LOCATION 1095 | ld a, (hl) 1096 | cp 0 1097 | jr z, down_on_top_floor 1098 | cp 1 1099 | jr z, down_on_middle_floor 1100 | cp 2 1101 | jr z, down_on_ground_floor 1102 | 1103 | ; fall-through - other options will show "NO DOWN" 1104 | no_down: 1105 | ld de, no_down_msg 1106 | call bios_output_string 1107 | ret 1108 | 1109 | down_on_top_floor: 1110 | down_on_middle_floor: 1111 | inc (hl) 1112 | call look_function 1113 | ret 1114 | 1115 | down_on_ground_floor: 1116 | ; The trapdoor must be open for us to go down. 1117 | ; Find the item, and get it's state. 1118 | ld hl, item_11_name 1119 | call get_item_by_name 1120 | 1121 | ; Once we've found the item IX will point to the entry 1122 | ; 1123 | ; Get the item state using the index register. 1124 | ld a, (IX+ITEM_TABLE_STATE_OFFSET) 1125 | 1126 | cp TRAPDOOR_STATE_OPEN 1127 | jr nz, no_down ; not open? Then we can't go down 1128 | 1129 | ; Does the user have the (lit) torch? 1130 | call user_has_lit_torch 1131 | cp 0 1132 | jr z, dark_destination 1133 | 1134 | ; OK the user does have it, we can move to the basement 1135 | ld hl, CURRENT_LOCATION 1136 | ld (hl),3 1137 | call look_function 1138 | ret 1139 | dark_destination: 1140 | ; user doesn't have lit torch - goes to the dark place 1141 | ld hl, CURRENT_LOCATION 1142 | ld (hl),4 1143 | call look_function 1144 | ret 1145 | 1146 | 1147 | 1148 | ; 1149 | ; Does the user have the torch, which is lit? 1150 | ; 1151 | ; Return 1 if the user does. 1152 | ; 1153 | ; Return 0 if the torch is unlit, or not carried. 1154 | user_has_lit_torch: 1155 | ; get the torch 1156 | ld hl, item_3_name 1157 | call get_item_by_name 1158 | 1159 | ; Once we've found the item IX will point to the entry 1160 | ; 1161 | ; Get the location using the index register. 1162 | ld a, (IX+ITEM_TABLE_LOCATION_OFFSET) 1163 | 1164 | ; Are we carrying the item? 1165 | cp ITEM_CARRIED 1166 | jr nz, user_not_got_torch 1167 | 1168 | ; OK now see if the torch is lit 1169 | ld a, (IX+ITEM_TABLE_STATE_OFFSET) 1170 | 1171 | ; Is it lit? 1172 | cp TORCH_STATE_LIT 1173 | jr nz, user_got_torch_but_not_lit 1174 | 1175 | ; OK the user has a torch, and it is lit 1176 | ld a,1 1177 | ret 1178 | 1179 | user_not_got_torch: 1180 | user_got_torch_but_not_lit: 1181 | xor a 1182 | ret 1183 | 1184 | 1185 | ; 1186 | ; Command-Handler DROP 1187 | ; 1188 | ; If the item is carried by the player it is dropped 1189 | ; 1190 | ; If there is a non-zero drop-function it is invoked. 1191 | ; 1192 | drop_function: 1193 | call input_second_term 1194 | cp 0 1195 | jr z, drop_object 1196 | 1197 | ld de, DROP_WHAT_MSG 1198 | call bios_output_string 1199 | ret 1200 | 1201 | drop_object: 1202 | ; store object in HL 1203 | call input_second_term 1204 | 1205 | call get_item_by_name 1206 | cp 0 1207 | jr z,drop_found_it 1208 | 1209 | ; if we didn't find it in the current location or inventory 1210 | ; then we're not carrying it. 1211 | cannot_drop: 1212 | ld de, not_carrying_item_msg 1213 | call bios_output_string 1214 | ret 1215 | 1216 | drop_found_it: 1217 | ; IX points to the item's table-entry 1218 | ; 1219 | ; When we find an item by name that returns success if: 1220 | ; 1221 | ; a. We're carrying the item 1222 | ; b. The item is in the same room as us. 1223 | ; c. Even if invisible 1224 | ld a, (IX+ITEM_TABLE_LOCATION_OFFSET) 1225 | cp ITEM_CARRIED 1226 | jr nz, cannot_drop 1227 | 1228 | ; Get the current location 1229 | ld hl, CURRENT_LOCATION 1230 | ld a,(hl) 1231 | 1232 | ; Update the item to have its location-field set to the 1233 | ; location we just discovered. 1234 | ld (IX + ITEM_TABLE_LOCATION_OFFSET),a 1235 | 1236 | ; IX still points to the start of the item's entry, 1237 | ; now look for a custom drop-function. 1238 | ld l, (IX+ITEM_TABLE_DROP_OFFSET) 1239 | ld h, (IX+ITEM_TABLE_DROP_OFFSET+1) 1240 | 1241 | ; is the handler empty? 1242 | ld a, l 1243 | or h 1244 | jr nz, drop_invoke_handler 1245 | ld de, you_drop_it 1246 | call bios_output_string 1247 | ret 1248 | 1249 | drop_invoke_handler: 1250 | ; jump to the handler, the ret at the end 1251 | ; of that handler will return here. 1252 | jp JP_HL 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | ; 1259 | ; Command-Handler EXAMINE 1260 | ; 1261 | ; Examine either: 1262 | ; 1263 | ; 1. An item in the current location. 1264 | ; 1265 | ; 2. An item in the users' inventory. 1266 | ; 1267 | ; Examination means: 1268 | ; 1269 | ; 1. Identify the object from the input-buffer 1270 | ; 2. Check if the object exists, and if so 1271 | ; 3. Show the extended-description 1272 | ; 1273 | examine_function: 1274 | call input_second_term 1275 | cp 0 1276 | jr z, examine_object 1277 | 1278 | ld de, EXAMINE_WHAT_MSG 1279 | call bios_output_string 1280 | ret 1281 | 1282 | examine_object: 1283 | call show_newline 1284 | 1285 | ; store object in HL 1286 | call input_second_term 1287 | 1288 | call get_item_by_name 1289 | cp 0 1290 | jr z,examine_found_it 1291 | 1292 | ld de, item_not_present_msg 1293 | call bios_output_string 1294 | ret 1295 | 1296 | examine_found_it: 1297 | 1298 | ; IX points to the start of the entry for the object. 1299 | ; 1300 | ; Find the name of the custom examine-handler, if any, using 1301 | ; the index-register. 1302 | ld l,(IX+ITEM_TABLE_EXAMINE_OFFSET) 1303 | ld h,(IX+ITEM_TABLE_EXAMINE_OFFSET+1) 1304 | 1305 | ; If the custom examine function is non-empty call 1306 | ; it and return, otherwise show the extended message 1307 | ld a, l 1308 | or h 1309 | jr z, show_ext_desc 1310 | 1311 | ; The ret at the end of that handler will return 1312 | ; from this function. 1313 | jp JP_HL 1314 | 1315 | 1316 | show_ext_desc: 1317 | 1318 | ; OK at this point: 1319 | ; A) The item has been found. 1320 | ; B) There is no custom function. 1321 | ; However the item might have been found because it is invisible 1322 | ; 1323 | ; If the item is invisible we cannot examine it 1324 | ld a,(IX+ITEM_TABLE_LOCATION_OFFSET) 1325 | cp ITEM_INVISIBLE 1326 | jr nz, describe_item_real 1327 | 1328 | ld de, item_not_present_msg 1329 | call bios_output_string 1330 | ret 1331 | 1332 | describe_item_real: 1333 | 1334 | ; get the location of the extended-description pointer 1335 | ld e, (IX+ITEM_TABLE_EXT_DESCRIPTION_OFFSET) 1336 | ld d, (IX+ITEM_TABLE_EXT_DESCRIPTION_OFFSET+1) 1337 | 1338 | ; show it and return 1339 | call bios_output_string 1340 | ret 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | ; 1347 | ; Command-Handler GO 1348 | ; 1349 | ; This allows the user to enter "GO UP", and "GO DOWN". 1350 | ; 1351 | go_function: 1352 | call input_second_term 1353 | cp 0 1354 | jr z, go_somewhere 1355 | 1356 | go_unknown: 1357 | ld de, GO_WHERE_MSG 1358 | call bios_output_string 1359 | ret 1360 | 1361 | go_somewhere: 1362 | 1363 | ; GO "U" vs GO "D" 1364 | ld a,(HL) 1365 | cp 'U' 1366 | jp z, up_function 1367 | cp 'D' 1368 | jp z, down_function 1369 | 1370 | ; not (u)p or (d)own - show error 1371 | jr go_unknown 1372 | 1373 | 1374 | 1375 | ; 1376 | ; Command-Handler CALL 1377 | ; 1378 | ; If we're in the middle-floor we can use the telephone 1379 | ; 1380 | call_function: 1381 | ld hl, CURRENT_LOCATION 1382 | ld a,(hl) 1383 | cp 1 1384 | jr z, call_proceed 1385 | 1386 | ; not in the phone-room? Show an error and return 1387 | ld de, NO_PHONE_HERE 1388 | call bios_output_string 1389 | ret 1390 | 1391 | call_proceed: 1392 | call input_second_term 1393 | cp 0 1394 | jr z, call_person 1395 | 1396 | ; missing person 1397 | ld de, CALL_WHO_MSG 1398 | call bios_output_string 1399 | ret 1400 | 1401 | call_person: 1402 | ; HL points to the person the user tried to call 1403 | ; look in our people-table to see if there is a response 1404 | ; we can make 1405 | ld de, people_table 1406 | next_person_test: 1407 | push de 1408 | ld a, (de) ; length of string 1409 | cp 0x00 ; zero? We've hit the end of the table and not found 1410 | jr nz, cont_call ; a match - show a failure message and return 1411 | 1412 | ; show the error-message and return 1413 | pop de 1414 | ld de, call_unknown_msg 1415 | call bios_output_string 1416 | ret 1417 | 1418 | ; OK we've got an entry 1419 | cont_call: 1420 | ld b, a ; B will contain the length 1421 | inc de ; DE point to the string itself 1422 | 1423 | push hl 1424 | call CompareStringsWithLength 1425 | jr z, found_person_to_call 1426 | pop hl 1427 | 1428 | ; ok we didn't find 1429 | pop de 1430 | 1431 | ; ok we didn't find 1432 | ld a, (de) 1433 | inc de ; get the entry-length 1434 | ld b,a 1435 | skip_string: 1436 | inc de 1437 | djnz skip_string 1438 | inc de ; addr1 1439 | inc de ; addr2 1440 | jr next_person_test 1441 | 1442 | found_person_to_call: 1443 | pop hl 1444 | pop de 1445 | ld a, (de) 1446 | inc de ; get the entry-length 1447 | ld b,a 1448 | skip_string2: 1449 | inc de 1450 | djnz skip_string2 1451 | push de 1452 | pop hl 1453 | ld e, (hl) 1454 | inc hl 1455 | ld d, (hl) 1456 | call bios_output_string 1457 | ret 1458 | 1459 | ; 1460 | ; Command-Handler GET 1461 | ; 1462 | ; If there is a custom take-function defined for the appropriate 1463 | ; object then we'll invoke it. 1464 | ; 1465 | ; Otherwise the location of the item will be updated to mark it as 1466 | ; within the player's inventory. 1467 | ; 1468 | get_function: 1469 | call input_second_term 1470 | cp 0 1471 | jr z, get_object 1472 | 1473 | ld de, GET_WHAT_MSG 1474 | call bios_output_string 1475 | ret 1476 | 1477 | get_object: 1478 | ; store object in HL 1479 | call input_second_term 1480 | 1481 | call get_item_by_name 1482 | cp 0 1483 | jr z,get_found_it 1484 | 1485 | ld de, item_not_present_msg 1486 | call bios_output_string 1487 | ret 1488 | 1489 | get_found_it: 1490 | ; IX points to the start of the entry for the object. 1491 | ; 1492 | ; Find the name of the custom take-handler, if any, using 1493 | ; the index-register. 1494 | ld l, (IX+ITEM_TABLE_TAKE_OFFSET) 1495 | ld h, (IX+ITEM_TABLE_TAKE_OFFSET+1) 1496 | 1497 | ; Is there a custom function? 1498 | ld a, h 1499 | or l 1500 | jr z, take_as_normal 1501 | 1502 | ; OK there is a custom function. Use it 1503 | call JP_HL 1504 | ret 1505 | 1506 | take_as_normal: 1507 | 1508 | ; Get the collectible property of the object, using 1509 | ; the index register. 1510 | ld a, (IX+ITEM_TABLE_COLLECTIBLE_OFFSET) 1511 | 1512 | ; is this item collectible? 1513 | cp 1 1514 | jr z, get_found_it_take 1515 | 1516 | ld de, cant_take_that_msg 1517 | call bios_output_string 1518 | ret 1519 | 1520 | get_found_it_take: 1521 | 1522 | ; Change the location of the object to be within the 1523 | ; players possession. 1524 | ld (IX+ITEM_TABLE_LOCATION_OFFSET),ITEM_CARRIED 1525 | 1526 | ; Now we've taken the item show the users (updated) 1527 | ; inventory. 1528 | call inventory_function 1529 | ret 1530 | 1531 | 1532 | 1533 | 1534 | ; 1535 | ; Make trapdoor visible is called when the rug is taken/examined. 1536 | ; 1537 | ; The trapdoor has three states: 1538 | ; 1539 | ; invisible, open, closed 1540 | ; 1541 | ; If the current state is invisible then we mark it as visible. 1542 | 1543 | make_trapdoor_visible: 1544 | ld hl, item_11_name 1545 | call get_item_by_name 1546 | cp 0 1547 | jr z, place_trapdoor 1548 | 1549 | ld de, failed_find_trapdoor_msg 1550 | call bios_output_string 1551 | ret 1552 | 1553 | place_trapdoor: 1554 | ; We set the location of the trapdoor to being the 1555 | ; current location. 1556 | ; 1557 | ; NOTE: We could just hardcode this to location "3". 1558 | ; 1559 | ; Get the current location. 1560 | ld hl, CURRENT_LOCATION 1561 | ld a, (hl) 1562 | 1563 | ; Save the location in the item. 1564 | ld (IX+ITEM_TABLE_LOCATION_OFFSET),a 1565 | 1566 | ; Update the state 1567 | ld a, TRAPDOOR_STATE_CLOSED 1568 | ld (IX+ITEM_TABLE_STATE_OFFSET), a 1569 | ret 1570 | 1571 | 1572 | 1573 | ; 1574 | ; Command-Handler HELP 1575 | ; 1576 | ; Show "These commands are available", then the names of each non-hidden 1577 | ; command. 1578 | ; 1579 | help_function: 1580 | ld de, HELP_MSG 1581 | call bios_output_string 1582 | ld de,command_table 1583 | help_function_again: ; have we run out of commands? 1584 | ld a, (de) ; length of string 1585 | cp 0x00 1586 | ret z 1587 | ld hl, TMP_BUFFER 1588 | ld b, a ; B will contain the length 1589 | inc de ; DE point to the string itself 1590 | help_func_loop: ; copy the command-name to the buffer 1591 | ld a,(de) 1592 | ld (hl),a 1593 | inc hl 1594 | inc de 1595 | djnz help_func_loop 1596 | ld a, 0x0d 1597 | ld (hl),a 1598 | inc hl 1599 | ld a, '$' 1600 | ld (hl),a 1601 | push de 1602 | ld a,(de) 1603 | cp 1 1604 | jr z, help_skip_this 1605 | call show_tab 1606 | ld de, TMP_BUFFER 1607 | call bios_output_string 1608 | 1609 | help_skip_this: 1610 | pop de 1611 | inc de ; skip hidden-flag 1612 | inc de ; skip addr1 1613 | inc de ; skip addr2 1614 | jr help_function_again 1615 | 1616 | 1617 | 1618 | ; 1619 | ; Command-handler INVENTORY 1620 | ; 1621 | inventory_function: 1622 | call player_has_objects ; is the player carrying: 1623 | cp 0 1624 | jr z, inventory_show_items ; Yes. Go show 1625 | 1626 | ld de,inventory_empty_message ; Otherwise "Carrying nothing" 1627 | call bios_output_string 1628 | ret 1629 | 1630 | inventory_show_items: 1631 | ld de,you_carrying_message 1632 | call bios_output_string 1633 | 1634 | ; 1635 | ; We know there are items being carried, so now we'll loop 1636 | ; over them and print them out appropriately. 1637 | ; 1638 | ld IX, items 1639 | ld DE, ITEM_ENTRY_LENGTH 1640 | ld b, ITEM_COUNT 1641 | inventory_test_item: 1642 | 1643 | ; Get the location of the object. 1644 | ld a, (IX+ITEM_TABLE_LOCATION_OFFSET) 1645 | 1646 | ; Is this carried? 1647 | cp ITEM_CARRIED 1648 | jr nz, inv_item_not_carried 1649 | 1650 | ; OK the item is carried, show it out. 1651 | PUSH_ALL 1652 | 1653 | ; Show a tab 1654 | call show_tab 1655 | 1656 | ; show the item 1657 | ld e,(IX + ITEM_TABLE_DESCRIPTION_OFFSET) 1658 | ld d,(IX + ITEM_TABLE_DESCRIPTION_OFFSET+1) 1659 | call bios_output_string 1660 | 1661 | ; then a newline 1662 | call show_newline 1663 | POP_ALL 1664 | 1665 | inv_item_not_carried: 1666 | ; bump to the next item 1667 | add ix,de 1668 | djnz inventory_test_item 1669 | ret 1670 | 1671 | 1672 | 1673 | ; 1674 | ; Command-Handler WRAP 1675 | ; 1676 | ; We're called with an argument which is numeric 1677 | ; 1678 | wrap_function: 1679 | call input_second_term 1680 | cp 0 1681 | jr z, wrap_handle_argument 1682 | show_wrap: 1683 | ; show the message 1684 | ld de, WRAP_IS_MSG 1685 | call bios_output_string 1686 | 1687 | ld hl, WRAP_LOCATION 1688 | ld a, (hl) 1689 | call show_a_register 1690 | 1691 | call show_newline 1692 | ret 1693 | 1694 | wrap_handle_argument: 1695 | push hl 1696 | pop de 1697 | call string_to_uint16 1698 | push hl 1699 | pop de 1700 | ld hl, WRAP_LOCATION 1701 | ld (hl),e 1702 | jr show_wrap 1703 | 1704 | 1705 | ; 1706 | ; Command-Handler LOOK 1707 | ; 1708 | ; Show the current location. If the seen-flag has not been set then 1709 | ; show the long description and set the flag. 1710 | ; 1711 | ; There is a special case here to handle the case where the user types 1712 | ; 1713 | ; LOOK AT XXX 1714 | ; 1715 | ; If we see "LOOK AT XXX" then we replace the command with "EXAMINE YYYY" 1716 | ; 1717 | look_function: 1718 | 1719 | ld de, LOOK_AT ;; "LOOK AT " 1720 | ld b, 7 ;; strlen("LOOK_AT ") 1721 | ld hl, INPUT_BUFFER+2 1722 | call CompareStringsWithLength 1723 | jr nz,look_at_environment 1724 | 1725 | ; OK we have a sleazy hack here. 1726 | ; 1727 | ; We want to convert: 1728 | ; 1729 | ; "LOOK AT PIE" 1730 | ; 1731 | ; to 1732 | ; 1733 | ; "EXAMINE PIE" 1734 | ; 1735 | ; Happily the length of "LOOK AT" is the same as the length of 1736 | ; EXAMINE. 1737 | ; 1738 | ; Edit the input-buffer in-place 1739 | ld hl, INPUT_BUFFER+2 1740 | ld (hl), "E" 1741 | inc hl 1742 | ld (hl), "X" 1743 | inc hl 1744 | ld (hl), "A" 1745 | inc hl 1746 | ld (hl), "M" 1747 | inc hl 1748 | ld (hl), "I" 1749 | inc hl 1750 | ld (hl), "N" 1751 | inc hl 1752 | ld (hl), "E" 1753 | inc hl 1754 | ld (hl), " " 1755 | 1756 | jp examine_function 1757 | 1758 | look_at_environment: 1759 | ld de,LOOK_FUNCTION_PREFIX 1760 | call bios_output_string 1761 | ld hl, CURRENT_LOCATION 1762 | ld b,(hl) 1763 | ld c, LOCATION_ENTRY_LENGTH 1764 | call multiply 1765 | ld de, location_table ; add to base of location table 1766 | add hl,de 1767 | 1768 | ; ok we point to the table - show the short version 1769 | ld e, (hl) 1770 | inc hl 1771 | ld d, (hl) 1772 | inc hl 1773 | call bios_output_string 1774 | 1775 | ld e, (hl) ; get the long-message address in de 1776 | inc hl 1777 | ld d, (hl) 1778 | inc hl 1779 | 1780 | ; input-buffer starts with L for look? 1781 | ; then always show 1782 | ld bc, INPUT_BUFFER+2 1783 | ld a, (bc) 1784 | cp 'L' 1785 | jr z,look_show_extended 1786 | 1787 | ; otherwise if we've seen this room don't show the 1788 | ; extended description or objects. 1789 | ld a, (hl) 1790 | cp 1 1791 | ret z 1792 | look_show_extended: 1793 | call bios_output_string 1794 | 1795 | ld (hl), 1 ; set the seen-flag 1796 | 1797 | 1798 | ; If no items are in this room we can return 1799 | call items_are_present 1800 | cp 0 1801 | ret nz 1802 | 1803 | ; Show "You see .." 1804 | ld de,you_see_message 1805 | call bios_output_string 1806 | 1807 | ; 1808 | ; OK at this point we have looked at the room, and 1809 | ; we want to see the items there. 1810 | ; 1811 | ; Walk over the list of items - and show any which 1812 | ; are present in this room. 1813 | ; 1814 | ld IX, items 1815 | ld b, ITEM_COUNT 1816 | 1817 | look_item_loop: 1818 | 1819 | ; Get the location of the object. 1820 | ld c, (IX+ITEM_TABLE_LOCATION_OFFSET) 1821 | 1822 | ; Get the current location 1823 | push hl 1824 | ld hl, CURRENT_LOCATION 1825 | ld a,(hl) 1826 | pop hl 1827 | 1828 | ; If the location of the object doesn't match it isn't here. 1829 | cp c 1830 | jr nz, item_not_present 1831 | 1832 | ; Get the description of the object in DE 1833 | ld e,(IX+ITEM_TABLE_DESCRIPTION_OFFSET) 1834 | ld d,(IX+ITEM_TABLE_DESCRIPTION_OFFSET+1) 1835 | 1836 | ; Is the description empty? Then the item is hidden. 1837 | ld a, d 1838 | or e 1839 | jr z, item_not_present 1840 | 1841 | PUSH_ALL 1842 | call show_tab 1843 | call bios_output_string 1844 | call show_newline 1845 | POP_ALL 1846 | item_not_present: 1847 | ; Since we have HL pointing to an entry we can move to the next 1848 | ; just by adding the length of the table-item. 1849 | ld de, ITEM_ENTRY_LENGTH 1850 | add IX,de 1851 | djnz look_item_loop 1852 | ret 1853 | 1854 | 1855 | 1856 | 1857 | ; 1858 | ; Command-handler MAGIC 1859 | ; 1860 | ; Show a different message each time this is called, until you are killed. 1861 | ; 1862 | magic_function: 1863 | ld hl, MAGIC_COUNT 1864 | ld a,(hl) 1865 | inc a 1866 | ld (hl),a 1867 | 1868 | cp 1 1869 | jr z, magic_one 1870 | 1871 | cp 2 1872 | jr z, magic_two 1873 | 1874 | cp 3 1875 | jr z, magic_three 1876 | 1877 | ; magic four 1878 | ld de, magic_four_msg 1879 | call bios_output_string 1880 | 1881 | ; you're dead 1882 | ld hl, PLAYER_MAGIC_OVERDOSE_FLAG 1883 | inc (hl) 1884 | ret 1885 | magic_one: 1886 | ld de, magic_one_msg 1887 | call bios_output_string 1888 | ret 1889 | magic_two: 1890 | ld de, magic_two_msg 1891 | call bios_output_string 1892 | ret 1893 | magic_three: 1894 | ld de, magic_three_msg 1895 | call bios_output_string 1896 | ret 1897 | 1898 | 1899 | 1900 | 1901 | ; 1902 | ; Command-Handler QUIT 1903 | ; 1904 | ; Terminate the game, and show the turn-count. 1905 | ; 1906 | quit_function: 1907 | ld de, QUIT_MSG 1908 | call bios_output_string 1909 | call turns_function 1910 | jp play_again 1911 | 1912 | 1913 | ;; 6. Bios test of wrapped text 1914 | bios_text_wrap_test: 1915 | call bios_clear_screen 1916 | ld hl, WRAP_LOCATION 1917 | ld a, (hl) 1918 | push af 1919 | ld (hl), 10 1920 | ld de, bios_wrap_test_message 1921 | call bios_output_string 1922 | pop af 1923 | ld (hl), a 1924 | jr pause_for_key_indirect 1925 | 1926 | 1927 | 1928 | bios_delay_test: 1929 | ; 4. delay test 1930 | call bios_clear_screen 1931 | ld de, delay_test_message 1932 | call bios_output_string 1933 | ld b, 30 1934 | delay_loop: 1935 | PUSH_ALL 1936 | call bios_delay 1937 | ld e, '.' 1938 | call bios_output_character 1939 | POP_ALL 1940 | djnz delay_loop 1941 | jr pause_for_key_indirect 1942 | 1943 | 1944 | ;; 7. bios dump of size 1945 | bios_size_dump: 1946 | call bios_clear_screen 1947 | ld de, bios_size_dump_code 1948 | call bios_output_string 1949 | ld hl, WRAP_LOCATION - ENTRYPOINT 1950 | call DispHL 1951 | 1952 | ld de, bios_size_dump_text 1953 | call bios_output_string 1954 | ld hl, end_of_source - WRAP_LOCATION 1955 | call DispHL 1956 | 1957 | ld de, bios_size_dump_total 1958 | call bios_output_string 1959 | ld hl, end_of_source - ENTRYPOINT 1960 | call DispHL 1961 | 1962 | pause_for_key_indirect: 1963 | jr pause_for_key 1964 | 1965 | ; 1966 | ; Command-handler: BIOS 1967 | ; 1968 | bios_function: 1969 | ; clear the screen & show the menu text 1970 | call bios_clear_screen 1971 | ld de, BIOS_MSG 1972 | call bios_output_string 1973 | bios_function_menu: 1974 | ; wait for a keypress 1975 | call bios_await_keypress 1976 | ; process the responses 1977 | cp '1' 1978 | jr z, bios_function_clear 1979 | cp '2' 1980 | jr z, bios_show_character 1981 | cp '3' 1982 | jr z, bios_input_test 1983 | cp '4' 1984 | jr z, bios_delay_test 1985 | cp '5' 1986 | jr z, bios_char_display 1987 | cp '6' 1988 | jr z, bios_text_wrap_test 1989 | cp '7' 1990 | jr z, bios_size_dump 1991 | cp '8' 1992 | jr z, bios_return_to_game_indirect 1993 | cp 'q' 1994 | jr z, bios_return_to_game_indirect 1995 | jr bios_function_menu 1996 | 1997 | bios_function_clear: 1998 | ; 1. Clear screen 1999 | call bios_clear_screen 2000 | jr bios_function 2001 | 2002 | bios_show_character: 2003 | ; 2. Show a character 2004 | ld e, '*' 2005 | call bios_output_character 2006 | jr bios_function_menu 2007 | 2008 | bios_input_test: 2009 | ; 3. Input test 2010 | call bios_clear_screen 2011 | 2012 | ; show the input-prompt 2013 | ld de, input_test_message 2014 | call bios_output_string 2015 | 2016 | ; read a line of text 2017 | ld de, INPUT_BUFFER 2018 | call bios_read_input 2019 | 2020 | ; get the amount of text entered 2021 | ld hl, INPUT_BUFFER+1 2022 | ld a,(hl) 2023 | 2024 | ; zero? 2025 | cp 0 2026 | jr nz, show_input_buffer 2027 | ld de, no_input 2028 | call bios_output_string 2029 | jr pause_for_key 2030 | show_input_buffer: 2031 | ; ok user entered something, get the length in B 2032 | ld hl, INPUT_BUFFER 2033 | inc hl 2034 | ld b, (hl) 2035 | skip_forward: 2036 | inc hl 2037 | djnz skip_forward 2038 | inc hl 2039 | ; terminate the buffer with '$' 2040 | ld (hl), '$' 2041 | 2042 | ; now show the prompt, and the users' input 2043 | ld de, you_entered_message 2044 | call bios_output_string 2045 | ld de, INPUT_BUFFER+2 2046 | call bios_output_string 2047 | pause_for_key: 2048 | ld de, press_key_continue 2049 | call bios_output_string 2050 | call bios_await_keypress 2051 | jr bios_function 2052 | 2053 | 2054 | ; work around the fact that our handler is too far away for a relative jump. 2055 | bios_return_to_game_indirect: 2056 | jr bios_return_to_game 2057 | 2058 | bios_char_display: 2059 | ; 5. character code display 2060 | call bios_clear_screen 2061 | ld de, press_key_continue 2062 | call bios_output_string 2063 | 2064 | ; Await a keypress, and save it 2065 | call bios_await_keypress 2066 | push af 2067 | 2068 | ; show explanation 2069 | ld de,you_pressed 2070 | call bios_output_string 2071 | 2072 | ; show decimal keycode 2073 | call show_a_register 2074 | 2075 | ; and the character itself 2076 | ld e, " " 2077 | call bios_output_character 2078 | ld e, "<" 2079 | call bios_output_character 2080 | pop af 2081 | ld e,a 2082 | call bios_output_character 2083 | ld e, ">" 2084 | call bios_output_character 2085 | 2086 | jr pause_for_key 2087 | 2088 | 2089 | bios_return_to_game: 2090 | ; 6. return to game 2091 | call bios_clear_screen 2092 | FAKE_LOOK 2093 | ret 2094 | 2095 | ; 2096 | ; Command-Handler SLEEP 2097 | ; 2098 | sleep_function: 2099 | 2100 | ld de, SLEEP_START_MSG 2101 | call bios_output_string 2102 | call show_dots 2103 | 2104 | ; Double the turn-count. (Cruel!) 2105 | ld hl,TURN_COUNT 2106 | ld a, (hl) 2107 | add a, a 2108 | ld (hl),a 2109 | 2110 | ld de, SLEEP_END_MSG 2111 | call bios_output_string 2112 | ret 2113 | 2114 | 2115 | ; 2116 | ; Command-Handler TURNS 2117 | ; 2118 | ; Show the number of turns a user has played 2119 | ; 2120 | turns_function: 2121 | call show_newline 2122 | ld de, PLAYER_TURN_COUNT 2123 | call bios_output_string 2124 | ld hl, TURN_COUNT 2125 | ld a, (hl) 2126 | call show_a_register 2127 | ld de, PLAYER_TURN_COUNT_END 2128 | call bios_output_string 2129 | ret 2130 | 2131 | 2132 | ; 2133 | ; Command-Handler UP 2134 | ; 2135 | ; Change location. Up is possible if you're not on the top-floor. 2136 | ; 2137 | ; When in the dark room "UP" moves you up two places. 2138 | ; 2139 | up_function: 2140 | ld hl, CURRENT_LOCATION 2141 | ld a, (hl) 2142 | cp 0 2143 | jr z, no_up ; can't go up 2144 | cp 1 2145 | jr z, up_on_middle_floor ; go up - possible victory condition 2146 | cp 2 2147 | jr z, up_on_ground_floor ; go up 2148 | cp 3 2149 | jr z, up_on_basement_floor ; go up 2150 | cp 4 2151 | jr z, up_on_dark_room ; go up two - to land in ground floor 2152 | 2153 | up_on_middle_floor: 2154 | ; Get the meteor? 2155 | ld hl, item_13_name 2156 | call get_item_by_name 2157 | 2158 | ; Get the location of the meteor. 2159 | ld a, (IX + ITEM_TABLE_LOCATION_OFFSET) 2160 | 2161 | ; Are we carrying it ? 2162 | cp ITEM_CARRIED 2163 | jr nz, up_on_ground_floor 2164 | 2165 | ; We're on the middle-floor, going up, carrying the meteor. 2166 | ; That's an easy victory. 2167 | ld hl, PLAYER_WON_FLAG 2168 | inc (hl) 2169 | 2170 | ; Show the successful result 2171 | ld de, meteor_saves_the_day 2172 | call bios_output_string 2173 | ret 2174 | 2175 | no_up: 2176 | ld de, no_up_msg 2177 | call bios_output_string 2178 | ret 2179 | 2180 | up_on_dark_room: 2181 | ld hl, CURRENT_LOCATION 2182 | dec (hl) 2183 | ; fall-through 2184 | 2185 | up_on_ground_floor: 2186 | up_on_basement_floor: 2187 | ld hl, CURRENT_LOCATION 2188 | dec (hl) 2189 | call look_function 2190 | ret 2191 | 2192 | 2193 | show_newline: 2194 | ld de, NEWLINE 2195 | jp bios_output_string 2196 | 2197 | show_tab: 2198 | PUSH_ALL 2199 | ld de, TAB 2200 | call bios_output_string 2201 | POP_ALL 2202 | ret 2203 | 2204 | 2205 | ; 2206 | ; Command-Handler WAIT 2207 | ; 2208 | ; Nothing happens. 2209 | ; 2210 | wait_function: 2211 | ld de, WAIT_CMD_MSG 2212 | call bios_output_string 2213 | ret 2214 | 2215 | ; }} 2216 | 2217 | 2218 | 2219 | ;******************************************************************** 2220 | ; CUSTOM OBJECT HOOKS 2221 | ;******************************************************************** 2222 | ; {{ 2223 | 2224 | ; When you drop the mirror it cracks. 2225 | ; 2226 | ; Move the "mirror" item to the current location, as per-usual, 2227 | ; but also update the item-name and expanded description. 2228 | ; 2229 | ; We record that we've broken it in the item-state, so only dropping 2230 | ; it the first time triggers this behaviour. 2231 | drop_mirror_fn: 2232 | ; find the mirror 2233 | ld hl, item_1_name 2234 | call get_item_by_name 2235 | 2236 | ; Once we've found the item IX will point to the entry 2237 | ; 2238 | ; Change the location to the current one 2239 | ld hl, CURRENT_LOCATION 2240 | ld a,(hl) 2241 | ld (IX + ITEM_TABLE_LOCATION_OFFSET), a 2242 | 2243 | ; Was the mirror already dropped? 2244 | ld a,(IX + ITEM_TABLE_STATE_OFFSET) 2245 | cp MIRROR_BROKEN 2246 | jr nz, drop_mirror_break_it 2247 | 2248 | ; OK the mirror was already broken, so we don't 2249 | ; need to do anything special. Just say "You drop it" 2250 | ; and return. 2251 | ld de,you_drop_it 2252 | call bios_output_string 2253 | ret 2254 | 2255 | drop_mirror_break_it: 2256 | ; Update the state to record that it was broken. 2257 | ld (IX + ITEM_TABLE_STATE_OFFSET), MIRROR_BROKEN 2258 | 2259 | ; change the description 2260 | ld de, item_1_desc_broken 2261 | ld hl, item_first_desc 2262 | ld (hl), e 2263 | inc hl 2264 | ld (hl), d 2265 | 2266 | ; change the extended description 2267 | ld de, item_1_long_broken 2268 | ld hl, item_first_edesc 2269 | ld (hl), e 2270 | inc hl 2271 | ld (hl), d 2272 | 2273 | ; Show the drop message 2274 | ld de, MIRROR_DROP_FUN 2275 | call bios_output_string 2276 | ret 2277 | 2278 | 2279 | ; When you EXAMINE ROOM you'll end up here 2280 | examine_room_fn: 2281 | FAKE_LOOK 2282 | ret 2283 | 2284 | ; When you EXAMINE RUG you'll end here. 2285 | ; 2286 | ; If the rug is on the ground: 2287 | ; show the user a message and 2288 | ; make the trapdoor appear 2289 | ; 2290 | ; If the rug is in your posession just show the description 2291 | examine_rug_fn: 2292 | 2293 | ; Get the rug object 2294 | ld hl, item_7_name 2295 | call get_item_by_name 2296 | 2297 | ; Get the location which it appears 2298 | ld a,(IX+ITEM_TABLE_LOCATION_OFFSET) 2299 | 2300 | ; Is the item in your possessions? 2301 | cp ITEM_CARRIED 2302 | jr nz, rug_on_ground 2303 | 2304 | ; The rug must be in your possession, just show the extended 2305 | ; description. 2306 | just_show_rug_details: 2307 | ld e, (IX+ITEM_TABLE_EXT_DESCRIPTION_OFFSET) 2308 | ld d, (IX+ITEM_TABLE_EXT_DESCRIPTION_OFFSET+1) 2309 | call bios_output_string 2310 | 2311 | ret 2312 | 2313 | rug_on_ground: 2314 | ;; If we're in the ground-floor we can show more details 2315 | ;; if we're not we'll just show the overview 2316 | ld hl, CURRENT_LOCATION 2317 | ld a,(hl) 2318 | cp 2 2319 | jr nz, just_show_rug_details 2320 | 2321 | ;; We're in the ground floor so we can do the reveal 2322 | ld de, rug_detail_msg 2323 | call bios_output_string 2324 | call make_trapdoor_visible 2325 | ret 2326 | 2327 | 2328 | 2329 | ; When you EXAMINE DESK you might find a meteor. 2330 | ; 2331 | ; If the meteor is hidden 2332 | ; it will become visible 2333 | ; 2334 | ; Otherwise you just see the desk. 2335 | ; 2336 | examine_desk_fn: 2337 | 2338 | ; Get the meteor item 2339 | ld hl, item_13_name 2340 | call get_item_by_name 2341 | 2342 | ; Get the state of the meteor 2343 | ld a, (IX+ITEM_TABLE_STATE_OFFSET) 2344 | 2345 | ; If the state of the meteor is hidden, then show it 2346 | cp 0 2347 | jr z, meteor_hidden 2348 | 2349 | ld de, item_9_long 2350 | call bios_output_string 2351 | ret 2352 | 2353 | meteor_hidden: 2354 | 2355 | ; Update the state of the desk to show we've examined it 2356 | ld (IX+ITEM_TABLE_STATE_OFFSET), 1 2357 | ld (IX+ITEM_TABLE_LOCATION_OFFSET), 1 2358 | ; desk description 2359 | ld de, item_9_long 2360 | call bios_output_string 2361 | 2362 | ; desk addendum 2363 | ld de, desk_has_meteor 2364 | call bios_output_string 2365 | ret 2366 | 2367 | 2368 | 2369 | ; 2370 | ; When you TAKE the meteor it only works if the item-state is 1 2371 | ; 2372 | ; This is set by examine desk 2373 | ; 2374 | take_meteor_fn: 2375 | ; Get the meteor item 2376 | ld hl, item_13_name 2377 | call get_item_by_name 2378 | 2379 | ; Get the state of the meteor - we must have discovered 2380 | ; it by examining the desk before we can take it. 2381 | ld a, (IX + ITEM_TABLE_STATE_OFFSET) 2382 | 2383 | ; State is 0? 2384 | cp 0 2385 | jr nz, take_meteor 2386 | 2387 | ld de,item_not_present_msg 2388 | call bios_output_string 2389 | ret 2390 | 2391 | take_meteor: 2392 | ; update the object location to be in the player's posession 2393 | ld (IX + ITEM_TABLE_LOCATION_OFFSET), ITEM_CARRIED 2394 | 2395 | ; Now we've taken the item show the users (updated) 2396 | ; inventory. 2397 | call inventory_function 2398 | ret 2399 | 2400 | 2401 | ; When you TAKE the RUG a trapdoor will be revealed: 2402 | ; 2403 | ; - If it was already hidden 2404 | ; 2405 | ; - AND you're in the basement 2406 | ; 2407 | take_rug_fn: 2408 | 2409 | ; Get the rug object 2410 | ld hl, item_7_name 2411 | call get_item_by_name 2412 | 2413 | ; Move the rug into the player's possession. 2414 | ld (IX+ITEM_TABLE_LOCATION_OFFSET),ITEM_CARRIED 2415 | 2416 | ; Taking the rug should only trigger this behaviour once. 2417 | ; 2418 | ; Since we have a piece of state with each object we can use that. 2419 | ld a, (IX + ITEM_TABLE_STATE_OFFSET) 2420 | 2421 | ; If the state contains non-zero we've done this 2422 | ; before so we just show the inventory as we would 2423 | ; for a normal "take xxx" action. 2424 | cp 0 2425 | jr nz, get_rug_show_inv 2426 | 2427 | ; Store 1 in the state, so we don't do this again. 2428 | ld (IX + ITEM_TABLE_STATE_OFFSET),1 2429 | 2430 | ; Is the trapdoor already present? If so we don't need 2431 | ; to do anything special. 2432 | 2433 | ; get the trapdoor 2434 | ld hl, item_11_name 2435 | call get_item_by_name 2436 | 2437 | ; Get the location of the object in A 2438 | ld a, (IX+ITEM_TABLE_LOCATION_OFFSET) 2439 | 2440 | ; trapdoor in the basement? No special action required 2441 | cp 2 2442 | jr z, get_rug_show_inv 2443 | 2444 | ; show a message telling the user about the trapdoor. 2445 | ld de, rug_taken_msg 2446 | call bios_output_string 2447 | 2448 | ; And make the trapdoor visible 2449 | call make_trapdoor_visible 2450 | ret 2451 | 2452 | get_rug_show_inv: 2453 | call inventory_function 2454 | ret 2455 | 2456 | 2457 | ; When you USE the TORCH it lights. 2458 | ; 2459 | ; This means updating the item-state to have the LIT attribute 2460 | ; 2461 | toggle_torch_fn: 2462 | ; find the torch 2463 | ld hl, item_3_name 2464 | call get_item_by_name 2465 | 2466 | cp 0 2467 | jr z,use_torch_found_t 2468 | 2469 | ld de, failed_find_torch_msg 2470 | call bios_output_string 2471 | ret 2472 | use_torch_found_t: 2473 | 2474 | ; The item-table entry will be stored in HL and IX 2475 | ; Get the state in A 2476 | ld a, (IX+ITEM_TABLE_STATE_OFFSET) 2477 | 2478 | cp TORCH_STATE_UNLIT 2479 | jr z, torch_was_off 2480 | cp TORCH_STATE_LIT 2481 | jr z, torch_was_on 2482 | 2483 | ld de, torch_bogus_state_msg 2484 | call bios_output_string 2485 | ret 2486 | 2487 | torch_was_off: 2488 | ; update state 2489 | ld (IX+ITEM_TABLE_STATE_OFFSET), TORCH_STATE_LIT 2490 | 2491 | ; update the description 2492 | ld hl, torch_item_desc 2493 | ld de, item_3_desc_on 2494 | ld (hl), e 2495 | inc hl 2496 | ld (hl), d 2497 | 2498 | ld hl, torch_item_long 2499 | ld de, item_3_long_on 2500 | ld (hl), e 2501 | inc hl 2502 | ld (hl), d 2503 | 2504 | ; show message 2505 | ld de, TORCH_ON_MSG 2506 | call bios_output_string 2507 | 2508 | ; If you're in the dark-place we'll now warp you to the 2509 | ; normal basement. 2510 | ld hl, CURRENT_LOCATION 2511 | ld a,(hl) 2512 | cp 4 2513 | jr nz, out_of_here 2514 | ld (hl),3 2515 | 2516 | ; show that you can see where you are 2517 | ld de,NOW_YOU_SEE 2518 | call bios_output_string 2519 | 2520 | ; show the location 2521 | FAKE_LOOK 2522 | 2523 | out_of_here: 2524 | ret 2525 | 2526 | 2527 | torch_was_on: 2528 | ; update state 2529 | ld (IX+ITEM_TABLE_STATE_OFFSET), TORCH_STATE_UNLIT 2530 | 2531 | ; update the description 2532 | ld hl, torch_item_desc 2533 | ld de, item_3_desc_off 2534 | ld (hl), e 2535 | inc hl 2536 | ld (hl), d 2537 | 2538 | ld hl, torch_item_long 2539 | ld de, item_3_long_off 2540 | ld (hl), e 2541 | inc hl 2542 | ld (hl), d 2543 | 2544 | ; show the message 2545 | ld de, TORCH_OFF_MSG 2546 | call bios_output_string 2547 | 2548 | ; If you're in the normal basement we'll warp you to the dark-place. 2549 | ld hl, CURRENT_LOCATION 2550 | ld a,(hl) 2551 | cp 3 2552 | ret nz 2553 | ld (hl),4 2554 | 2555 | ld de, NOW_YOU_ARE_BLIND 2556 | call bios_output_string 2557 | 2558 | ; show the location 2559 | FAKE_LOOK 2560 | ret 2561 | 2562 | 2563 | ; When you OPEN the TRAPDOOR it opens. 2564 | ; 2565 | ; Find the trapdoor, and update the state to be "OPEN". 2566 | ; 2567 | toggle_trapdoor_function: 2568 | ; find the trapdoor 2569 | ld hl, item_11_name 2570 | call get_item_by_name 2571 | 2572 | ; get the state offset 2573 | ld a,(IX+ITEM_TABLE_STATE_OFFSET) 2574 | 2575 | ; update the state to be open 2576 | cp TRAPDOOR_STATE_INVISIBLE 2577 | jr nz, not_invisible 2578 | 2579 | ld de, item_not_present_msg 2580 | call bios_output_string 2581 | ret 2582 | not_invisible: 2583 | cp TRAPDOOR_STATE_CLOSED 2584 | jr z, trapdoor_was_closed 2585 | cp TRAPDOOR_STATE_OPEN 2586 | jr z, trapdoor_was_open 2587 | 2588 | ; bug 2589 | ld de,trapdoor_bogus_state_msg 2590 | call bios_output_string 2591 | ret 2592 | 2593 | trapdoor_was_closed: 2594 | ; change the state 2595 | ld (IX+ITEM_TABLE_STATE_OFFSET), TRAPDOOR_STATE_OPEN 2596 | 2597 | ; update the description 2598 | ld hl, trapdoor_desc 2599 | ld de, item_11_open 2600 | ld (hl),e 2601 | inc hl 2602 | ld (hl),d 2603 | ld hl, trapdoor_edesc 2604 | ld de, item_11_desc_open 2605 | ld (hl),e 2606 | inc hl 2607 | ld (hl),d 2608 | 2609 | ; Show the message 2610 | ld de, TRAPDOOR_OPEN_MSG 2611 | call bios_output_string 2612 | ret 2613 | 2614 | trapdoor_was_open: 2615 | ; change the state 2616 | ld (IX+ITEM_TABLE_STATE_OFFSET), TRAPDOOR_STATE_CLOSED 2617 | 2618 | ; update the description 2619 | ld hl, trapdoor_desc 2620 | ld de, item_11_closed 2621 | ld (hl),e 2622 | inc hl 2623 | ld (hl),d 2624 | ld hl, trapdoor_edesc 2625 | ld de, item_11_desc_closed 2626 | ld (hl),e 2627 | inc hl 2628 | ld (hl),d 2629 | 2630 | ; Show the message 2631 | ld de, TRAPDOOR_CLOSED_MSG 2632 | call bios_output_string 2633 | ret 2634 | 2635 | 2636 | 2637 | 2638 | ; When you USE the GENERATOR you win. 2639 | ; 2640 | use_generator_fn: 2641 | ; find the generator 2642 | ld hl, item_0_name 2643 | call get_item_by_name 2644 | 2645 | cp 0 2646 | jr z,use_generator_found_t 2647 | 2648 | ld de, failed_find_generator_msg 2649 | call bios_output_string 2650 | ret 2651 | 2652 | use_generator_found_t: 2653 | 2654 | ; You can only win if the generator is used on the top-floor 2655 | ld hl, CURRENT_LOCATION 2656 | ld a,(hl) 2657 | cp 0 2658 | jr z, use_generator_won_fn 2659 | 2660 | ld de, use_gen_location 2661 | jr print_and_return 2662 | use_generator_won_fn: 2663 | ld hl, PLAYER_WON_FLAG 2664 | ld (hl),1 2665 | 2666 | ld de, use_generator_won 2667 | print_and_return: 2668 | call bios_output_string 2669 | ret 2670 | 2671 | ; }} 2672 | 2673 | include "bios.z80" 2674 | 2675 | 2676 | 2677 | 2678 | 2679 | ;******************************************************************** 2680 | ; Data / State Storage 2681 | ;******************************************************************** 2682 | ; {{ 2683 | WRAP_LOCATION: 2684 | IF SPECTRUM 2685 | WRAP_WIDTH: DB 50 2686 | ELSE 2687 | WRAP_WIDTH: DB 60 2688 | ENDIF 2689 | per_game_state_start: 2690 | TURN_COUNT: 2691 | db 0 ; count of turns 2692 | CURRENT_LOCATION: 2693 | db 0 ; offset into location table 2694 | PLAYER_MAGIC_OVERDOSE_FLAG: 2695 | db 0 ; 1 iff the player overdosed on magic. 2696 | PLAYER_DEAD_FLAG: 2697 | db 0 ; 1 iff player is dead 2698 | PLAYER_WON_FLAG: 2699 | db 0 ; 1 iff player has won 2700 | MAGIC_COUNT: 2701 | db 0 ; state for magic-function 2702 | SHIP_WARNING: 2703 | db REMINDER_TURN_COUNT ; State for showing a "ship closer" message. 2704 | ; }} 2705 | 2706 | 2707 | ;******************************************************************** 2708 | ; String Area 2709 | ;******************************************************************** 2710 | ; {{ 2711 | call_911_msg: 2712 | call_999_msg: 2713 | call_police_msg: 2714 | db 0x0d, "Unfortunately Mayor Goodway's budget mishandling have resulted " 2715 | db "in a lack of a functioning police force.", 0x0d 2716 | db 0x0d, "The best you can do is call a bunch of dogs, and their teenaged handler.", 0x0d, "$" 2717 | call_unknown_msg: 2718 | db 0x0d, "I'm sorry I don't know who that is.", 0x0d, "$" 2719 | call_ghostbusters_msg: 2720 | db 0x0d, "Who you gonna call? Really?", 0x0d, "$" 2721 | call_ryder_msg: 2722 | db 0x0d, "Ryder here! No pup is too small, no job is too big!", 0x0d 2723 | db 0x0d, "Please leave a message after the beep, and I'll get back to you soon!" 2724 | db 0x0d, 0x0d, "Sorry!", 0x0d, "$" 2725 | call_skye_msg: 2726 | db 0x0d, "Skye responds quickly, but over the sound of air roaring down the phone you " 2727 | db "cannot hear what she's saying.", 0x0d 2728 | db "She must be a bit up in the air at the moment.", 0x0d, "$" 2729 | call_steve_msg: 2730 | db 0x0d, "Steve doesn't publish his phone number.", 0x0d 2731 | db 0x0d, "But emails to steve@steve.fi would be most welcome.", "$" 2732 | call_rubble_msg: 2733 | db 0x0d, "Rubble doesn't answer your call.", 0x0d 2734 | db 0x0d, "He's probably enjoying a nap.", 0x0d, "$" 2735 | call_me_msg: 2736 | db 0x0d, "Debbie Harry says 'hello', before hanging up." 2737 | ; FALL-THROUGH 2738 | NEWLINE: 2739 | db 0x0d, "$" 2740 | TAB: 2741 | db " $" 2742 | kill_dog: 2743 | db 0x0d, "We've all been there, we've all had that thought, but that's not the answer.", 0x0d 2744 | db "I'm sorry, I really am, but nobody ever dies in Paw Patrol, sadly.", 0x0d, "$" 2745 | invalid_msg: 2746 | db 0x0d, "I did not understand your input.", 0x0d, 0x0d 2747 | db "Enter 'HELP' to see some of our commands.", 0x0d, "$" 2748 | BAD_LANGUAGE_MSG: 2749 | db 0x0d, "What language!", 0x0d, "$" 2750 | NO_PHONE_HERE: 2751 | db 0x0d, "You can't see a telephone to use here!", 0x0d, "$" 2752 | magic_one_msg: 2753 | db 0x0d, "Magic happens.", 0x0d, "$" 2754 | magic_two_msg: 2755 | db 0x0d, "Magic intensifies.", 0x0d, "$" 2756 | magic_three_msg: 2757 | db 0x0d, "The sensation of magic screaming through your veins gives you a heady rush. " 2758 | db "This can't be good for you, maybe stop now?", 0x0d, "$" 2759 | magic_four_msg: 2760 | db 0x0d , "You couldn't draw the line, could you?", 0x0d, 0x0d 2761 | db "The magic flooding your body is too powerful, and you're finding it impossible " 2762 | db "to breathe. With a wail of frustration you topple backwards, clutching " 2763 | db "at your chest.", 0x0d, 0x0d 2764 | db "You're dying, soon the end will come.", 0x0d, 0x0d, "$" 2765 | 2766 | CALL_WHO_MSG: 2767 | db 0x0d, "Call who?", 0x0d, "$" 2768 | no_up_msg: 2769 | db 0x0d, "You can't go up from here.", 0x0d, "$" 2770 | no_down_msg: 2771 | db 0x0d, "You can't go down from here.", 0x0d, "$" 2772 | rug_taken_msg: 2773 | db 0x0d, "You roll up the rug to make carrying it more straightforward, and as you do so " 2774 | db "you notice that it was covering a trapdoor.", 0x0d, "$" 2775 | failed_find_torch_msg: 2776 | db 0x0d, "BUG:Failed to find torch.", 0x0d, "$" 2777 | torch_bogus_state_msg: 2778 | db 0x0d, "BUG:Bogus state for the torch", 0x0d, "$" 2779 | trapdoor_bogus_state_msg: 2780 | db 0x0d, "BUG:Bogus state for the trapdoor", 0x0d, "$" 2781 | failed_find_trapdoor_msg: 2782 | db 0x0d, "BUG:Failed to find trapdoor", 0x0d, "$" 2783 | rug_detail_msg: 2784 | db 0x0d, "You examine the rug, which shows nothing special. " 2785 | db "But while looking at the ground you notice that the rug covered a trapdoor.", 0x0d, "$" 2786 | rug_moved_message: 2787 | db 0x0d, "You move the rug, which reveals a trapdoor which had previously been hidden.", 0x0d, "$" 2788 | TRAPDOOR_OPEN_MSG: 2789 | db 0x0d, "The trapdoor opens, showing a murky set of steps leading downwards into shadow.", 0x0d, "$" 2790 | TRAPDOOR_CLOSED_MSG: 2791 | db 0x0d, "The trapdoor is now closed, hopefully trapping the grue forevermore", 0x0d, "$" 2792 | failed_find_generator_msg: 2793 | db 0x0d, "BUG:Failed to find the generator.", 0x0d, "$" 2794 | use_gen_location: 2795 | db 0x0d, "You cannot see anywhere to connect the generator to." 2796 | db 0x0d, "$" 2797 | use_generator_won: 2798 | db 0x0d, "You connect the generator to the console, and turn it on. With a steady thrum the generator begins to provide power.", 0x0d 2799 | db "Success! The main-light turns on! " 2800 | db "The boat sees the light, and begins to execute a sharp turn to port, " 2801 | db "it looks like it will make it." 2802 | db 0x0d 2803 | db "Congratulations, you will survive and so will the lighthouse!", 0x0d 2804 | db "$" 2805 | 2806 | meteor_saves_the_day: 2807 | db 0x0d, "The glowing meteor you're carrying suddenly flares into an even brighter glow.", 0x0d 2808 | db 0x0d 2809 | db "The glow is almost blinding, and must surely be visible through the windows of " 2810 | db "the lighthouse. With a moment of inspiration you hold it above your head, and " 2811 | db "it gets brighter still, the light arcing out over the sea in giant curved beam. " 2812 | db "The boat sees the light, and begins to execute a sharp turn to port, " 2813 | db "it looks like it will make it.", 0x0d 2814 | db 0x0d 2815 | db "Congratulations!", 0x0d 2816 | db "$" 2817 | QUIT_MSG: 2818 | db 0x0d, "You said 'QUIT' so we terminate!", 0x0d, "$" 2819 | BIOS_MSG: 2820 | db 0x0d, "BIOS test-function", 0x0d 2821 | db 0x0d, " 1 Clear the screen" 2822 | db 0x0d, " 2 Write a character" 2823 | db 0x0d, " 3 Input test" 2824 | db 0x0d, " 4 Delay test" 2825 | db 0x0d, " 5 Input character display" 2826 | db 0x0d, " 6 Wrapped text output test" 2827 | db 0x0d, " 7 Dump game sizes" 2828 | db 0x0d, " 8 Return to the game" 2829 | db 0x0d 2830 | db "$" 2831 | bios_size_dump_code: 2832 | db 0x0d, "Code: $" 2833 | bios_size_dump_text: 2834 | db 0x0d, "Text: $" 2835 | bios_size_dump_total: 2836 | db 0x0d, "Total: $" 2837 | bios_wrap_test_message: 2838 | db "This is a long message which is entered as a string of text, it is expected that this will need to be wrapped a bunch of times since we've configured the width of the display to be ten characters.$" 2839 | input_test_message: 2840 | db "Please enter a string:$" 2841 | delay_test_message: 2842 | db "Test our delay function:", 0x0d,0x0d,"$" 2843 | you_entered_message: 2844 | db 0x0d, "You entered: $" 2845 | no_input: 2846 | db "You didn't enter anything.$" 2847 | you_pressed: 2848 | db 0x0d, 0x0d, "You pressed:$" 2849 | press_key_continue: 2850 | db 0x0d, 0x0d, "Press a key to continue $" 2851 | HELP_MSG: 2852 | db 0x0d, "The following commands are available:", 0x0d, "$" 2853 | EXAMINE_WHAT_MSG: 2854 | db 0x0d, "Examine what? Please try again.", 0x0d, "$" 2855 | GO_WHERE_MSG: 2856 | db 0x0d, "Go where? Please try again.", 0x0d, "$" 2857 | GET_WHAT_MSG: 2858 | db 0x0d, "Take what? Please try again.", 0x0d, "$" 2859 | DROP_WHAT_MSG: 2860 | db 0x0d, "Drop what? Please try again.", 0x0d, "$" 2861 | WRAP_IS_MSG: 2862 | db 0x0d, "Wrapping is set to $" 2863 | cant_take_that_msg: 2864 | db 0x0d, "You can't take that.", 0x0d, "$" 2865 | you_drop_it: 2866 | db 0x0d, "You drop it.", 0x0d, "$" 2867 | inventory_empty_message: 2868 | db 0x0d, "You are not carrying anything", 0x0d, "$" 2869 | you_carrying_message: 2870 | db 0x0d, "You are carrying:", 0x0d, "$" 2871 | you_see_message: 2872 | db 0x0d, "You see:", 0x0d, "$" 2873 | item_not_present_msg: 2874 | db 0x0d, "I can't see that here!", 0x0d, "$" 2875 | not_carrying_item_msg: 2876 | db 0x0d, "You're not carrying that!", 0x0d, "$" 2877 | MIRROR_DROP_FUN: 2878 | db 0x0d, "You drop the mirror, which cracks and breaks.", 0x0d, "$" 2879 | SLEEP_START_MSG: 2880 | db 0x0d, "You lay down and take a small nap.", 0x0d 2881 | db "$" 2882 | SLEEP_END_MSG: 2883 | db 0x0d, "You jerk awake, unsure of how much time has passed or how much closer the boat, " 2884 | db "and yourself, are to certain doom.", 0x0d 2885 | db "$" 2886 | WAIT_CMD_MSG: 2887 | db 0x0d, "You wait for a moment, lost in thought.", 0x0d, "$" 2888 | TORCH_ON_MSG: 2889 | db 0x0d, "You turn on the torch.", 0x0d, "$" 2890 | TORCH_OFF_MSG: 2891 | db 0x0d, "You turn the torch off.", 0x0d, "$" 2892 | you_were_eaten: 2893 | db 0x0d, "You were eaten by a grue.", 0x0d,0x0d 2894 | db "At least you were spared the sight of the ship crashing into the rocks, and no " 2895 | db "doubt causing the lighthouse itself to collapse, dooming you all.", 0x0d 2896 | db "$" 2897 | grue_message: 2898 | db 0x0d, "You hear the scrabble of claws getting closer, is the grue about to attack?", 0x0d, "$" 2899 | ship_closer_msg: 2900 | db 0x0d, "The ship is getting closer!", 0x0d, 0x0d 2901 | db "Hurry up, or you'll all be dead!", 0x0d, "$" 2902 | NOW_YOU_SEE: 2903 | db "Now you can see where you are:", 0x0d, "$" 2904 | NOW_YOU_ARE_BLIND: 2905 | db "Now you cannot see anything in front of you.", 0x0d, "$" 2906 | usage_message: 2907 | db 0x0d 2908 | db " .n. " 2909 | IF SPECTRUM 2910 | ELSE 2911 | db 27, "[1;4m" 2912 | ENDIF 2913 | db "The lighthouse of doom" 2914 | IF SPECTRUM 2915 | ELSE 2916 | db 27, "[1;0m" 2917 | ENDIF 2918 | db 0x0d 2919 | db " /___\\ ", 0x0d 2920 | db " [|||] ", 0x0d 2921 | db " [___] ", 0x0d 2922 | db " }-=-{ ", 0x0d 2923 | db " |-\" | ", 0x0d 2924 | db " |.-\"| p", 0x0d 2925 | db "~^=~^~-|_.-|~^-~^~ ~^~ -^~^~|\\ ~^-~^~-", 0x0d 2926 | db "^ .=.| _.|__ ^ ~ /| \\", 0x0d 2927 | db " ~ /:. \\\" _|_/\ ~ /_|__\\ ^", 0x0d 2928 | db ".-/::. | |\"\"|-._ ^ ~~~~", 0x0d 2929 | db " `===-'-----'\"\"` '-. ~", 0x0d 2930 | db " __.-' ^", 0x0d 2931 | db "", 0x0d 2932 | db "Written by Steve Kemp in 2021, version " 2933 | 2934 | ; 2935 | ; This file is updated with the current git-tag by `make release`. 2936 | ; 2937 | ; The github action/pipeline will do that when a new release is issued. 2938 | ; 2939 | include "version.z80" 2940 | 2941 | db ".", 0x0d 2942 | db 0x0d 2943 | db " https://github.com/skx/lighthouse-of-doom" 2944 | db 0x0d 2945 | db 0x0d 2946 | db "Any references to the Paw Patrol are entirely deliberate." 2947 | db 0x0d, 0x0d 2948 | db "Press any key to start.$" 2949 | 2950 | 2951 | play_again_msg: 2952 | db 0x0d, "Play again? (y/n)$" 2953 | play_again_no_msg: 2954 | db 0x0d, "Resetting", 0x0d, "$" 2955 | PLAYER_DEAD_MESSAGE: 2956 | db 0x0d, "Unfortunately you took too long to fix the broken light." 2957 | db 0x0d, "The ship ploughs into the base of your lighthouse, causing it to collapse around" 2958 | db 0x0d, "you." 2959 | db 0x0d, "You're buried and helpless, and as the darkness consumes you the sounds of the" 2960 | db 0x0d, "sailors thrashing in the water reach you, from a distance." 2961 | db 0x0d, "Let us hope some of them can swim to shore.." 2962 | 2963 | db "Game over - you're dead." 2964 | db 0x0d,"$" 2965 | PLAYER_WON_MESSAGE: 2966 | db 0x0d 2967 | db "You won!" 2968 | db 0x0d,"$" 2969 | PLAYER_TURN_COUNT: 2970 | db "You've played $" 2971 | PLAYER_TURN_COUNT_END: 2972 | db " turns." 2973 | db 0x0d,"$" 2974 | LOOK_FUNCTION_PREFIX: 2975 | db 0x0d, "You are in $" 2976 | LOOK_AT: 2977 | db "LOOK AT " 2978 | 2979 | ; 2980 | ; Location text 2981 | ; 2982 | location_0_short: 2983 | db "the top floor of the lighthouse.", 0x0d, 0x0d, "$" 2984 | location_0_long: 2985 | IF SPECTRUM 2986 | ELSE 2987 | db "The lighthouse has a spiral staircase which runs from top to bottom.", 0x0d, 0x0d 2988 | ENDIF 2989 | db "Through the window you can see the lights of an approaching ship, and you know " 2990 | db "that without the lighthouse's beacon it will surely crash upon the rocks your " 2991 | db "lighthouse is built upon. " 2992 | 2993 | IF SPECTRUM 2994 | db 0x0d 2995 | ELSE 2996 | db "If the ship crashes not only will the sailors drown, " 2997 | ENDIF 2998 | db "the lighthouse itself is liable to be seriously damaged.", 0x0d, 0x0d 2999 | db "Too bad the lighthouse light doesn't seem to be working..", 0x0d 3000 | db "$" 3001 | 3002 | location_1_short: 3003 | db "the middle floor of the lighthouse.", 0x0d, 0x0d, "$" 3004 | location_1_long: 3005 | db "This seems to be a relaxation-room, you see some comfy chairs, a work-desk, a telephone and various odds and ends. " 3006 | db "An impressive painting hangs over the desk, and a dog sleeps in a basket " 3007 | db "to the side of it.", 0x0d 3008 | db "$" 3009 | 3010 | location_2_short: 3011 | db "the ground floor of the lighthouse.", 0x0d, 0x0d, "$" 3012 | location_2_long: 3013 | db "The ground floor seems very crowded, with most of the room " 3014 | db "taken up by a coat-rack, boots, and similar things.", 0x0d 3015 | db "$" 3016 | 3017 | location_3_short: 3018 | db "the lighthouse basement.", 0x0d, 0x0d, "$" 3019 | location_3_long: 3020 | db "This seems to be a graveyard for discarded machinery, and", 0x0d 3021 | db "other random junk.", 0x0d 3022 | db "$" 3023 | 3024 | location_4_short: 3025 | db "a dark place.", 0x0d, 0x0d, "$" 3026 | location_4_long: 3027 | db "You cannot see anything, but you can certainly smell something animal-like.", 0x0d, 0x0d 3028 | db "In the distance you hear the scrabble of claws on concrete.", 0x0d, 0x0d 3029 | db "Could you have discovered the haunt of the mythical grue?", 0x0d 3030 | db "$" 3031 | 3032 | 3033 | ; 3034 | ; Item text 3035 | ; 3036 | item_0_name: db "GENERATOR" 3037 | item_0_desc: db "A small, portable, generator.$" 3038 | item_0_long: db "This is a small and easily carried diesel-power generator.", 0x0d 3039 | db "The generator seems to be full of fuel, and ready to go.", 0x0d 3040 | db "The important thing is probably working out where to use it.", 0x0d 3041 | db "$" 3042 | 3043 | item_1_name: db "MIRROR" 3044 | item_1_desc: db "A small mirror.$" 3045 | item_1_long: db "The mirror doesn't seem to be anything special.", 0x0d 3046 | db "But your reflection? It looks fabulous.", 0x0d 3047 | db "$" 3048 | 3049 | item_1_desc_broken: db "A small broken mirror.$" 3050 | item_1_long_broken: db "The mirror was once small and delicate.", 0x0d 3051 | db "But now it shows a warped reflection of yourself,", 0x0d 3052 | db "which is oddly unsettling.", 0x0d 3053 | db "$" 3054 | 3055 | item_3_name: db "TORCH" 3056 | item_3_desc_off: db "A small torch.$" 3057 | item_3_desc_on: db "A small torch, which is turned on.$" 3058 | item_3_long_off: db "The torch doesn't seem to be anything special," 3059 | db 0x0d 3060 | db "but you do notice that it contains batteries and can be turned on easily.", 0x0d 3061 | db "$" 3062 | item_3_long_on: db "The torch doesn't seem to be anything special." 3063 | db 0x0d, "$" 3064 | 3065 | item_5_name: db "DOG" 3066 | item_5_long: db "The dog seems to be sleeping quite deeply. As you examine him ", 0x0d 3067 | db "he mutters something about 'Apollo the Super-Pup' in his sleep,", 0x0d 3068 | db "before settling back into silence.", 0x0d 3069 | db "$" 3070 | 3071 | item_6_name: db "PAINTING" 3072 | item_6_long: db "The painting shows a teenager with spiky hair, surrounded by a group of dogs. They all look like good pups.$" 3073 | 3074 | item_7_name: db "RUG" 3075 | item_7_desc: db "A small rug.$" 3076 | item_7_long: db "The rug is made of coarse wool, roughly woven, but there's nothing remarkable " 3077 | db "about it.", 0x0d, "$" 3078 | 3079 | item_8_name: db "BOOK" 3080 | item_8_desc: db "A small black book.$" 3081 | item_8_long: db "This seems to be a book of phone numbers.", 0x0d, "$" 3082 | item_8_read: db 0x0d, "The little black book seems to contain names and phone numbers:", 0x0d, 0x0d 3083 | db " Police - 999", 0x0d 3084 | db " Ambulance - 999", 0x0d 3085 | db " Fire Service - 999", 0x0d 3086 | db " Paw Patrol - 999", 0x0d 3087 | db " Steve - ???", 0x0d 3088 | db 0x0d 3089 | db "Too bad there are no instructions on powering the main light, although there is " 3090 | db "an advert for a portable diesel generator being used as a bookmark.$" 3091 | 3092 | item_9_name: db "DESK" 3093 | item_9_long: db "The desk is made of solid wood, unlike everything else in the room.", 0x0d, "$" 3094 | desk_has_meteor: 3095 | db 0x0d 3096 | db "Towards the back of the desk you notice a small glowing rock, possibly a meteor?", 0x0d, "$" 3097 | 3098 | 3099 | item_10_name: db "TELEPHONE" 3100 | item_10_long: db "The telephone is an average telephone, which looks like it was made in the 80s. Perhaps you should try to CALL somebody?", 0x0d, "$" 3101 | 3102 | item_11_name: db "TRAPDOOR" 3103 | item_11_closed: db "A trapdoor.$" 3104 | item_11_open: db "An open trapdoor.$" 3105 | item_11_desc_closed: db "You cannot see anything special about the trapdoor.", 0x0d 3106 | db "Perhaps you should open it to explore further?", 0x0d, "$" 3107 | item_11_desc_open: db "An average looking trapdoor.", 0x0d, "$" 3108 | 3109 | 3110 | item_13_name: db "METEOR" 3111 | item_13_desc: db "A meteor fragment$" 3112 | item_13_long: db "This piece of rock looks like it came from a larger meteor, it is glowing with an eerie light.$" 3113 | 3114 | ; For "EXAMINE ROOM" 3115 | item_14_name: db "ROOM" 3116 | 3117 | item_15_name: db "GRUE" 3118 | 3119 | item_16_name: db "BASKET" 3120 | item_16_long: db "The dog-basket is a faded red colour, and covered with dog-hairs.", 0x0d, "$" 3121 | 3122 | item_17_name: db "COAT-RACK" 3123 | item_17_long: db "The coat-rack looks normal.", 0x0d, "$" 3124 | 3125 | item_18_name: db "CHAIR" 3126 | item_18_long: db "The chair is just a chair", 0x0d, "$" 3127 | 3128 | item_19_name: db "CHAIRS" 3129 | item_19_long: db "The chairs look very comfortable, but also covered in slightly too much dog-hair.", 0x0d, "$" 3130 | 3131 | item_20_name: db "BOOTS" 3132 | item_20_long: db "The boots are black, and shiny.", 0x0d, 0x0d 3133 | db "They're probably made for walking?", 0x0d, "$" 3134 | 3135 | item_21_name: db "JUNK" 3136 | item_21_long: db "The junk seems too fragile to move around and examine.", 0x0d, "$" 3137 | 3138 | item_22_name: db "MACHINERY" 3139 | item_22_long: db "The machinery doesn't seem very special.", 0x0d, "$" 3140 | 3141 | ; }} 3142 | 3143 | 3144 | 3145 | ;******************************************************************** 3146 | ; Table Area 3147 | ;******************************************************************** 3148 | ; {{ 3149 | 3150 | ; 3151 | ; Our command-table contains the commands we accept, a pointer to their 3152 | ; handlers, and a "hidden" flag. 3153 | ; 3154 | ; The HELP command will show all non-hidden commands. 3155 | ; 3156 | ; NOTE: There is no help-text for the commands 3157 | ; 3158 | command_table: 3159 | DEFB 3, 'CLS', 1 3160 | DEFW bios_clear_screen 3161 | DEFB 4, 'DOWN', 0 3162 | DEFW down_function 3163 | DEFB 4, 'DROP', 0 3164 | DEFW drop_function 3165 | DEFB 7, 'EXAMINE', 0 3166 | DEFW examine_function 3167 | DEFB 3, 'GET', 0 3168 | DEFW get_function 3169 | DEFB 4, 'HELP', 0 3170 | DEFW help_function 3171 | DEFB 9, 'INVENTORY', 0 3172 | DEFW inventory_function 3173 | DEFB 4, 'LOOK', 0 3174 | DEFW look_function 3175 | DEFB 4, 'QUIT', 0 3176 | DEFW quit_function 3177 | DEFB 4, 'WRAP', 0 3178 | DEFW wrap_function 3179 | DEFB 4, 'BIOS', 1 3180 | DEFW bios_function 3181 | DEFB 5, 'TURNS', 0 3182 | DEFW turns_function 3183 | DEFB 2, 'UP', 0 3184 | DEFW up_function 3185 | ;; synonyms 3186 | DEFB 5, 'CLEAR', 1 3187 | DEFW bios_clear_screen 3188 | DEFB 2, 'GO', 1 3189 | DEFW go_function 3190 | DEFB 4, 'TAKE', 1 3191 | DEFW get_function 3192 | DEFB 6, 'PICKUP', 1 3193 | DEFW get_function 3194 | ;; easter-eges 3195 | DEFB 4, 'CALL', 1 3196 | DEFW call_function 3197 | DEFB 4, 'DIAL', 1 3198 | DEFW call_function 3199 | DEFB 4, 'FUCK', 1 3200 | DEFW bad_language_function 3201 | DEFB 5, 'PHONE', 1 3202 | DEFW call_function 3203 | DEFB 4, 'SHIT', 1 3204 | DEFW bad_language_function 3205 | DEFB 5, 'XYZZY', 1 3206 | DEFW magic_function 3207 | DEFB 5, 'SLEEP', 1 3208 | DEFW sleep_function 3209 | DEFB 4, 'WAIT', 1 3210 | DEFW wait_function 3211 | ;; abbreviations 3212 | DEFB 1, 'L', 1 3213 | DEFW look_function 3214 | DEFB 1, 'Q', 1 3215 | DEFW quit_function 3216 | DEFB 3, 'INV', 1 3217 | DEFW inventory_function 3218 | ;; end of table 3219 | DEFB 0 3220 | 3221 | 3222 | ;; 3223 | ;; We now have a set of tables for per-item actions. 3224 | ;; 3225 | ;; Given an object such as "BOOK", "TORCH", etc, we allow custom verbs 3226 | ;; to be applied to those objects. So we can allow "READ BOOK", but not 3227 | ;; "READ TORCH". 3228 | ;; 3229 | ;; These tables use an identical format to the command-table above, even 3230 | ;; with the "hidden" flag - here it is ignored - just so we can reuse the 3231 | ;; search/lookup code for working with the tables. 3232 | ;; 3233 | book_actions: 3234 | DEFB 4, 'READ', 0 3235 | DEFW read_book_function 3236 | DEFB 0 3237 | 3238 | dog_actions: 3239 | DEFB 4, 'KILL', 0 3240 | DEFW kill_dog_function 3241 | DEFB 0 3242 | 3243 | generator_actions: 3244 | DEFB 5, 'START', 0 3245 | DEFW use_generator_fn 3246 | DEFB 3, 'USE', 0 3247 | DEFW use_generator_fn 3248 | DEFB 6, 'IGNITE', 0 3249 | DEFW use_generator_fn 3250 | DEFB 0 3251 | 3252 | rug_actions: 3253 | DEFB 4, 'MOVE', 0 3254 | DEFW move_rug_function 3255 | DEFB 0 3256 | 3257 | trapdoor_actions: 3258 | DEFB 4, 'OPEN', 0 3259 | DEFW toggle_trapdoor_function 3260 | DEFB 5, 'CLOSE', 0 3261 | DEFW toggle_trapdoor_function 3262 | DEFB 0 3263 | 3264 | torch_actions: 3265 | DEFB 5, 'LIGHT', 0 3266 | DEFW toggle_torch_fn 3267 | DEFB 6, 'IGNITE', 0 3268 | DEFW toggle_torch_fn 3269 | DEFB 3, 'USE', 0 3270 | DEFW toggle_torch_fn 3271 | DEFB 7, 'TURN ON', 0 3272 | DEFW toggle_torch_fn 3273 | DEFB 8, 'TURN OFF', 0 3274 | DEFW toggle_torch_fn 3275 | DEFB 0 3276 | 3277 | 3278 | ;; 3279 | ;; Now we have the custom functions defined, which are referenced in the 3280 | ;; item-specific command tables above: 3281 | ;; 3282 | 3283 | ;; Custom function "READ BOOK" 3284 | read_book_function: 3285 | ld de, item_8_read 3286 | call bios_output_string 3287 | ret 3288 | 3289 | ;; Custom function "KILL DOG" 3290 | kill_dog_function: 3291 | ld de, kill_dog 3292 | call bios_output_string 3293 | ret 3294 | 3295 | ;; Custom function "MOVE RUG" 3296 | move_rug_function: 3297 | call make_trapdoor_visible 3298 | ld de, rug_moved_message 3299 | call bios_output_string 3300 | ret 3301 | 3302 | ; 3303 | ; People table 3304 | ; 3305 | ; Similar to the command-table, each entry is: 3306 | ; 3307 | ; LENGTH "NAME HERE" Ptr-To-Response 3308 | ; 3309 | people_table: 3310 | DEFB 3, '112' 3311 | DEFW call_911_msg 3312 | DEFB 3, '911' 3313 | DEFW call_911_msg 3314 | DEFB 3, '999' 3315 | DEFW call_999_msg 3316 | DEFB 6, 'POLICE' 3317 | DEFW call_police_msg 3318 | DEFB 5, 'RYDER' 3319 | DEFW call_ryder_msg 3320 | DEFB 5, 'RIDER' 3321 | DEFW call_ryder_msg 3322 | DEFB 10, 'PAW PATROL' 3323 | DEFW call_ryder_msg 3324 | DEFB 4, 'SKYE' 3325 | DEFW call_skye_msg 3326 | DEFB 5, 'STEVE' 3327 | DEFW call_steve_msg 3328 | DEFB 6, 'RUBBLE' 3329 | DEFW call_rubble_msg 3330 | DEFB 2, 'ME' 3331 | DEFW call_me_msg 3332 | DEFB 12, 'GHOSTBUSTERS' 3333 | DEFW call_ghostbusters_msg 3334 | DEFB 0 3335 | 3336 | 3337 | ; 3338 | ; Location table 3339 | ; 3340 | ; Each location contains a pointer to a short-description, a pointer to 3341 | ; a long description, and a seen-flag. 3342 | ; 3343 | location_table: 3344 | DEFW location_0_short ; top-floor 3345 | DEFW location_0_long 3346 | DB 0 ; seen-flag 3347 | location_first: 3348 | DEFW location_1_short ; middle-floor 3349 | DEFW location_1_long 3350 | DB 0 ; seen-flag 3351 | 3352 | DEFW location_2_short ; ground-floor 3353 | DEFW location_2_long 3354 | DB 0 ; seen-flag 3355 | 3356 | DEFW location_3_short ; basement 3357 | DEFW location_3_long 3358 | DB 0 ; seen-flag 3359 | 3360 | DEFW location_4_short ; dark-place 3361 | DEFW location_4_long 3362 | DB 0 ; seen-flag 3363 | 3364 | LOCATION_ENTRY_LENGTH: EQU location_first - location_table 3365 | 3366 | 3367 | ; 3368 | ; Item table - Each item contains: 3369 | ; 3370 | ; 1. A pointer to the (internal) name. 3371 | ; 2. A pointer to a short description - empty for "hidden" items 3372 | ; 3. A pointer to a long description - shown by "EXAMINE $object" 3373 | ; 4. A pointer to a take-function. 3374 | ; 5. A pointer to a drop-function. 3375 | ; 6. A pointer to an examine-function. 3376 | ; If this is null then the extended description will be shown. 3377 | ; 7. A byte used for item-state. 3378 | ; 8. A byte to specify whether this can be taken. 3379 | ; 9. A byte to contain the location of the object. 3380 | ; 0xff == the player is carrying it. 3381 | ; 0xfe == can be found but won't be displayed when a location is entered. 3382 | ; 10. A word pointing to a table of custom actions. 3383 | ; 3384 | items: 3385 | item_name: 3386 | DEFW item_0_name ; generator 3387 | item_description_ptr: 3388 | DEFW item_0_desc 3389 | item_ext_description_ptr: 3390 | DEFW item_0_long 3391 | item_take_ptr: 3392 | DEFB 0,0 ; No take function 3393 | item_drop_ptr: 3394 | DEFB 0,0 ; No drop function 3395 | item_examine_ptr: 3396 | DEFB 0,0 ; No examine function 3397 | item_state: 3398 | DEFB 0 ; Item state 3399 | item_collectible: 3400 | DEFB 1 ; this item can be picked up 3401 | item_location: 3402 | DEFB 0x03 ; basement 3403 | item_custom_action: 3404 | DEFW generator_actions ; custom actions 3405 | 3406 | 3407 | ; 3408 | ; We have a function called "get_item_by_name" which returns a pointer 3409 | ; to the start of the item's table-entry. 3410 | ; 3411 | ; Rather than manually counting bytes to get access to the fields we 3412 | ; use some constants to access them - these constants are calculated here. 3413 | ; 3414 | ; e.g. Find a torch and get the location: 3415 | ; 3416 | ; find the item, so the base is in IX 3417 | ; 3418 | ; ld a, (IX+ ITEM_TABLE_LOCATION_OFFSET) 3419 | ; 3420 | ITEM_TABLE_NAME_OFFSET: EQU item_name - items 3421 | ITEM_TABLE_DESCRIPTION_OFFSET: EQU item_description_ptr - items 3422 | ITEM_TABLE_EXT_DESCRIPTION_OFFSET: EQU item_ext_description_ptr - items 3423 | ITEM_TABLE_TAKE_OFFSET: EQU item_take_ptr - items 3424 | ITEM_TABLE_DROP_OFFSET: EQU item_drop_ptr - items 3425 | ITEM_TABLE_EXAMINE_OFFSET: EQU item_examine_ptr - items 3426 | ITEM_TABLE_COLLECTIBLE_OFFSET: EQU item_collectible - items 3427 | ITEM_TABLE_STATE_OFFSET: EQU item_state - items 3428 | ITEM_TABLE_LOCATION_OFFSET: EQU item_location - items 3429 | ITEM_TABLE_CUSTOM_ACTION_OFFSET: EQU item_custom_action - items 3430 | 3431 | ; description of the mirror changes when it is dropped 3432 | ; so we have labels here to update the strings that 3433 | ; are pointed to. 3434 | item_first: 3435 | DEFW item_1_name ; mirror 3436 | item_first_desc: 3437 | DEFW item_1_desc 3438 | item_first_edesc: 3439 | DEFW item_1_long 3440 | DEFB 0,0 ; No take function 3441 | DEFW drop_mirror_fn ; custom drop function 3442 | DEFB 0,0 ; no custom examine function 3443 | DEFB MIRROR_OK ; item state 3444 | DEFB 1 ; this item can be picked up 3445 | DEFB 0x01 ; middle-floor 3446 | DEFB 0,0 ; no custom actions 3447 | 3448 | 3449 | ; description of the torch changes when it is lit/unlit 3450 | ; so we have labels here to update the strings that 3451 | ; are pointed to. 3452 | DEFW item_3_name ; torch 3453 | torch_item_desc: 3454 | DEFW item_3_desc_off 3455 | torch_item_long: 3456 | DEFW item_3_long_off 3457 | DEFB 0,0 ; No take function 3458 | DEFB 0,0 ; No drop function 3459 | DEFB 0,0 ; No examine function 3460 | DEFB TORCH_STATE_UNLIT ; item state 3461 | DEFB 1 ; this item can be picked up 3462 | DEFB 0x00 ; top-floor 3463 | DEFW torch_actions ; custom actions 3464 | 3465 | DEFW item_5_name ; dog 3466 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3467 | DEFW item_5_long 3468 | DEFB 0,0 ; No take function 3469 | DEFB 0,0 ; No drop function 3470 | DEFB 0,0 ; No examine function 3471 | DEFB 0 ; item state 3472 | DEFB 0 ; this item cannot be picked up 3473 | DEFB 0x01 ; middle-floor 3474 | DEFW dog_actions ; no custom actions 3475 | 3476 | DEFW item_6_name ; painting 3477 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3478 | DEFW item_6_long 3479 | DEFB 0,0 ; No take function 3480 | DEFB 0,0 ; No drop function 3481 | DEFB 0,0 ; No examine function 3482 | DEFB 0 ; item state 3483 | DEFB 0 ; this item cannot be picked up 3484 | DEFB 0x01 ; middle-floor 3485 | DEFB 0,0 ; no custom actions 3486 | 3487 | DEFW item_7_name ; rug 3488 | DEFW item_7_desc 3489 | DEFW item_7_long 3490 | DEFW take_rug_fn ; Custom take-function 3491 | DEFB 0,0 ; No drop function 3492 | DEFW examine_rug_fn ; custom examine function 3493 | DEFB 0 ; item state 3494 | DEFB 1 ; this item can be picked up 3495 | DEFB 0x02 ; ground-floor 3496 | DEFW rug_actions ; custom actions 3497 | 3498 | DEFW item_8_name ; book 3499 | DEFW item_8_desc 3500 | DEFW item_8_long 3501 | DEFB 0,0 ; No take function 3502 | DEFB 0,0 ; No drop function 3503 | DEFB 0,0 ; No examine function 3504 | DEFB 0 ; item state 3505 | DEFB 1 ; this item can be picked up 3506 | DEFB 0x01 ; middle-floor 3507 | DEFW book_actions ; custom actions 3508 | 3509 | DEFW item_9_name ; desk 3510 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3511 | DEFW item_9_long 3512 | DEFB 0,0 ; No take function 3513 | DEFB 0,0 ; No drop function 3514 | DEFW examine_desk_fn ; custom examine function 3515 | DEFB 0 ; item state 3516 | DEFB 0 ; this item cannot be picked up 3517 | DEFB 0x01 ; middle-floor 3518 | DEFB 0,0 ; no custom actions 3519 | 3520 | DEFW item_10_name ; telephone 3521 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3522 | DEFW item_10_long 3523 | DEFB 0,0 ; No take function 3524 | DEFB 0,0 ; No drop function 3525 | DEFB 0,0 ; no custom examine function 3526 | DEFB 0 ; item state 3527 | DEFB 0 ; this item cannot be picked up 3528 | DEFB 0x01 ; middle-floor 3529 | DEFB 0,0 ; no custom actions 3530 | 3531 | ; description of the trapdoor changes when it is opened/ 3532 | ; closed. So we have labels here to update the strings 3533 | ; that are pointed to. 3534 | DEFW item_11_name ; trapdoor 3535 | trapdoor_desc: 3536 | DEFW item_11_closed 3537 | trapdoor_edesc: 3538 | DEFW item_11_desc_closed 3539 | DEFB 0,0 ; No take function 3540 | DEFB 0,0 ; No drop function 3541 | DEFB 0,0 ; no custom examine function 3542 | DEFB TRAPDOOR_STATE_INVISIBLE ; item state 3543 | DEFB 0 ; this item cannot be picked up 3544 | DEFB ITEM_INVISIBLE ; hidden until the rug is moved 3545 | DEFW trapdoor_actions ; custom actions 3546 | 3547 | DEFW item_13_name ; meteor 3548 | DEFW item_13_desc 3549 | DEFW item_13_long 3550 | DEFW take_meteor_fn ; Custom take function 3551 | DEFB 0,0 ; No drop function 3552 | DEFB 0,0 ; no custom examine function 3553 | DEFB 0 ; item state 3554 | DEFB 0 ; this item cannot be picked up 3555 | DEFB ITEM_INVISIBLE ; hidden until the desk is examined 3556 | DEFB 0,0 ; no custom actions 3557 | 3558 | DEFW item_14_name ; room 3559 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3560 | DEFB 0,0 ; No extended description 3561 | DEFB 0,0 ; Custom take function 3562 | DEFB 0,0 ; No drop function 3563 | DEFW examine_room_fn ; EXAMINE ROOM -> custom function 3564 | DEFB 0 ; item state 3565 | DEFB 0 ; this item cannot be picked up 3566 | DEFB ITEM_INVISIBLE ; hidden. 3567 | DEFB 0,0 ; no custom actions 3568 | 3569 | DEFW item_15_name ; grue 3570 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3571 | DEFB 0,0 ; No extended description 3572 | DEFB 0,0 ; Custom take function 3573 | DEFB 0,0 ; No drop function 3574 | DEFB 0,0 ; No custom examine function 3575 | DEFB 0 ; item state 3576 | DEFB 0 ; this item cannot be picked up 3577 | DEFB ITEM_INVISIBLE ; hidden. 3578 | DEFB 0,0 ; no custom actions 3579 | 3580 | DEFW item_16_name ; dog-basket 3581 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3582 | DEFW item_16_long 3583 | DEFB 0,0 ; No take function 3584 | DEFB 0,0 ; No drop function 3585 | DEFB 0,0 ; No custom examine function. 3586 | DEFB 0 ; item state 3587 | DEFB 0 ; this item cannot be picked up 3588 | DEFB 0x01 ; middle-floor 3589 | DEFB 0,0 ; no custom actions 3590 | 3591 | DEFW item_17_name ; coat-rack 3592 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3593 | DEFW item_17_long 3594 | DEFB 0,0 ; No take function 3595 | DEFB 0,0 ; No drop function 3596 | DEFB 0,0 ; No custom examine function. 3597 | DEFB 0 ; item state 3598 | DEFB 0 ; this item cannot be picked up 3599 | DEFB 0x02 ; ground-floor 3600 | DEFB 0,0 ; no custom actions 3601 | 3602 | DEFW item_18_name ; chair 3603 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3604 | DEFW item_18_long 3605 | DEFB 0,0 ; No take function 3606 | DEFB 0,0 ; No drop function 3607 | DEFB 0,0 ; No custom examine function. 3608 | DEFB 0 ; item state 3609 | DEFB 0 ; this item cannot be picked up 3610 | DEFB 0x01 ; middle-floor 3611 | DEFB 0,0 ; no custom actions 3612 | 3613 | DEFW item_19_name ; chairs 3614 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3615 | DEFW item_19_long 3616 | DEFB 0,0 ; No take function 3617 | DEFB 0,0 ; No drop function 3618 | DEFB 0,0 ; No custom examine function. 3619 | DEFB 0 ; item state 3620 | DEFB 0 ; this item cannot be picked up 3621 | DEFB 0x01 ; middle-floor 3622 | DEFB 0,0 ; no custom actions 3623 | 3624 | DEFW item_20_name ; boots 3625 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3626 | DEFW item_20_long 3627 | DEFB 0,0 ; No take function 3628 | DEFB 0,0 ; No drop function 3629 | DEFB 0,0 ; No custom examine function. 3630 | DEFB 0 ; item state 3631 | DEFB 0 ; this item cannot be picked up 3632 | DEFB 0x02 ; ground-floor 3633 | DEFB 0,0 ; no custom actions 3634 | 3635 | DEFW item_21_name ; junk 3636 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3637 | DEFW item_21_long 3638 | DEFB 0,0 ; No take function 3639 | DEFB 0,0 ; No drop function 3640 | DEFB 0,0 ; No custom examine function. 3641 | DEFB 0 ; item state 3642 | DEFB 0 ; this item cannot be picked up 3643 | DEFB 0x03 ; basement 3644 | DEFB 0,0 ; no custom actions 3645 | 3646 | DEFW item_22_name ; machinery 3647 | DEFB 0,0 ; NO DESCRIPTION - hidden item 3648 | DEFW item_22_long 3649 | DEFB 0,0 ; No take function 3650 | DEFB 0,0 ; No drop function 3651 | DEFB 0,0 ; No custom examine function. 3652 | DEFB 0 ; item state 3653 | DEFB 0 ; this item cannot be picked up 3654 | DEFB 0x03 ; basement 3655 | DEFB 0,0 ; no custom actions 3656 | 3657 | item_last: 3658 | 3659 | ; length of an entry in the item table. 3660 | ITEM_ENTRY_LENGTH: EQU item_first - items 3661 | ITEM_COUNT: EQU ( item_last - items ) / ITEM_ENTRY_LENGTH 3662 | ; }} 3663 | 3664 | 3665 | 3666 | 3667 | ;******************************************************************** 3668 | ; Buffer Area 3669 | ;******************************************************************** 3670 | ; {{ 3671 | 3672 | ; 3673 | ; This is the buffer which is used for reading a line of text from the 3674 | ; CP/M BIOS. 3675 | ; 3676 | ; The way this works is you pass the address of a region of memory, the 3677 | ; first byte is the length of the buffer. 3678 | ; 3679 | ; On return the second byte of the buffer will be populated by the 3680 | ; amount of text which was read, then the input itself: 3681 | ; 3682 | ; http://www.gaby.de/cpm/manuals/archive/cpm22htm/ch5.htm#Function_10 3683 | ; 3684 | INPUT_BUFFER: 3685 | db MAX_INPUT_LENGTH ; size of our input-buffer 3686 | db 0x00 ; Count of characters returned. 3687 | 3688 | 3689 | 3690 | ; 3691 | ; A temporary buffer, available to all handlers. 3692 | ; 3693 | ; This is placed after the input-buffer above, and is defined as being 50 3694 | ; bytes long. It is used for finding the argument to a handler. 3695 | ; 3696 | ; For example: 3697 | ; 3698 | ; CALL RYDER 3699 | ; 3700 | ; The CALL handler will be invoked, and the object of the call, "RYDER" will 3701 | ; be copied into this buffer for table-lookup. 3702 | ; 3703 | TMP_BUFFER: EQU INPUT_BUFFER + 255 3704 | TMP_BUFFER_LEN: EQU 50 3705 | 3706 | ; }} 3707 | 3708 | ; 3709 | ; Our source code ends here. 3710 | ; 3711 | ; We use this label to calculate how many bytes to decrypt when we 3712 | ; launch - if that support is enabled. 3713 | ; 3714 | end_of_source: 3715 | 3716 | END ENTRYPOINT 3717 | -------------------------------------------------------------------------------- /prn.z80: -------------------------------------------------------------------------------- 1 | ; --------------- 2 | ; 4x8 font driver, (c) 2007 Andrew Owen 3 | ; --------------- 4 | 5 | ; org 50000 ; In the original this was fixed. 6 | 7 | ; -------------------------------- 8 | ; CREATE CHANNEL AND ATTACH STREAM 9 | ; -------------------------------- 10 | ; 11 | ; Based on code by Ian Beardsmore from Your Spectrum issue 7, September 1984. 12 | 13 | c_chan: ld hl,($5c53) ; a channel must be created below basic 14 | ; so look at the system variable PROG 15 | dec hl ; move hl down one address 16 | 17 | ld bc,$0005 ; the new channel takes 5 bytes 18 | call $1655 ; call the MAKE_ROOM routine 19 | inc hl ; move HL up one address 20 | 21 | ld bc,chan_4 ; could write the bytes directly but 22 | ; then code would be non-relocatable 23 | 24 | ld (hl),c ; low byte of the output routine 25 | inc hl ; move HL up one address 26 | push hl ; save this address for later 27 | 28 | ld (hl),b ; high byte of the output routine 29 | inc hl ; move HL up one address 30 | 31 | ld bc,$15c4 ; address of input routine 32 | 33 | ld (hl),c ; low byte of the input routine 34 | inc hl ; move HL up one address 35 | 36 | ld (hl),b ; high byte of the input routine 37 | inc hl ; move HL up one address 38 | 39 | ld (hl),'P' ; channel type; 'K', 'S', 'R' or 'P' 40 | 41 | ; attach stream 42 | 43 | pop hl ; the first address plus one of the 44 | ; extra space stored earlier 45 | ld de,($5c4f) ; store the contents of CHANS in DE 46 | and a ; clear the carry flag before 47 | ; calculation 48 | sbc hl,de ; the difference between the start of 49 | ; the channels area and the start of the 50 | ; extra space becomes the offset, stored 51 | ; in HL 52 | ex de,hl ; store the offset in DE 53 | 54 | ld hl,$5c10 ; store the contents of STRMS in HL 55 | ld a,$04 ; stream number 4 56 | add a,$03 ; take account of streams -3 to -1 57 | add a,a ; each of the seven default streams has 58 | ; two bytes of offset data 59 | ; the total number of bytes occupied, 60 | ; held in a, forms the offset for the 61 | ; new stream 62 | ld b,$00 ; set b to hold $00 63 | ld c,a ; set the low byte of the offset 64 | add hl,bc ; the offset is added to the base 65 | ; address to give the correct location 66 | ; in the streams table to store the 67 | ; offset 68 | ld (hl),e ; the low byte of the offset 69 | inc hl ; move HL up one address 70 | ld (hl),d ; the high byte of the offset 71 | ret ; all done 72 | 73 | ; ----------------- 74 | ; CHANNEL #4 OUTPUT 75 | ; ----------------- 76 | ; 77 | ; Based on code by Tony Samuels from Your Spectrum issue 13, April 1985. 78 | ; A channel wrapper for the 64-column display driver. 79 | 80 | chan_4: ld b,a ; save character 81 | ld a,(atflg) ; value of AT flag 82 | and a ; test against zero 83 | jr nz,getrow ; jump if not 84 | ld a,b ; restore character 85 | 86 | atchk: cp $16 ; test for AT 87 | jr nz,crchk ; if not test for CR 88 | ld a,$ff ; set the AT flag 89 | ld (atflg),a ; next character will be row 90 | ret ; return 91 | 92 | getrow: cp $fe ; test AT flag 93 | jr z,getcol ; jump if setting col 94 | ld a,b ; restore character 95 | cp $18 ; greater than 23? 96 | jr nc,err_b ; error if so 97 | 98 | ld (row),a ; store it in row 99 | ld hl,atflg ; AT flag 100 | dec (hl) ; indicates next character is col 101 | ret ; return 102 | 103 | getcol: ld a,b ; restore character 104 | cp $40 ; greater than 63? 105 | jr nc,err_b ; error if so 106 | ld (col),a ; store it in col 107 | xor a ; set a to zero 108 | ld (atflg),a ; store in AT flag 109 | ret ; return 110 | 111 | err_b: xor a ; set a to zero 112 | ld (atflg),a ; clear AT flag 113 | rst 08h ; 114 | defb $0a ; 115 | 116 | crchk: cp $0d ; check for return 117 | jr z,do_cr ; to carriage return if so 118 | call pr_64 ; print it 119 | 120 | ld hl,col ; increment 121 | inc (hl) ; the column 122 | ld a,(hl) ; 123 | 124 | cp $40 ; column 64? 125 | ret nz ; 126 | 127 | do_cr: xor a ; set A to zero 128 | ld (col),a ; reset column 129 | ld a,(row) ; get the row 130 | inc a ; increment it 131 | cp $18 ; row 24? 132 | jr z,wrap ; 133 | 134 | zend: ld (row),a ; write it back 135 | ret 136 | 137 | wrap: xor a ; 138 | jr zend ; 139 | 140 | ; ------------------------ 141 | ; 64 COLUMN DISPLAY DRIVER 142 | ; ------------------------ 143 | 144 | pr_64: rra ; divide by two with remainder in carry flag 145 | 146 | ld h,$00 ; clear H 147 | ld l,a ; CHAR to low byte of HL 148 | 149 | ex af,af' ; save the carry flag 150 | 151 | add hl,hl ; multiply 152 | add hl,hl ; by 153 | add hl,hl ; eight 154 | ld de,font-$80 ; offset to FONT 155 | add hl,de ; HL holds address of first byte of 156 | ; character map in FONT 157 | push hl ; save font address 158 | 159 | ; convert the row to the base screen address 160 | 161 | ld a,(row) ; get the row 162 | ld b,a ; save it 163 | and $18 ; mask off bit 3-4 164 | ld d,a ; store high byte of offset in D 165 | ld a,b ; retrieve it 166 | and $07 ; mask off bit 0-2 167 | rlca ; shift 168 | rlca ; five 169 | rlca ; bits 170 | rlca ; to the 171 | rlca ; left 172 | ld e,a ; store low byte of offset in E 173 | 174 | ; add the column 175 | 176 | ld a,(col) ; get the column 177 | rra ; divide by two with remainder in carry flag 178 | push af ; store the carry flag 179 | 180 | ld h,$40 ; base location 181 | ld l,a ; plus column offset 182 | 183 | add hl,de ; add the offset 184 | 185 | ex de,hl ; put the result back in DE 186 | 187 | ; HL now points to the location of the first byte of char data in FONT_1 188 | ; DE points to the first screen byte in SCREEN_1 189 | ; C holds the offset to the routine 190 | 191 | pop af ; restore column carry flag 192 | pop hl ; restore the font address 193 | 194 | jr nc,odd_col ; jump if odd column 195 | 196 | even_col: 197 | ex af,af' ; restore char position carry flag 198 | jr c,l_on_l ; left char on left col 199 | jr r_on_l ; right char on left col 200 | 201 | odd_col: 202 | ex af,af' ; restore char position carry flag 203 | jr nc,r_on_r ; right char on right col 204 | jr l_on_r ; left char on right col 205 | 206 | ; ------------------------------- 207 | ; WRITE A CHARACTER TO THE SCREEN 208 | ; ------------------------------- 209 | ; 210 | ; There are four separate routines 211 | 212 | ; HL points to the first byte of a character in FONT 213 | ; DE points to the first byte of the screen address 214 | 215 | ; left nibble on left hand side 216 | 217 | l_on_l: ld c,$08 ; 8 bytes to write 218 | ll_lp: ld a,(de) ; read byte at destination 219 | and $f0 ; mask area used by new character 220 | ld b,a ; store in b 221 | ld a,(hl) ; get byte of font 222 | and $0f ; mask off unused half 223 | or b ; combine with background 224 | ld (de),a ; write it back 225 | inc d ; point to next screen location 226 | inc hl ; point to next font data 227 | dec c ; adjust counter 228 | jr nz,ll_lp ; loop 8 times 229 | ret ; done 230 | 231 | ; right nibble on right hand side 232 | 233 | r_on_r: ld c,$08 ; 8 bytes to write 234 | rr_lp: ld a,(de) ; read byte at destination 235 | and $0f ; mask area used by new character 236 | ld b,a ; store in b 237 | ld a,(hl) ; get byte of font 238 | and $f0 ; mask off unused half 239 | or b ; combine with background 240 | ld (de),a ; write it back 241 | inc d ; point to next screen location 242 | inc hl ; point to next font data 243 | dec c ; adjust counter 244 | jr nz,rr_lp ; loop 8 times 245 | ret ; done 246 | 247 | ; left nibble on right hand side 248 | 249 | l_on_r: ld c,$08 ; 8 bytes to write 250 | lr_lp: ld a,(de) ; read byte at destination 251 | and $0f ; mask area used by new character 252 | ld b,a ; store in b 253 | ld a,(hl) ; get byte of font 254 | rrca ; shift right 255 | rrca ; four bits 256 | rrca ; leaving 7-4 257 | rrca ; empty 258 | and $f0 ; 259 | or b ; combine with background 260 | ld (de),a ; write it back 261 | inc d ; point to next screen location 262 | inc hl ; point to next font data 263 | dec c ; adjust counter 264 | jr nz,lr_lp ; loop 8 times 265 | ret ; done 266 | 267 | ; right nibble on left hand side 268 | 269 | r_on_l: ld c,$08 ; 8 bytes to write 270 | rl_lp: ld a,(de) ; read byte at destination 271 | and $f0 ; mask area used by new character 272 | ld b,a ; store in b 273 | ld a,(hl) ; get byte of font 274 | rlca ; shift left 275 | rlca ; four bits 276 | rlca ; leaving 3-0 277 | rlca ; empty 278 | and $0f ; 279 | or b ; combine with background 280 | ld (de),a ; write it back 281 | inc d ; point to next screen location 282 | inc hl ; point to next font data 283 | dec c ; adjust counter 284 | jr nz,rl_lp ; loop 8 times 285 | ret ; done 286 | 287 | ; -------------- 288 | ; TEXT VARIABLES 289 | ; -------------- 290 | ; 291 | ; Used by the 64 column driver 292 | 293 | atflg: defb $00 ; AT flag 294 | row: defb $00 ; row 295 | col: defb $00 ; col 296 | 297 | ; ------------------- 298 | ; half width 4x8 font 299 | ; ------------------- 300 | ; 301 | ; 384 bytes 302 | 303 | font: 304 | defb $00,$02,$02,$02,$02,$00,$02,$00,$00,$52,$57,$02,$02,$07,$02,$00; 305 | defb $00,$25,$71,$62,$32,$74,$25,$00,$00,$22,$42,$30,$50,$50,$30,$00; 306 | defb $00,$14,$22,$41,$41,$41,$22,$14,$00,$20,$70,$22,$57,$02,$00,$00; 307 | defb $00,$00,$00,$00,$07,$00,$20,$20,$00,$01,$01,$02,$02,$04,$14,$00; 308 | defb $00,$22,$56,$52,$52,$52,$27,$00,$00,$27,$51,$12,$21,$45,$72,$00; 309 | defb $00,$57,$54,$56,$71,$15,$12,$00,$00,$17,$21,$61,$52,$52,$22,$00; 310 | defb $00,$22,$55,$25,$53,$52,$24,$00,$00,$00,$00,$22,$00,$00,$22,$02; 311 | defb $00,$00,$10,$27,$40,$27,$10,$00,$00,$02,$45,$21,$12,$20,$42,$00; 312 | defb $00,$23,$55,$75,$77,$45,$35,$00,$00,$63,$54,$64,$54,$54,$63,$00; 313 | defb $00,$67,$54,$56,$54,$54,$67,$00,$00,$73,$44,$64,$45,$45,$43,$00; 314 | defb $00,$57,$52,$72,$52,$52,$57,$00,$00,$35,$15,$16,$55,$55,$25,$00; 315 | defb $00,$45,$47,$45,$45,$45,$75,$00,$00,$62,$55,$55,$55,$55,$52,$00; 316 | defb $00,$62,$55,$55,$65,$45,$43,$00,$00,$63,$54,$52,$61,$55,$52,$00; 317 | defb $00,$75,$25,$25,$25,$25,$22,$00,$00,$55,$55,$55,$55,$27,$25,$00; 318 | defb $00,$55,$55,$25,$22,$52,$52,$00,$00,$73,$12,$22,$22,$42,$72,$03; 319 | defb $00,$46,$42,$22,$22,$12,$12,$06,$00,$20,$50,$00,$00,$00,$00,$0F; 320 | defb $00,$20,$10,$03,$05,$05,$03,$00,$00,$40,$40,$63,$54,$54,$63,$00; 321 | defb $00,$10,$10,$32,$55,$56,$33,$00,$00,$10,$20,$73,$25,$25,$43,$06; 322 | defb $00,$42,$40,$66,$52,$52,$57,$00,$00,$14,$04,$35,$16,$15,$55,$20; 323 | defb $00,$60,$20,$25,$27,$25,$75,$00,$00,$00,$00,$62,$55,$55,$52,$00; 324 | defb $00,$00,$00,$63,$55,$55,$63,$41,$00,$00,$00,$53,$66,$43,$46,$00; 325 | defb $00,$00,$20,$75,$25,$25,$12,$00,$00,$00,$00,$55,$55,$27,$25,$00; 326 | defb $00,$00,$00,$55,$25,$25,$53,$06,$00,$01,$02,$72,$34,$62,$72,$01; 327 | defb $00,$24,$22,$22,$21,$22,$22,$04,$00,$56,$A9,$06,$04,$06,$09,$06; 328 | -------------------------------------------------------------------------------- /version.z80: -------------------------------------------------------------------------------- 1 | DB "unreleased-git" 2 | --------------------------------------------------------------------------------