├── .gitattributes ├── .gitignore ├── LICENSE ├── Makefile ├── Makefile-dsk.mk ├── Makefile-po.mk ├── README.md ├── apple2 ├── template.dsk └── template.po └── src ├── apple2.loader ├── loader.cfg └── loader.s ├── apple2 ├── audio.inc ├── defs.inc ├── game.inc ├── input.inc ├── level.inc ├── logo.hgr ├── logo.inc ├── mminer.asm ├── mminer.cfg ├── roaudio.inc ├── rofont.inc ├── rolevels.inc ├── rosprites.inc ├── rosystem.inc ├── rotext.inc ├── rotiles.inc ├── screen.inc ├── sprite.inc ├── text.inc ├── tiles.inc ├── ui.inc ├── variables.inc └── willy.inc └── mmm ├── CMakeLists.txt ├── README.md └── src ├── 6502.c ├── 6502.h ├── mminer.c ├── mminer.h └── mmm.c /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | *.code-workspace 3 | /obj/ 4 | /Makefile.options 5 | /mminer.dsk 6 | /mminer.apple2* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ### Generic Makefile for cc65 projects - full version with abstract options ### 3 | ### V1.3.0(w) 2010 - 2013 Oliver Schmidt & Patryk "Silver Dream !" Łogiewa ### 4 | ############################################################################### 5 | 6 | ############################################################################### 7 | ### In order to override defaults - values can be assigned to the variables ### 8 | ############################################################################### 9 | 10 | # Space or comma separated list of cc65 supported target platforms to build for. 11 | # Default: c64 (lowercase!) 12 | TARGETS := apple2 13 | 14 | # Name of the final, single-file executable. 15 | # Default: name of the current dir with target name appended 16 | PROGRAM := mminer 17 | 18 | # Path(s) to additional libraries required for linking the program 19 | # Use only if you don't want to place copies of the libraries in SRCDIR 20 | # Default: none 21 | LIBS := 22 | 23 | # Custom linker configuration file 24 | # Use only if you don't want to place it in SRCDIR 25 | # Default: none 26 | CONFIG := 27 | 28 | # Additional C compiler flags and options. 29 | # Default: none 30 | CFLAGS = 31 | 32 | # Additional assembler flags and options. 33 | # Default: none 34 | ASFLAGS = 35 | 36 | # Additional linker flags and options. 37 | # Default: none 38 | LDFLAGS = 39 | ifeq ($(PROGRAM),) 40 | NAME := $(notdir $(CURDIR)) 41 | else 42 | NAME := $(PROGRAM) 43 | endif 44 | $(NAME).apple2: LDFLAGS += 45 | 46 | # Path to the directory containing C and ASM sources. 47 | # Default: src 48 | SRCDIR := 49 | 50 | # Path to the directory where object files are to be stored (inside respective target subdirectories). 51 | # Default: obj 52 | OBJDIR := 53 | 54 | # Command used to run the emulator. 55 | # Default: depending on target platform. For default (c64) target: x64 -kernal kernal -VICIIdsize -autoload 56 | EMUCMD := 57 | 58 | # On Windows machines VICE emulators may not be available in the PATH by default. 59 | # In such case, please set the variable below to point to directory containing 60 | # VICE emulators. 61 | #VICE_HOME := "C:\Program Files\WinVICE-2.2-x86\" 62 | VICE_HOME := 63 | #APPLEWIN_HOME := c:\users\swessels\apps\applewin 64 | 65 | # Optional commands used before starting the emulation process, and after finishing it. 66 | # Default: none 67 | # Examples 68 | #PREEMUCMD := osascript -e "tell application \"System Events\" to set isRunning to (name of processes) contains \"X11.bin\"" -e "if isRunning is true then tell application \"X11\" to activate" 69 | #PREEMUCMD := osascript -e "tell application \"X11\" to activate" 70 | #POSTEMUCMD := osascript -e "tell application \"System Events\" to tell process \"X11\" to set visible to false" 71 | #POSTEMUCMD := osascript -e "tell application \"Terminal\" to activate" 72 | #PREEMUCMD := sed "s/^al \([[0-9A-F]\+\)\ \./\1 /g" $(NAME).apple2.lbl > $(APPLEWIN_HOME)\A2_USER1.SYM 73 | POSTEMUCMD := 74 | 75 | 76 | # Options state file name. You should not need to change this, but for those 77 | # rare cases when you feel you really need to name it differently - here you are 78 | STATEFILE := Makefile.options 79 | 80 | ################################################################################### 81 | #### DO NOT EDIT BELOW THIS LINE, UNLESS YOU REALLY KNOW WHAT YOU ARE DOING! #### 82 | ################################################################################### 83 | 84 | ################################################################################### 85 | ### Mapping abstract options to the actual compiler, assembler and linker flags ### 86 | ### Predefined compiler, assembler and linker flags, used with abstract options ### 87 | ### valid for 2.14.x. Consult the documentation of your cc65 version before use ### 88 | ################################################################################### 89 | 90 | # Compiler flags used to tell the compiler to optimise for SPEED 91 | define _optspeed_ 92 | CFLAGS += -Oris 93 | endef 94 | 95 | # Compiler flags used to tell the compiler to optimise for SIZE 96 | define _optsize_ 97 | CFLAGS += -Or 98 | endef 99 | 100 | # Compiler and assembler flags for generating listings 101 | define _listing_ 102 | CFLAGS += --listing $$(@:.o=.lst) 103 | ASFLAGS += --listing $$(@:.o=.lst) 104 | REMOVES += $(addsuffix .lst,$(basename $(OBJECTS))) 105 | endef 106 | 107 | # Linker flags for generating map file 108 | define _mapfile_ 109 | LDFLAGS += --mapfile $$@.map 110 | REMOVES += $(PROGRAM).map 111 | endef 112 | 113 | # Linker flags for generating VICE label file 114 | define _labelfile_ 115 | ASFLAGS += --debug-info 116 | LDFLAGS += -Ln $$@.lbl 117 | REMOVES += $(PROGRAM).lbl 118 | endef 119 | 120 | # Linker flags for generating a debug file 121 | define _debugfile_ 122 | LDFLAGS += -Wl --dbgfile,$$@.dbg 123 | REMOVES += $(PROGRAM).dbg 124 | endef 125 | 126 | ############################################################################### 127 | ### Defaults to be used if nothing defined in the editable sections above ### 128 | ############################################################################### 129 | 130 | # Presume the C64 target like the cl65 compile & link utility does. 131 | # Set TARGETS to override. 132 | ifeq ($(TARGETS),) 133 | TARGETS := c64 134 | endif 135 | 136 | # Presume we're in a project directory so name the program like the current 137 | # directory. Set PROGRAM to override. 138 | ifeq ($(PROGRAM),) 139 | PROGRAM := $(notdir $(CURDIR)) 140 | endif 141 | 142 | # Presume the C and asm source files to be located in the subdirectory 'src'. 143 | # Set SRCDIR to override. 144 | ifeq ($(SRCDIR),) 145 | SRCDIR := src 146 | endif 147 | 148 | # Presume the object and dependency files to be located in the subdirectory 149 | # 'obj' (which will be created). Set OBJDIR to override. 150 | ifeq ($(OBJDIR),) 151 | OBJDIR := obj 152 | endif 153 | TARGETOBJDIR := $(OBJDIR)/$(TARGETS) 154 | 155 | # Default emulator commands and options for particular targets. 156 | # Set EMUCMD to override. 157 | c64_EMUCMD := $(VICE_HOME)x64 -kernal kernal -VICIIdsize -autoload 158 | c128_EMUCMD := $(VICE_HOME)x128 -kernal kernal -VICIIdsize -autoload 159 | vic20_EMUCMD := $(VICE_HOME)xvic -kernal kernal -VICdsize -autoload 160 | pet_EMUCMD := $(VICE_HOME)xpet -Crtcdsize -autoload 161 | plus4_EMUCMD := $(VICE_HOME)xplus4 -TEDdsize -autoload 162 | # So far there is no x16 emulator in VICE (why??) so we have to use xplus4 with -memsize option 163 | c16_EMUCMD := $(VICE_HOME)xplus4 -ramsize 16 -TEDdsize -autoload 164 | cbm510_EMUCMD := $(VICE_HOME)xcbm2 -model 510 -VICIIdsize -autoload 165 | cbm610_EMUCMD := $(VICE_HOME)xcbm2 -model 610 -Crtcdsize -autoload 166 | atari_EMUCMD := atari800 -windowed -xl -pal -nopatchall -run 167 | cx16_EMUCMD := x16emu -run -prg 168 | apple2_EMUCMD := $(APPLEWIN_HOME)\AppleWin.exe -d1 $(CURDIR)\$(NAME).dsk 169 | 170 | ifeq ($(EMUCMD),) 171 | EMUCMD = $($(CC65TARGET)_EMUCMD) 172 | endif 173 | 174 | ############################################################################### 175 | ### The magic begins ### 176 | ############################################################################### 177 | 178 | # The "Native Win32" GNU Make contains quite some workarounds to get along with 179 | # cmd.exe as shell. However it does not provide means to determine that it does 180 | # actually activate those workarounds. Especially $(SHELL) does NOT contain the 181 | # value 'cmd.exe'. So the usual way to determine if cmd.exe is being used is to 182 | # execute the command 'echo' without any parameters. Only cmd.exe will return a 183 | # non-empty string - saying 'ECHO is on/off'. 184 | # 185 | # Many "Native Win32" programs accept '/' as directory delimiter just fine. How- 186 | # ever the internal commands of cmd.exe generally require '\' to be used. 187 | # 188 | # cmd.exe has an internal command 'mkdir' that doesn't understand nor require a 189 | # '-p' to create parent directories as needed. 190 | # 191 | # cmd.exe has an internal command 'del' that reports a syntax error if executed 192 | # without any file so make sure to call it only if there's an actual argument. 193 | ifeq ($(shell echo),) 194 | MKDIR = mkdir -p $1 195 | RMDIR = rmdir $1 196 | RMFILES = $(RM) $1 197 | else 198 | MKDIR = mkdir $(subst /,\,$1) 199 | RMDIR = rmdir $(subst /,\,$1) 200 | RMFILES = $(if $1,del /f $(subst /,\,$1)) 201 | endif 202 | COMMA := , 203 | SPACE := $(N/A) $(N/A) 204 | define NEWLINE 205 | 206 | 207 | endef 208 | # Note: Do not remove any of the two empty lines above ! 209 | 210 | TARGETLIST := $(subst $(COMMA),$(SPACE),$(TARGETS)) 211 | 212 | ifeq ($(words $(TARGETLIST)),1) 213 | 214 | # Set PROGRAM to something like 'myprog.c64'. 215 | override PROGRAM := $(PROGRAM).$(TARGETLIST) 216 | 217 | # Set SOURCES to something like 'src/foo.c src/bar.s'. 218 | # Use of assembler files with names ending differently than .s is deprecated! 219 | SOURCES := $(wildcard $(SRCDIR)/*.c) 220 | SOURCES += $(wildcard $(SRCDIR)/*.s) 221 | SOURCES += $(wildcard $(SRCDIR)/*.asm) 222 | SOURCES += $(wildcard $(SRCDIR)/*.a65) 223 | 224 | # Add to SOURCES something like 'src/c64/me.c src/c64/too.s'. 225 | # Use of assembler files with names ending differently than .s is deprecated! 226 | SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.c) 227 | SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.s) 228 | SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.asm) 229 | SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.a65) 230 | 231 | # Set OBJECTS to something like 'obj/c64/foo.o obj/c64/bar.o'. 232 | OBJECTS := $(addsuffix .o,$(basename $(addprefix $(TARGETOBJDIR)/,$(notdir $(SOURCES))))) 233 | 234 | # Set DEPENDS to something like 'obj/c64/foo.d obj/c64/bar.d'. 235 | DEPENDS := $(OBJECTS:.o=.d) 236 | 237 | # Add to LIBS something like 'src/foo.lib src/c64/bar.lib'. 238 | LIBS += $(wildcard $(SRCDIR)/*.lib) 239 | LIBS += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.lib) 240 | 241 | # Add to CONFIG something like 'src/c64/bar.cfg src/foo.cfg'. 242 | CONFIG += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.cfg) 243 | CONFIG += $(wildcard $(SRCDIR)/*.cfg) 244 | 245 | # Select CONFIG file to use. Target specific configs have higher priority. 246 | ifneq ($(word 2,$(CONFIG)),) 247 | CONFIG := $(firstword $(CONFIG)) 248 | $(info Using config file $(CONFIG) for linking) 249 | endif 250 | 251 | .SUFFIXES: 252 | .PHONY: all test clean zap love 253 | 254 | all: $(PROGRAM) 255 | 256 | -include $(DEPENDS) 257 | -include $(STATEFILE) 258 | 259 | # If OPTIONS are given on the command line then save them to STATEFILE 260 | # if (and only if) they have actually changed. But if OPTIONS are not 261 | # given on the command line then load them from STATEFILE. Have object 262 | # files depend on STATEFILE only if it actually exists. 263 | ifeq ($(origin OPTIONS),command line) 264 | ifneq ($(OPTIONS),$(_OPTIONS_)) 265 | ifeq ($(OPTIONS),) 266 | $(info Removing OPTIONS) 267 | $(shell $(RM) $(STATEFILE)) 268 | $(eval $(STATEFILE):) 269 | else 270 | $(info Saving OPTIONS=$(OPTIONS)) 271 | $(shell echo _OPTIONS_=$(OPTIONS) > $(STATEFILE)) 272 | endif 273 | $(eval $(OBJECTS): $(STATEFILE)) 274 | endif 275 | else 276 | ifeq ($(origin _OPTIONS_),file) 277 | $(info Using saved OPTIONS=$(_OPTIONS_)) 278 | OPTIONS = $(_OPTIONS_) 279 | $(eval $(OBJECTS): $(STATEFILE)) 280 | endif 281 | endif 282 | 283 | # Transform the abstract OPTIONS to the actual cc65 options. 284 | $(foreach o,$(subst $(COMMA),$(SPACE),$(OPTIONS)),$(eval $(_$o_))) 285 | 286 | # Strip potential variant suffix from the actual cc65 target. 287 | CC65TARGET := $(firstword $(subst .,$(SPACE),$(TARGETLIST))) 288 | 289 | # The remaining targets. 290 | $(TARGETOBJDIR): 291 | $(call MKDIR,$@) 292 | 293 | vpath %.c $(SRCDIR)/$(TARGETLIST) $(SRCDIR) 294 | 295 | $(TARGETOBJDIR)/%.o: %.c | $(TARGETOBJDIR) 296 | cl65 -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(CFLAGS) -o $@ $< 297 | 298 | vpath %.s $(SRCDIR)/$(TARGETLIST) $(SRCDIR) 299 | 300 | $(TARGETOBJDIR)/%.o: %.s | $(TARGETOBJDIR) 301 | cl65 -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $< 302 | 303 | vpath %.asm $(SRCDIR)/$(TARGETLIST) $(SRCDIR) 304 | 305 | $(TARGETOBJDIR)/%.o: %.asm | $(TARGETOBJDIR) 306 | cl65 -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $< 307 | 308 | vpath %.a65 $(SRCDIR)/$(TARGETLIST) $(SRCDIR) 309 | 310 | $(TARGETOBJDIR)/%.o: %.a65 | $(TARGETOBJDIR) 311 | cl65 -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $< 312 | 313 | $(PROGRAM): $(CONFIG) $(OBJECTS) $(LIBS) 314 | cl65 -t $(CC65TARGET) $(LDFLAGS) -o $@ $(patsubst %.cfg,-C %.cfg,$^) 315 | 316 | test: $(PROGRAM) 317 | $(PREEMUCMD) 318 | $(EMUCMD) 319 | $(POSTEMUCMD) 320 | 321 | clean: 322 | $(call RMFILES,$(OBJECTS)) 323 | $(call RMFILES,$(DEPENDS)) 324 | $(call RMFILES,$(REMOVES)) 325 | $(call RMFILES,$(PROGRAM)) 326 | 327 | else # $(words $(TARGETLIST)),1 328 | 329 | all test clean: 330 | $(foreach t,$(TARGETLIST),$(MAKE) TARGETS=$t $@$(NEWLINE)) 331 | 332 | endif # $(words $(TARGETLIST)),1 333 | 334 | OBJDIRLIST := $(wildcard $(OBJDIR)/*) 335 | 336 | zap: 337 | $(foreach o,$(OBJDIRLIST),-$(call RMFILES,$o/*.o $o/*.d $o/*.lst)$(NEWLINE)) 338 | $(foreach o,$(OBJDIRLIST),-$(call RMDIR,$o)$(NEWLINE)) 339 | -$(call RMDIR,$(OBJDIR)) 340 | -$(call RMFILES,$(basename $(PROGRAM)).* $(STATEFILE)) 341 | 342 | love: 343 | @echo "Not war, eh?" 344 | 345 | ################################################################### 346 | ### Place your additional targets in the additional Makefiles ### 347 | ### in the same directory - their names have to end with ".mk"! ### 348 | ################################################################### 349 | -include *.mk 350 | -------------------------------------------------------------------------------- /Makefile-dsk.mk: -------------------------------------------------------------------------------- 1 | DSK = $(NAME).dsk 2 | 3 | # For this one, see https://applecommander.github.io/ 4 | AC ?= ac.jar 5 | 6 | # Unix or Windows 7 | ifeq ($(shell echo),) 8 | CP = cp $1 9 | else 10 | CP = copy $(subst /,\,$1) 11 | endif 12 | 13 | REMOVES += $(DSK) 14 | 15 | .PHONY: dsk 16 | dsk: $(DSK) 17 | 18 | $(DSK): $(NAME).apple2 19 | $(call CP, apple2/template.dsk $@) 20 | java -jar $(AC) -p $@ $(NAME).system sys < $(NAME).apple2.loader 21 | java -jar $(AC) -as $@ $(NAME) bin < $(NAME).apple2 22 | -------------------------------------------------------------------------------- /Makefile-po.mk: -------------------------------------------------------------------------------- 1 | # This requires at least cadius v 1.3.0 (https://github.com/mach-kernel/cadius/releases; 2 | # Tested with cadius v 1.4.5) - support for file's type and auxtype via the filename needed. 3 | PO = $(NAME).po 4 | 5 | CA ?= cadius 6 | 7 | # Unix or Windows 8 | ifeq ($(shell echo),) 9 | CP = cp $1 10 | MV = mv 11 | RM = rm 12 | else 13 | CP = copy $(subst /,\,$1) 14 | MV = ren 15 | RM = del 16 | endif 17 | 18 | REMOVES += $(PO) 19 | 20 | .PHONY: po 21 | po: $(PO) 22 | 23 | $(NAME).system: 24 | cp $(NAME).apple2.loader $(NAME).system#FF2000 25 | 26 | $(PO): $(NAME).apple2 $(NAME).system 27 | $(call CP, apple2/template.po $@) 28 | $(MV) $(NAME).apple2 $(NAME)#064000 29 | $(CA) addfile $(NAME).po /mminer $(NAME).system#FF2000 30 | $(CA) addfile $(NAME).po /mminer $(NAME)#064000 31 | $(RM) $(NAME).system#FF2000 32 | $(RM) $(NAME)#064000 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Manic Miner 2 | Remake of the ZX Spectrum game for the Apple II. 3 | 4 | ## Updates: 5 | * 9 September 2024. 6 | Check out the Manic Miner Machine! It's an Apple II "emulator" that I built 7 | just to run the Apple II version of manic Miner on a modern computer, using 8 | SDL2. The speaker audio is terrible, but otherwise it works pretty great. 9 | No NTSC artifact colors. Windows version in RELEASES tab. 10 | [The Manic Miner Machine README](src/mmm/README.md) 11 | [The Manic Miner Machine Source](src/mmm/src) 12 | 13 | * 30 April 2021 14 | I Sped up rendering for a more even experience, especially in The Vat and 15 | The Warehouse. The Apple II vesrsion is marginally slower than the 16 | ZX Spectrum version (I think - tested with Speccy) but feels pretty good. 17 | To make room for the fully unrolled level rendering, I sacrificed the 18 | lower case letters in the font. I updated the table of timings, below. 19 | 20 | 1. INTRODUCTION 21 | 22 | This is a game I have always loved, from the moment I saw it in a computer shop 23 | back in the 80's. I once made an okay Commodore 64 version, in C, but this 24 | version is a rewrite from the ground up for the Apple II, in 6502 assembly 25 | language and I am very pleased with the outcome. 26 | 27 | This is my first color program for the Apple II. Another learning experience :) 28 | 29 | 2. CURRENT STATUS 30 | 31 | The game is complete. 32 | 33 | There's a pre-built Apple II disk of the project, mminer.dsk under the 34 | releases tab. This can be played directly in emulators. 35 | 36 | There's also a video of the game here: https://youtu.be/OGxj_g1ImhM 37 | 38 | 3. DIFFERENCES FROM THE ORIGINAL 39 | 40 | There are several differences that all have to do with Apple II hardware 41 | limitations. 42 | 43 | In the Apple II version, the title screen is not the high resolution picture 44 | with the animated piano keyboard. Instead it uses the ZX Spectrum loading 45 | screen, which has the words Manic and Miner bounce up and down. I do use the 46 | hires screen for loading though, so it is just the opposite way ;) 47 | 48 | The reason for not using the high resolution picture is due to a memory 49 | limitation. If I did use some of the ROM memory for RAM, I could probably fix 50 | this, but I didn't want to trash ProDOS and I didn't want to have to reboot the 51 | Apple II when the program is quit. 52 | 53 | The biggest differences have to do with the screen and keys. 54 | 55 | The ZX Spectrum has a 32 column, 8 pixel per column, color display (256 pixels). 56 | The Apple II can only display 20 7-pixel color columns (140 pixels). This means 57 | I cannot fit the level on-screen at any given time. The level does need to 58 | scroll as you play through it. 59 | 60 | I have settled on a 3 zone scrolling method. As Willy enters a zone, the screen 61 | will quickly scroll to the proper "camera position" for the zone Willy is in. 62 | However, if you press the C key, the scrolling method toggles to one where the 63 | game tries to keep Willy more or less in the middle of the screen - so as you 64 | move around the center of the room, the game scrolls the screen all the time. 65 | This method may be better suited to learning a room. 66 | 67 | The scrolling method I use scrolls the game in "game columns" which means 2 68 | bytes at a time. This is a rather coarse scroll. I did spend a lot of effort 69 | on making run-time duplicates off all instanced data that allowed me to do a 70 | 1-byte scroll. Sadly, this turned out to be even worse as the motion became 71 | very jerky a lot more frequently. 72 | 73 | For the curious - if you want to draw a green line on the Apple II, starting at 74 | column 0, for 7 pixels, you need to write the following 2 bytes (bit 75 | representation): %00101010 %01010101. However, if you want that green line 76 | starting at column 1, you need to write the bytes in the other order, i.e. 77 | %01010101 %00101010. To get the speed I need, I have all data to be rendered 78 | stored as instances that I can "blast" to the screen quickly. For a 1-byte 79 | scroll, the whole screen needs to be rendered with the image bytes reversed, so 80 | I needed more memory to make the reverse second set. And then that didn't pay 81 | off :( 82 | 83 | About the keys - The ZX Spectrum version requires keys to be held down to move. 84 | The Apple II doesn't have a concept of keys being held down. You only know when 85 | a key is pressed, not when it's released so you cannot tell what state a key is 86 | in. You can also only detect one key press at a time. 87 | 88 | In order to make the game play with the key limitation, I use the keys as 89 | toggles. If you press P, Willy will start and keep walking, as though you are 90 | keeping the key down. Pressing P again will stop Willy, as though you had let 91 | go of the P key. My Apple IIe does have auto-repeat so actually holding down 92 | the P key has an undesirable effect of stutter-walking. 93 | 94 | I also made the game remember keys so that you can anticipate a key-press (like 95 | you would steer early in Pac Man). This works very well but it can also cause 96 | grief. For example, if you press Jump while in a jump, but you land somewhere 97 | unexpected, you will still jump because of the stored anticipated jump. If, of 98 | course, you are quick enough, just pressing the key again before it activates 99 | would cancel the anticipation. 100 | 101 | All in all, the key and scroll system makes the game fun and playable, I think, 102 | as much as is possible given the limitations. 103 | 104 | 4. TECHNICAL DETAILS 105 | 106 | The game is written in 6502 assembly language using the ca65 assembler. The 107 | game uses memory from $0800 to $BE4B. 108 | 109 | Below is a CPU cycle profile of 1 game frame in stage 1 after a couple of 110 | seconds of being on the level. The door isn't visible so this renders only the 111 | one enemy and Willy, as well as the level tiles. gameDelay is an artificial 112 | delay based on how many tiles were rendered, which smooths out the gameplay, 113 | and thus song tempo across levels. If the music is turned off, audioPlayNote 114 | will also create an artificial delay to simulate the delay incurred by 115 | toggling the speaker. 116 | 117 | Cycle counts are not constant across all frames so all timings below are 118 | approximate. 119 | 120 | Hex | Dec | Frame % | Item 121 | --- | --- | --- | --- 122 | 1267F | 75391 | 100% | Total Frame 123 | 18 | 24 | 0% | inputGet 124 | E8 | 232 | 0% | willyMove 125 | 80 | 128 | 0% | gameAI 126 | 5EF3 | 24307 | 32% | screenClear 127 | 123 | 291 | 0% | tilesAnimateKeys 128 | 47 | 71 | 0% | tilesAnimateConveyor 129 | 6EB | 1771 | 2% | screenDrawSprites 130 | A07 | 2567 | 3% | screenDrawWilly (collision) 131 | 71D7 | 29143 | 39% | screenDrawLevel 132 | 8D1 | 2257 | 3% | screenDrawWilly (view) 133 | 3A | 58 | 0% | uiUpdate 134 | 3A | 58 | 0% | screenDrawSprite (door) 135 | 28 | 40 | 0% | screenSwap 136 | 1D1F | 7455 | 10% | audioPlayNote 137 | 1F76 | 8054 | 11% | gameDelay 138 | 139 | 140 | As can be seen, clearing the area where the world will be drawn takes almost 141 | 32% of the frame and drawing the level tiles takes about 39%, plus 3% to 142 | re-render willy, of the total frame. Drawing is almost 80% of the frame. 143 | 144 | The ZX Spectrum version had Willy under the level tiles, but I prefer Willy 145 | in front, so I re-render willy without collision detection once the level tiles 146 | have been drawn, so he is always in front (but before the door is drawn so he is 147 | behind the door). 148 | 149 | 5. KEYS 150 | 151 | After the intro music on the title screen, the game has a scrolling ticker that 152 | shows help and keys, but these are the keys: 153 | 154 | * Q or O - Move Willy left 155 | * W or P - Move Willy right 156 | * Space - Jump 157 | * B - Toggle Black and White / Color mode 158 | * C - Toggle scrolling mode 159 | * M - Turn the music on/off 160 | * S - Turn in-game audio on/off 161 | * ESC - Quit level or in UI, return to ProDOS 162 | 163 | The game also supports the cheat mode, as in the original. In game, enter the 164 | cheat code 6031769 and a boot will appear next to the lives at the bottom of the 165 | screen. Cheat mode is now enabled. Press the 6 key to start the cheat. Now 166 | use the keys 12345 to enter the binary value for the level (0 through 19). Key 167 | one sets bit 0, so it's reversed. 168 | 169 | Once the appropriate room has been selected, press 6 again to start playing that 170 | level. Note that the cheat cannot be turned off and also that the Game Win 171 | Sequence (once you beat the last level) is disabled if you cheated. To see 172 | that, you really have to beat all levels! 173 | 174 | Here's a "cheat sheet" for the binary to access a level. If you enter a room 175 | number greater than 20, the room is reset to room 0. 176 | 177 | Cheat Key (binary 0-19 reverse) | 12345 178 | --- | --- 179 | Central Cavern | 00000 180 | The Cold Room | 10000 181 | The Menagerie | 01000 182 | Abandoned Uranium Workings | 11000 183 | Eugenes Lair | 00100 184 | Processing Plant | 10100 185 | The Vat | 01100 186 | Miner Willy meets the Kong | 11100 187 | Wacky Amoebatrons | 00010 188 | The Endorian Forest | 10010 189 | Attack of the Mutant Telephones | 01010 190 | Return of the Alien Kong Beast | 11010 191 | Ore Refinery | 00110 192 | Skylab Landing Bay | 10110 193 | The Bank | 01110 194 | The Sixteenth Cavern | 11110 195 | The Warehouse | 00001 196 | Amoebatrons Revenge | 10001 197 | Solar Power Generator | 01001 198 | The Final Barrier | 11001 199 | 200 | 6. THE FILES 201 | 202 | I tried to thoroughly comment all the code. 203 | 204 | There are actually 2 programs in this. The 1st is the game, and it's in 205 | src/apple2. 206 | 207 | File | Desc 208 | --- | --- 209 | audio.inc | Routines to make the speaker beep 210 | defs.inc | Constants and definitions used throughout 211 | game.inc | The in-game logic, AI etc. The bulk of the "game" 212 | input.inc | User controls for game and editor 213 | level.inc | Decompress a level and place the keys 214 | logo.hgr | 8Kb splash screen in HGR format 215 | logo.inc | File that simply does an incbin on logo.hgr 216 | mminer.asm | Where the game starts, initial setup, etc. 217 | mminer.cfg | ca65 configuration file 218 | roaudio.inc | Frequency and timing data for music and SFX 219 | rofont.inc | A ZX Spectrum font 220 | rolevels.inc | Level layout, tile usage, sprite positions, etc. 221 | rosprites.inc | Sprite definitions 222 | rosystem.inc | Helper tables (multiplication, color masks, etc.) 223 | rotext.inc | All text used in the game 224 | rotiles.inc | Background tile definitions 225 | screen.inc | Code related to drawing tiles, sprites, etc. 226 | sprite.inc | Code for making instances of sprites, coloring them, etc. 227 | text.inc | In game text and print functions 228 | ui.inc | User facing screens (title, scroller) 229 | variables.inc | All variables (scores, instance buffers, positions, etc.) 230 | Willy.inc | All logic relating to the movement of the main character, Willy 231 | 232 | The second is the ProDOS loader that will auto-load the game. It's in the 233 | src/apple2.loader folder. It has these files (all provided to me by Oliver 234 | Schmidt) 235 | 236 | File | Desc 237 | --- | --- 238 | loader.cfg | ca65 configuration file 239 | loader.s | file to load and start the game 240 | 241 | 7. BUILDING THE GAME 242 | 243 | Making the game has a few steps. Use make and the Makefile on all OSs, that 244 | would be the easiest. 245 | 246 | Start by making the loader - this needs to be done once only. 247 | make TARGETS=apple2.loader 248 | 249 | Next, make the game with: 250 | make 251 | 252 | The next step is to make a bootable disk image. For this, you will need 3rd 253 | party software. I use AppleCommander. This software will put the loader and 254 | game into the disk image. You will need to install Java to use AppleCommander. 255 | 256 | The apple2/template.dsk is a "blank ProDOS floppy" that has the loader and the 257 | game placed on it by AppleCommander. 258 | 259 | To make the disc image, set an environment variable to point at apple commander 260 | (see notes) and then use the command: 261 | make dsk 262 | 263 | This will make a disc named mminer.dsk which can be loaded up in an emulator. 264 | 265 | If you want to edit the code and get into some iterative development/testing, 266 | you can edit the Makefile. Look for, and change, apple2_EMUCMD. It is 267 | currently configured to work with AppleWin and the path to AppleWin is expected 268 | to be in an environment variable called APPLEWIN_HOME. Edit variables as 269 | necessary. 270 | 271 | If you use AppleWin and you have sed installed, you can also uncomment the 272 | PREEMUCMD := sed... command which will copy the game symbols to the emulator for 273 | use. For that to really make sense, you should to do this make command once: 274 | make OPTIONS=mapfile,labelfile,listing,debugfile. That will make a file called 275 | Makefile.options that will be re-used, and will generate a label file with all 276 | the labels. 277 | 278 | Once done, you can build and play the game with the command: make dsk test 279 | 280 | NOTES: 281 | 1) Find AppleCommander here (I used Version 1.6.0): 282 | https://github.com/AppleCommander/AppleCommander/releases 283 | 2) Set the environment variable (or change the Makefile-dsk.md) to point at the 284 | apple commander jar file. Here's how it's done for different shell's: 285 | Powershell: 286 | $env:AC = "path to apple commander.jar" 287 | cmd.exe 288 | set AC="path to apple commander.jar" 289 | bash (Unix or MacOS terminal): 290 | export AC="path to apple commander.jar" 291 | 292 | 8. CREDITS 293 | 294 | * Matthew Smith and BUG-BYTE for creating and publishing the game in 295 | 1983. Matthew later re-released the game with minor tweaks under the 296 | Software Projects banner. This is not that version. 297 | * A special call-out to Oliver Schmidt who provided me with invaluable 298 | advice and support. 299 | * Bill Buckels for Bmb2HDR which I used to make the logo HGR from my 300 | GIMP exported BMP. 301 | * Everyone involved in the Apple II projects (AppleWin | AppleCommander). 302 | * Everyone involved in making the cc65 tools, it's very good. 303 | * Ian Brumby for showing me how you really unroll a draw loop. 304 | 305 | 9. CONTACT 306 | 307 | Feel free to contact me at swessels@email.com if you have thoughts or 308 | suggestions. 309 | 310 | Thank you 311 | Stefan Wessels 312 | 21 April 2020 - Initial Revision 313 | -------------------------------------------------------------------------------- /apple2/template.dsk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StewBC/mminer-apple2/4d05b761981a7367b1a9ca24ce9ae31126e20d8f/apple2/template.dsk -------------------------------------------------------------------------------- /apple2/template.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StewBC/mminer-apple2/4d05b761981a7367b1a9ca24ce9ae31126e20d8f/apple2/template.po -------------------------------------------------------------------------------- /src/apple2.loader/loader.cfg: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # # 3 | # LOADER.SYSTEM - an Apple][ ProDOS 8 loader for cc65 programs (Oliver Schmidt) # 4 | # # 5 | ################################################################################# 6 | 7 | MEMORY { 8 | LOADER: start = $2000, size = $0200, file = %O; 9 | } 10 | 11 | SEGMENTS { 12 | CODE: load = LOADER, type = ro; 13 | DATA: load = LOADER, type = rw; 14 | } 15 | -------------------------------------------------------------------------------- /src/apple2.loader/loader.s: -------------------------------------------------------------------------------- 1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 2 | ; ; 3 | ; LOADER.SYSTEM - an Apple][ ProDOS 8 loader for cc65 programs (Oliver Schmidt) ; 4 | ; ; 5 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 6 | 7 | A1L := $3C 8 | A1H := $3D 9 | PATHNAME := $0280 10 | MLI := $BF00 11 | TXTCLR := $C050 ; Display graphics 12 | TXTSET := $C051 ; Display text 13 | MIXCLR := $C052 ; Disable 4 lines of text 14 | LOWSCR := $C054 ; Page 1 15 | HISCR := $C055 ; Page 2 16 | HIRES := $C057 ; Hires graphics 17 | VERSION := $FBB3 18 | RDKEY := $FD0C 19 | PRBYTE := $FDDA 20 | COUT := $FDED 21 | 22 | QUIT_CALL = $65 23 | OPEN_CALL = $C8 24 | READ_CALL = $CA 25 | CLOSE_CALL = $CC 26 | 27 | FILE_NOT_FOUND_ERR = $46 28 | 29 | ; ------------------------------------------------------------------------ 30 | 31 | .data 32 | 33 | OPEN_PARAM: 34 | .byte $03 ;PARAM_COUNT 35 | .addr PATHNAME ;PATHNAME 36 | .addr $4000 - $0400 ;IO_BUFFER 37 | OPEN_REF: .byte $00 ;REF_NUM 38 | 39 | READ_PARAM: 40 | .byte $04 ;PARAM_COUNT 41 | READ_REF: .byte $00 ;REF_NUM 42 | .addr $4000 ;DATA_BUFFER 43 | .word $FFFF ;REQUEST_COUNT 44 | .word $0000 ;TRANS_COUNT 45 | 46 | CLOSE_PARAM: 47 | .byte $01 ;PARAM_COUNT 48 | CLOSE_REF: .byte $00 ;REF_NUM 49 | 50 | QUIT_PARAM: 51 | .byte $04 ;PARAM_COUNT 52 | .byte $00 ;QUIT_TYPE 53 | .word $0000 ;RESERVED 54 | .byte $00 ;RESERVED 55 | .word $0000 ;RESERVED 56 | 57 | LOADING: 58 | .byte $0D 59 | .asciiz "Loading " 60 | 61 | ELLIPSES: 62 | .byte " ...", $0D, $0D, $00 63 | 64 | FILE_NOT_FOUND: 65 | .asciiz "... File Not Found" 66 | 67 | ERROR_NUMBER: 68 | .asciiz "... Error $" 69 | 70 | PRESS_ANY_KEY: 71 | .asciiz " - Press Any Key " 72 | 73 | ; ------------------------------------------------------------------------ 74 | 75 | .code 76 | 77 | ; Reset stack 78 | ldx #$FF 79 | txs 80 | 81 | ; Remove ".SYSTEM" from pathname 82 | lda PATHNAME 83 | sec 84 | sbc #.strlen(".SYSTEM") 85 | sta PATHNAME 86 | 87 | ; Add trailing '\0' to pathname 88 | tax 89 | lda #$00 90 | sta PATHNAME+1,x 91 | 92 | ; Provide some user feedback 93 | lda #LOADING 95 | jsr PRINT 96 | lda #<(PATHNAME+1) 97 | ldx #>(PATHNAME+1) 98 | jsr PRINT 99 | lda #ELLIPSES 101 | jsr PRINT 102 | 103 | jsr MLI 104 | .byte OPEN_CALL 105 | .word OPEN_PARAM 106 | bcc :+ 107 | jmp ERROR 108 | 109 | ; Copy file reference number 110 | : lda OPEN_REF 111 | sta READ_REF 112 | sta CLOSE_REF 113 | 114 | ; Turn off 80-column firmware 115 | lda VERSION 116 | cmp #$06 ; //e ? 117 | bne :+ 118 | lda #$15 119 | jsr $C300 120 | 121 | ; Switch to hires page 2 122 | : bit TXTCLR 123 | bit MIXCLR 124 | bit HISCR 125 | bit HIRES 126 | 127 | jsr MLI 128 | .byte READ_CALL 129 | .word READ_PARAM 130 | bcs ERROR 131 | 132 | jsr MLI 133 | .byte CLOSE_CALL 134 | .word CLOSE_PARAM 135 | bcs ERROR 136 | 137 | ; Go for it ... 138 | jmp $6000 139 | 140 | PRINT: 141 | sta A1L 142 | stx A1H 143 | ldx VERSION 144 | ldy #$00 145 | : lda (A1L),y 146 | beq :++ 147 | cpx #$06 ; //e ? 148 | beq :+ 149 | cmp #$60 ; lowercase ? 150 | bcc :+ 151 | and #$5F ; -> uppercase 152 | : ora #$80 153 | jsr COUT 154 | iny 155 | bne :-- ; BRA 156 | : rts 157 | 158 | ERROR: 159 | bit TXTSET 160 | bit LOWSCR 161 | cmp #FILE_NOT_FOUND_ERR 162 | bne :+ 163 | lda #FILE_NOT_FOUND 165 | jsr PRINT 166 | beq :++ ; BRA 167 | : pha 168 | lda #ERROR_NUMBER 170 | jsr PRINT 171 | pla 172 | jsr PRBYTE 173 | : lda #PRESS_ANY_KEY 175 | jsr PRINT 176 | jsr RDKEY 177 | jsr MLI 178 | .byte QUIT_CALL 179 | .word QUIT_PARAM 180 | -------------------------------------------------------------------------------- /src/apple2/audio.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; audio.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "CODE" 10 | 11 | ;----------------------------------------------------------------------------- 12 | .proc audioPlayTitleNote 13 | 14 | fixedDuration = srcPtrL 15 | noteDuration = srcPtrH 16 | freqTimer1 = dstPtrL 17 | freqTimer2 = dstPtrH 18 | 19 | 20 | ldy #0 ; load the duration 21 | lda (musicL), y 22 | beq reset ; on 0, the song is done 23 | sta noteDuration 24 | 25 | lda audioMask ; see if the note should be played or simply delayed 26 | and #AUDIO_MUSIC 27 | bne :+ ; audio is on, go play 28 | jsr uiDelay ; waste time as though a note was played 29 | jmp leave ; and move the audio pointer along 30 | 31 | : 32 | iny ; load the 1st timer 33 | lda (musicL), y 34 | sta freqTimer1 35 | iny ; load the second timer 36 | lda (musicL), y 37 | sta freqTimer2 38 | 39 | preplay: 40 | lda #$8A ; repeat minimum this much 41 | sta fixedDuration 42 | play: 43 | lda SPEAKER ; toggle the speaker 44 | dec freqTimer1 ; timer1 down 45 | bne :++ ; not zero, go to timer 2 46 | ldy #1 ; reset timer 1 47 | lda (musicL), y 48 | sta freqTimer1 49 | ldx #11 ; delay about 20 clock cycles 50 | : 51 | dex 52 | bne :- 53 | : 54 | dec freqTimer2 ; dec timer 2 55 | bne :++ ; not zero? 56 | ldy #2 ; at zero, reset timer 2 57 | lda (musicL), y 58 | sta freqTimer2 59 | ldx #11 ; and waste 20 clock cycles 60 | : 61 | dex 62 | bne :- 63 | : 64 | dec fixedDuration ; dec the fixed repeat count 65 | bne play ; not zero, do another loop 66 | dec noteDuration ; dec the note delay 67 | bne preplay ; do more loops of at least fixedDuration 68 | 69 | leave: 70 | clc 71 | lda musicL ; add 3 to the music pointer 72 | adc #3 73 | sta musicL 74 | bcc :+ 75 | inc musicH 76 | clc ; leave with carry clear 77 | : 78 | rts 79 | 80 | reset: 81 | lda #titleMusic 84 | sta musicH 85 | 86 | sec ; and leave with carry set 87 | rts 88 | 89 | .endproc 90 | 91 | ;----------------------------------------------------------------------------- 92 | .proc audioPlayNote 93 | 94 | delayTime = tmpBot + 0 95 | 96 | lda audioMask ; see if the music is on 97 | and #AUDIO_MUSIC 98 | beq done 99 | 100 | play: 101 | ldx musicL ; get the index into the in-game music 102 | inc musicL ; and advance that index 103 | lda inGameMusic, x ; get the note at the index 104 | bne :+ ; if non-zero it's a valid note 105 | sta musicL ; reset the index to the start of the song 106 | beq play 107 | : 108 | ldy #$08 ; fixed number of iterations 109 | 110 | freq: ; external entry point for custom fixed iterations/delay 111 | sta delayTime ; store it in a delay counter 112 | lda audioMask ; see if 113 | and #AUDIO_SOUND ; audio is enabled 114 | beq done ; and play or ignore audio accordingly 115 | 116 | loop: 117 | lda SPEAKER ; toggle the speaker 118 | ldx delayTime ; and load the delay 119 | : 120 | dex ; count down 121 | bne :- ; to zero 122 | dey ; then repeat 123 | bne loop ; for the fixed iterations 124 | 125 | done: 126 | rts ; and return 127 | 128 | .endproc 129 | -------------------------------------------------------------------------------- /src/apple2/defs.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; defs.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | HGRPage1 := $2000 10 | HGRPage2 := $4000 11 | MLI := $BF00 12 | SPEAKER := $C030 13 | PLACEHOLDER := $FFFF 14 | 15 | ;----------------------------------------------------------------------------- 16 | ; Cheat Key (binary 0-19 reverse) 12345 17 | LEVEL_Central_Cavern := 0 ; 00000 18 | LEVEL_The_Cold_Room := 1 ; 10000 19 | LEVEL_The_Menagerie := 2 ; 01000 20 | LEVEL_Abandoned_Uranium_Workings := 3 ; 11000 21 | LEVEL_Eugenes_Lair := 4 ; 00100 22 | LEVEL_Processing_Plant := 5 ; 10100 23 | LEVEL_The_Vat := 6 ; 01100 24 | LEVEL_Miner_Willy_meets_the_Kong := 7 ; 11100 25 | LEVEL_Wacky_Amoebatrons := 8 ; 00010 26 | LEVEL_The_Endorian_Forest := 9 ; 10010 27 | LEVEL_Attack_of_the_Mutant_Telephones := 10 ; 01010 28 | LEVEL_Return_of_the_Alien_Kong_Beast := 11 ; 11010 29 | LEVEL_Ore_Refinery := 12 ; 00110 30 | LEVEL_Skylab_Landing_Bay := 13 ; 10110 31 | LEVEL_The_Bank := 14 ; 01110 32 | LEVEL_The_Sixteenth_Cavern := 15 ; 11110 33 | LEVEL_The_Warehouse := 16 ; 00001 34 | LEVEL_Amoebatrons_Revenge := 17 ; 10001 35 | LEVEL_Solar_Power_Generator := 18 ; 01001 36 | LEVEL_The_Final_Barrier := 19 ; 11001 37 | LEVEL_Game_Over := 20 38 | 39 | ;----------------------------------------------------------------------------- 40 | DATA_BLANK := $00 41 | DATA_FLOOR1 := $10 42 | DATA_FLOOR2 := $20 43 | DATA_CONVEYOR := $30 44 | DATA_WALL := $40 45 | DATA_BUSH := $50 46 | DATA_ROCK := $60 47 | DATA_COLLAPSE := $70 48 | DATA_KEY := $80 49 | DATA_SWITCH1 := $90 50 | DATA_SWITCH1_OPEN := $A0 51 | DATA_SWITCH2 := $B0 52 | DATA_SWITCH2_OPEN := $C0 53 | DATA_DOOR := $D0 54 | 55 | ;----------------------------------------------------------------------------- 56 | START_LIVES := 2 57 | MAX_LIVES := 5 58 | 59 | PLAY_ROWS := 16 60 | PLAY_COLS := 32 61 | VISIBLE_COLS := 20 62 | 63 | TILES_PER_LEVEL := 8 64 | TILE_BYTES := 16 65 | CONVEYOR_FRAMES := 7 66 | KEYS_FRAMES := 4 67 | 68 | MAX_SPRITES := 10 69 | SPRITE_BYTES := 64 70 | MAX_SPRITE_IFRAMES := 36 71 | 72 | AIR_STR_LEN := 5 73 | AIR_SPEED := 14 74 | 75 | DEMO_TIMER_DURATION := 2 76 | DEMO_TIMER_INITAL := 18 77 | 78 | ;----------------------------------------------------------------------------- 79 | CLASS_MOVE_Y := levelLayout 23 | sta store + 2 24 | 25 | lda levelsL, x ; get the start of the level data intp srcPtr 26 | sta srcPtrL 27 | lda levelsH, x 28 | sta srcPtrH 29 | 30 | ldx #0 ; start at 0 in read and write area 31 | ldy #0 32 | 33 | loop: 34 | lda (srcPtrL), y ; read the first level byte (repeat counts) 35 | beq done ; $00 ends level 36 | pha ; save 37 | iny ; point y at next byte 38 | 39 | lsr ; get high repeat nibble into low nibble 40 | lsr 41 | lsr 42 | lsr 43 | tax ; but result in X 44 | 45 | lda (srcPtrL), y ; read the next byte from level (values) 46 | sta value ; save 47 | iny ; advance y to next rep counts 48 | 49 | lsr ; get high value nibble into low nibble 50 | lsr 51 | lsr 52 | lsr 53 | jsr :+ ; store value repeat times into level decode area 54 | 55 | pla ; restore the rep byte 56 | and #$0f ; only the low nibble 57 | tax ; put in x 58 | lda value ; restore values 59 | and #$0f ; only low nibble 60 | jsr :+ ; store value repeat times into level decode area 61 | jmp loop ; repeat 62 | 63 | : 64 | stx len ; save the repeat count 65 | tax ; value into x (value is a tile index) 66 | lda mult16, x ; mult value by 16 as that's how wide a tile is 67 | ldx len ; restore the repeat count 68 | dex ; store index is 0+ but count is 1+ so pre-dec 69 | 70 | store: 71 | sta PLACEHOLDER, x ; store the tile offset into the level decode area 72 | dex ; from back to front and 73 | bpl store ; repeat for all repetitions 74 | 75 | lda len ; get the repeat length 76 | clc 77 | adc store + 1 ; advance the stor pointer by the length 78 | sta store + 1 79 | bcc done 80 | inc store + 2 81 | 82 | done: 83 | rts ; return to self or caller 84 | 85 | .endproc 86 | 87 | ;----------------------------------------------------------------------------- 88 | .proc levelPlaceKeys 89 | 90 | lda #0 91 | sta keysToCollect ; init to 0 keys to collect 92 | 93 | lda currLevel 94 | asl ; * 2 95 | asl ; * 4 96 | adc currLevel ; * 5 - index into key table 97 | tax ; keep index in x 98 | 99 | lda #4 ; Need to do 5 keys, 0-4 100 | sta sizeL ; save count in sizeL 101 | ldy #0 ; keep offst from ptr at 0 102 | : 103 | lda keyyH, x ; get the key Y hi (y=row*32 in key table) 104 | bmi next ; if $ff then skip 105 | inc keysToCollect ; count the key 106 | sta dstPtrH ; save the hi 107 | lda keyyL, x ; get the y lo 108 | adc keyx, x ; and add the x (no carry still) 109 | sta dstPtrL ; save as dst pointer 110 | lda #>levelLayout ; get the scratch hi (the lo is 0 as it's aligned) 111 | adc dstPtrH ; and add to the Y hi (and any carry) 112 | sta dstPtrH ; and save as the dst Pointer Hi 113 | lda #8*16 ; key tile offset * width of a tile 114 | sta (dstPtrL), y ; poke into the unpacked level 115 | 116 | next: 117 | inx ; next key 118 | dec sizeL ; one less key to do 119 | bpl :- ; all keys done? 120 | 121 | rts 122 | 123 | .endproc 124 | -------------------------------------------------------------------------------- /src/apple2/logo.hgr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StewBC/mminer-apple2/4d05b761981a7367b1a9ca24ce9ae31126e20d8f/src/apple2/logo.hgr -------------------------------------------------------------------------------- /src/apple2/logo.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; logo.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "HGR" 10 | 11 | .incbin "logo.hgr" 12 | 13 | ;----------------------------------------------------------------------------- 14 | .segment "RODATA" 15 | 16 | manicText: 17 | .byte $88, $D8, $A8, $88, $88, $00, $00 18 | .byte $00, $70, $88, $88, $F8, $88, $00 19 | .byte $88, $C8, $A8, $98, $88, $00, $00 20 | .byte $00, $00, $E0, $40, $40, $40, $E0 21 | .byte $00, $70, $88, $80, $88, $70, $00 22 | 23 | manicCharWidth: 24 | .byte 6, 6, 6, 4, 5 25 | 26 | manicColors: 27 | .byte 2, 1, 0, 3, 4 28 | 29 | ;----------------------------------------------------------------------------- 30 | minerText: 31 | .byte $00, $00, $88, $D8, $A8, $88, $88 32 | .byte $E0, $40, $40, $40, $E0, $00, $00 33 | .byte $00, $88, $C8, $A8, $98, $88, $00 34 | .byte $F8, $80, $F8, $80, $F8, $00, $00 35 | .byte $00, $00, $F0, $88, $F0, $A0, $98 36 | 37 | minerCharWidth: 38 | .byte 6, 4, 6, 6, 5 39 | 40 | minerColors: 41 | .byte 3, 4, 2, 1, 0 42 | -------------------------------------------------------------------------------- /src/apple2/mminer.asm: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; mminer.asm 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "CODE" 10 | 11 | ;----------------------------------------------------------------------------- 12 | .proc main 13 | 14 | jsr mainInit ; do some one-time global init 15 | jsr uiWaitForIntroEnter ; color cycle ENTER and wait for key 16 | 17 | loop: 18 | jsr uiTitleScreen ; go to the ui 19 | and #EVENT_EXIT_GAME ; see if event to exit game is set 20 | bne quit 21 | jsr gameLoop ; not quit, so run the gameplay (or demo) 22 | jmp loop ; go back to the ui 23 | 24 | quit: 25 | jsr MLI ; quit using the prodos mli 26 | 27 | .byte $65 ; ProDOS Quit request 28 | .addr * + 2 29 | .byte 4 30 | .byte 0 31 | .word 0000 32 | .byte 0 33 | .word 0000 34 | 35 | .endproc 36 | 37 | ;----------------------------------------------------------------------------- 38 | .include "apple2.inc" ; cc65 include file for LOWSCR, etc 39 | .include "logo.inc" ; loading bitmap, manic miner bouncy text 40 | .include "defs.inc" ; globally used defines 41 | .include "variables.inc" ; all game variables and buffers 42 | .include "roaudio.inc" ; all ro files read only. This music and sfx 43 | .include "rofont.inc" ; ZX Spectrum font 44 | .include "rolevels.inc" ; level layout, sprite positions, colors, etc. 45 | .include "rosprites.inc" ; sprite definitions 46 | .include "rosystem.inc" ; useful tables for mult, color masks, etc. 47 | .include "rotext.inc" ; all in-game text (except scores in variables) 48 | .include "rotiles.inc" ; all tile (background) definitions 49 | .include "screen.inc" ; code to draw, clear, etc. the screen 50 | .include "text.inc" ; code to manipulate and show text 51 | .include "input.inc" ; keyboard handling 52 | .include "level.inc" ; unpack level and put keys in place 53 | .include "sprite.inc" ; instance and color sprites, etc. 54 | .include "tiles.inc" ; put right tiles in place and color, etc. 55 | .include "ui.inc" ; all pre-game screens 56 | .include "audio.inc" ; play the music and make tones 57 | .include "willy.inc" ; user controlled character logic 58 | .include "game.inc" ; game flow, ai, game over, etc. 59 | 60 | 61 | ;----------------------------------------------------------------------------- 62 | .proc mainInit 63 | 64 | lda #0 ; init some one-time globals 65 | sta backPage 66 | sta leftEdge 67 | sta cameraMode 68 | sta uiComponent 69 | sta cheatActive 70 | sta cheatIndex 71 | sta monochrome 72 | 73 | lda #AUDIO_MUSIC | AUDIO_SOUND ; turn the music and in-game sounds on 74 | sta audioMask 75 | 76 | lda #>HGRPage1 ; set the current hidden (back) page to page 1 77 | sta currPageH ; (page 2 was made visible by the loader) 78 | 79 | lda #$80 ; make a zero-page bit mask area for checking bits 80 | ldx #7 ; from 1 to 128, set each bit (backwards) 81 | : 82 | sta bitMasks, x ; set the bits in the area called bitMasks 83 | lsr 84 | dex 85 | bpl :- 86 | 87 | rts 88 | 89 | .endproc 90 | -------------------------------------------------------------------------------- /src/apple2/mminer.cfg: -------------------------------------------------------------------------------- 1 | SYMBOLS { 2 | __EXEHDR__: type = import; 3 | __FILETYPE__: type = export, value = $0006; 4 | } 5 | MEMORY { 6 | ZP: file = "", start = $0050, size = $0100 - $0050; 7 | HEADER: file = %O, start = $4000 - $003A, size = $003A; 8 | MAIN: file = %O, define = yes, start = $4000, size = $BF00 - $4000; 9 | LOW: file = "", start = $0800, size = $2000 - $0800; 10 | } 11 | SEGMENTS { 12 | ZEROPAGE: load = ZP, type = zp; 13 | EXEHDR: load = HEADER, type = ro; 14 | HGR: load = MAIN, type = rw; 15 | CODE: load = MAIN, type = rw, start = $6000; 16 | RODATA: load = MAIN, type = ro, align = $10; 17 | DATA: load = MAIN, type = rw; 18 | LOWMEM: load = LOW, type = rw; 19 | } 20 | -------------------------------------------------------------------------------- /src/apple2/roaudio.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; roaudio.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "RODATA" 10 | 11 | ;----------------------------------------------------------------------------- 12 | titleMusic: 13 | .byte $50, $80, $81, $50, $66, $67, $50, $56 14 | .byte $57, $32, $56, $57, $32, $AB, $CB, $32 15 | .byte $2B, $33, $32, $2B, $33, $32, $AB, $CB 16 | .byte $32, $33, $40, $32, $33, $40, $32, $AB 17 | .byte $CB, $32, $80, $81, $32, $80, $81, $32 18 | .byte $66, $67, $32, $56, $57, $32, $60, $56 19 | .byte $32, $AB, $C0, $32, $2B, $30, $32, $2B 20 | .byte $30, $32, $AB, $C0, $32, $30, $44, $32 21 | .byte $30, $44, $32, $AB, $C0, $32, $88, $89 22 | .byte $32, $88, $89, $32, $72, $73, $32, $4C 23 | .byte $4D, $32, $4C, $4D, $32, $AB, $C0, $32 24 | .byte $26, $30, $32, $26, $30, $32, $AB, $C0 25 | .byte $32, $30, $44, $32, $30, $44, $32, $AB 26 | .byte $C0, $32, $88, $89, $32, $88, $89, $32 27 | .byte $72, $73, $32, $4C, $4D, $32, $4C, $4D 28 | .byte $32, $AB, $CB, $32, $26, $33, $32, $26 29 | .byte $33, $32, $AB, $CB, $32, $33, $40, $32 30 | .byte $33, $40, $32, $AB, $CB, $32, $80, $81 31 | .byte $32, $80, $81, $32, $66, $67, $32, $56 32 | .byte $57, $32, $40, $41, $32, $80, $AB, $32 33 | .byte $20, $2B, $32, $20, $2B, $32, $80, $AB 34 | .byte $32, $2B, $33, $32, $2B, $33, $32, $80 35 | .byte $AB, $32, $80, $81, $32, $80, $81, $32 36 | .byte $66, $67, $32, $56, $57, $32, $40, $41 37 | .byte $32, $80, $98, $32, $20, $26, $32, $20 38 | .byte $26, $32, $80, $98, $32, $26, $30, $32 39 | .byte $26, $30, $32, $00, $00, $32, $72, $73 40 | .byte $32, $72, $73, $32, $60, $61, $32, $4C 41 | .byte $4D, $32, $4C, $99, $32, $4C, $4D, $32 42 | .byte $4C, $4D, $32, $4C, $99, $32, $5B, $5C 43 | .byte $32, $56, $57, $32, $33, $CD, $32, $33 44 | .byte $34, $32, $33, $34, $32, $33, $CD, $32 45 | .byte $40, $41, $32, $66, $67, $64, $66, $67 46 | .byte $32, $72, $73, $64, $4C, $4D, $32, $56 47 | .byte $57, $32, $80, $CB, $19, $80, $00, $19 48 | .byte $80, $81, $32, $80, $CB, $00 49 | 50 | inGameMusic: 51 | .byte $E6, $CD, $B7, $AC, $9A, $B7, $9A, $9A 52 | .byte $91, $AC, $91, $91, $9A, $B7, $9A, $9A 53 | .byte $E6, $CD, $B7, $AC, $9A, $B7, $9A, $9A 54 | .byte $91, $AC, $91, $91, $9A, $9A, $9A, $9A 55 | .byte $E6, $CD, $B7, $AC, $9A, $B7, $9A, $9A 56 | .byte $91, $AC, $91, $91, $9A, $B7, $9A, $9A 57 | .byte $E6, $CD, $B7, $AC, $9A, $B7, $9A, $73 58 | .byte $9A, $B7, $E6, $B7, $9A, $9A, $9A, $9A 59 | .byte $00 60 | 61 | jumpFreq: 62 | .byte $40, $40, $38, $38, $30, $30, $28, $28 63 | .byte $20, $20, $28, $28, $30, $30, $38, $38 64 | .byte $40, $40 65 | 66 | fallFreq: 67 | .byte $F4, $FF, $4C, $58, $64, $70, $7C, $88 68 | .byte $94, $A0, $AC, $B8, $C4, $D0, $DC, $E8 69 | -------------------------------------------------------------------------------- /src/apple2/rolevels.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; rolevels.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "RODATA" 10 | 11 | level_01: 12 | .byte $1A, $40, $14, $60, $1E, $60, $2F, $40, $F2, $04, $FF, $00, $2F, $40, $F2, $04 13 | .byte $F7, $00, $13, $50, $13, $50, $2D, $41, $41, $71, $48, $71, $2F, $40, $F2, $04 14 | .byte $3F, $10, $C2, $04, $F1, $00, $31, $40, $19, $50, $24, $41, $3F, $03, $53, $30 15 | .byte $2F, $40, $D2, $01, $2F, $40, $F2, $04, $B1, $05, $73, $04, $53, $71, $24, $40 16 | .byte $FB, $10, $2F, $40, $F2, $04, $FF, $11, $01, $04 17 | .byte $00 18 | 19 | 20 | level_02: 21 | .byte $1F, $40, $3E, $04, $FE, $00, $12, $64, $FF, $00, $2F, $40, $53, $07, $16, $10 22 | .byte $2F, $40, $F2, $04, $F4, $11, $81, $04, $22, $04, $F5, $00, $41, $14, $21, $74 23 | .byte $22, $04, $15, $17, $F3, $00, $12, $40, $12, $40, $2F, $40, $91, $04, $21, $74 24 | .byte $22, $04, $87, $01, $91, $04, $21, $74, $22, $04, $F3, $00, $42, $70, $12, $47 25 | .byte $12, $40, $22, $40, $4F, $30, $31, $04, $21, $74, $22, $04, $D4, $01, $71, $04 26 | .byte $21, $74, $22, $04, $74, $07, $F4, $00, $2F, $40, $F2, $04, $FF, $11, $01, $04 27 | .byte $00 28 | 29 | 30 | level_03: 31 | .byte $19, $40, $17, $60, $18, $20, $13, $60, $2F, $40, $21, $06, $C2, $04, $FF, $00 32 | .byte $2F, $40, $F2, $04, $FF, $00, $24, $41, $FB, $77, $2F, $40, $F2, $04, $6F, $10 33 | .byte $54, $01, $21, $42, $FE, $00, $21, $42, $46, $03, $F4, $00, $21, $42, $F8, $00 34 | .byte $62, $14, $1C, $60, $5C, $10, $24, $40, $6F, $10, $52, $04, $F5, $00, $A2, $14 35 | .byte $FF, $00, $2F, $41, $F1, $14 36 | .byte $00 37 | 38 | 39 | level_04: 40 | .byte $16, $40, $16, $60, $F4, $44, $FF, $00, $2F, $40, $F2, $04, $F3, $00, $66, $10 41 | .byte $2F, $40, $B4, $01, $21, $41, $51, $01, $91, $01, $D2, $04, $B2, $01, $73, $01 42 | .byte $72, $04, $3F, $70, $C2, $04, $62, $01, $F2, $00, $32, $10, $2F, $40, $23, $01 43 | .byte $A2, $04, $3F, $30, $B1, $01, $2B, $40, $37, $10, $36, $10, $25, $40, $2F, $10 44 | .byte $14, $60, $32, $14, $F2, $00, $2B, $10, $2F, $40, $F2, $04, $FF, $11, $01, $04 45 | .byte $00 46 | 47 | 48 | level_05: 49 | .byte $1F, $40, $41, $06, $A2, $04, $FF, $00, $2F, $40, $F2, $04, $FF, $00, $2F, $40 50 | .byte $81, $05, $62, $04, $D4, $10, $46, $71, $32, $04, $FD, $00, $22, $14, $F5, $00 51 | .byte $19, $50, $2F, $40, $2A, $03, $32, $04, $3A, $01, $F2, $00, $2F, $40, $F2, $04 52 | .byte $2B, $71, $47, $01, $51, $01, $27, $40, $1F, $40, $72, $04, $25, $10, $15, $40 53 | .byte $12, $40, $1D, $40, $24, $40, $12, $50, $15, $40, $12, $40, $72, $45, $52, $04 54 | .byte $7F, $14, $17, $41, $01, $04 55 | .byte $00 56 | 57 | 58 | level_06: 59 | .byte $1F, $40, $F2, $04, $FF, $00, $2F, $40, $F2, $04, $FF, $00, $2F, $40, $51, $06 60 | .byte $92, $04, $73, $01, $42, $01, $45, $01, $52, $04, $22, $01, $B1, $04, $B3, $01 61 | .byte $2F, $40, $1E, $40, $2F, $40, $75, $01, $32, $04, $2F, $10, $D2, $04, $69, $01 62 | .byte $19, $41, $52, $04, $F1, $04, $1D, $60, $22, $40, $1C, $50, $1B, $40, $32, $14 63 | .byte $24, $03, $F2, $01, $72, $04, $FF, $00, $2F, $41, $F1, $14 64 | .byte $00 65 | 66 | 67 | level_07: 68 | .byte $1D, $40, $F4, $44, $FF, $00, $2F, $40, $F2, $04, $E2, $01, $1C, $47, $12, $04 69 | .byte $F1, $00, $1D, $47, $26, $40, $52, $30, $31, $14, $A1, $76, $22, $74, $3D, $10 70 | .byte $12, $47, $1A, $07, $2F, $40, $11, $04, $91, $70, $32, $74, $1F, $10, $15, $47 71 | .byte $17, $67, $2D, $40, $31, $14, $D2, $74, $B5, $10, $11, $47, $18, $07, $12, $67 72 | .byte $2F, $40, $11, $04, $C1, $70, $2D, $40, $45, $47, $17, $67, $28, $40, $32, $10 73 | .byte $1F, $40, $12, $04, $D1, $04, $F1, $00, $2D, $41, $F3, $44 74 | .byte $00 75 | 76 | 77 | level_08: 78 | .byte $11, $40, $17, $60, $16, $60, $12, $40, $1A, $40, $2F, $40, $11, $04, $21, $04 79 | .byte $A2, $04, $E2, $01, $1B, $40, $22, $14, $F1, $00, $1D, $40, $2F, $40, $11, $04 80 | .byte $D2, $04, $35, $10, $62, $10, $12, $41, $B2, $04, $F1, $00, $13, $40, $45, $10 81 | .byte $12, $14, $13, $01, $C1, $04, $91, $01, $32, $04, $73, $01, $61, $04, $D2, $04 82 | .byte $F1, $00, $15, $41, $82, $04, $1A, $10, $32, $10, $19, $40, $42, $14, $82, $01 83 | .byte $61, $04, $D2, $04, $32, $01, $B1, $04, $45, $01, $42, $04, $A3, $03, $12, $40 84 | .byte $12, $41, $B2, $04, $D1, $04, $21, $04, $51, $05, $72, $04, $FF, $11, $01, $04 85 | .byte $00 86 | 87 | 88 | level_09: 89 | .byte $12, $40, $1F, $40, $C2, $04, $FF, $00, $2F, $40, $F2, $04, $FF, $00, $2F, $40 90 | .byte $F2, $04, $42, $10, $32, $10, $82, $10, $32, $10, $22, $10, $2F, $40, $F2, $04 91 | .byte $FD, $00, $22, $14, $22, $01, $23, $01, $28, $03, $B2, $04, $F6, $00, $32, $10 92 | .byte $22, $10, $22, $41, $FD, $00, $2F, $40, $F2, $04, $22, $01, $23, $01, $28, $01 93 | .byte $23, $01, $22, $01, $22, $04, $FD, $00, $22, $14, $FF, $00, $2F, $41, $F1, $14 94 | .byte $00 95 | 96 | 97 | level_10: 98 | .byte $1A, $40, $11, $60, $31, $14, $11, $06, $11, $06, $A2, $14, $F1, $04, $41, $06 99 | .byte $92, $04, $69, $10, $1A, $40, $42, $14, $21, $06, $C1, $04, $E2, $04, $F1, $04 100 | .byte $4A, $10, $27, $40, $17, $17, $16, $40, $82, $14, $4B, $10, $1E, $40, $2F, $40 101 | .byte $17, $41, $34, $70, $25, $41, $A1, $04, $E2, $04, $17, $60, $71, $14, $C2, $01 102 | .byte $24, $41, $29, $70, $17, $41, $61, $06, $2F, $40, $16, $40, $13, $67, $42, $04 103 | .byte $7A, $02, $D2, $04, $3F, $10, $93, $01, $2F, $40, $F1, $04, $FF, $22, $02, $02 104 | .byte $00 105 | 106 | 107 | level_11: 108 | .byte $7C, $40, $1B, $60, $2F, $40, $31, $05, $B2, $04, $FF, $00, $24, $41, $FB, $00 109 | .byte $2F, $40, $F2, $04, $46, $01, $42, $01, $72, $21, $52, $04, $F8, $00, $14, $60 110 | .byte $22, $14, $F8, $00, $16, $60, $22, $41, $22, $03, $F2, $00, $14, $60, $22, $14 111 | .byte $A9, $01, $41, $05, $62, $04, $B1, $06, $61, $06, $81, $01, $22, $04, $53, $07 112 | .byte $12, $10, $16, $60, $1B, $50, $2B, $40, $1F, $50, $32, $14, $2F, $10, $53, $01 113 | .byte $52, $04, $FF, $00, $2F, $41, $F1, $14 114 | .byte $00 115 | 116 | 117 | level_12: 118 | .byte $11, $40, $17, $60, $16, $60, $13, $40, $19, $40, $2F, $40, $F2, $04, $E2, $07 119 | .byte $E2, $04, $FF, $00, $2F, $40, $F2, $04, $35, $10, $51, $74, $21, $04, $62, $71 120 | .byte $52, $04, $D1, $04, $21, $04, $C1, $01, $25, $40, $26, $10, $12, $40, $1D, $40 121 | .byte $22, $40, $1A, $10, $12, $40, $17, $40, $62, $14, $94, $01, $12, $40, $1D, $40 122 | .byte $25, $40, $1A, $10, $13, $41, $A2, $04, $F1, $00, $17, $40, $24, $10, $26, $41 123 | .byte $A1, $04, $51, $05, $41, $05, $22, $04, $A3, $01, $12, $40, $1B, $43, $22, $04 124 | .byte $D1, $04, $21, $04, $D2, $04, $D4, $14, $D1, $14 125 | .byte $00 126 | 127 | 128 | level_13: 129 | .byte $FF, $44, $32, $40, $1F, $20, $C2, $04, $21, $02, $FC, $00, $22, $40, $13, $20 130 | .byte $F3, $11, $24, $01, $22, $40, $1F, $20, $C2, $04, $21, $02, $FC, $00, $22, $40 131 | .byte $13, $20, $22, $10, $42, $10, $52, $10, $42, $10, $12, $14, $21, $02, $FC, $00 132 | .byte $22, $40, $1F, $20, $C2, $04, $21, $02, $35, $01, $23, $01, $35, $01, $24, $01 133 | .byte $22, $40, $1F, $20, $C2, $04, $21, $02, $FC, $00, $22, $40, $13, $20, $32, $10 134 | .byte $32, $10, $42, $10, $42, $10, $22, $14, $21, $02, $FC, $00, $22, $40, $1F, $20 135 | .byte $C2, $04, $2F, $13, $B2, $31, $01, $04 136 | .byte $00 137 | 138 | 139 | level_14: 140 | .byte $1F, $40, $F2, $04, $FF, $00, $2F, $40, $F2, $04, $FF, $00, $2F, $40, $F2, $04 141 | .byte $E1, $01, $1E, $20, $22, $40, $11, $12, $61, $01, $16, $20, $11, $12, $61, $01 142 | .byte $12, $20, $26, $40, $11, $12, $E1, $01, $16, $20, $2F, $40, $F2, $04, $41, $01 143 | .byte $16, $20, $11, $12, $61, $01, $16, $20, $11, $12, $2F, $40, $F2, $04, $11, $12 144 | .byte $61, $01, $14, $20, $64, $30, $11, $12, $42, $04, $FF, $00, $26, $40, $11, $12 145 | .byte $F7, $00, $2F, $40, $FF, $04, $F3, $44 146 | .byte $00 147 | 148 | 149 | level_15: 150 | .byte $15, $40, $FC, $44, $FD, $00, $22, $24, $FD, $00, $22, $24, $7F, $03, $15, $31 151 | .byte $22, $24, $71, $06, $F4, $00, $12, $62, $25, $41, $21, $05, $F4, $00, $12, $62 152 | .byte $2F, $40, $82, $01, $21, $06, $22, $24, $61, $07, $42, $01, $E1, $06, $22, $24 153 | .byte $22, $01, $D2, $01, $81, $06, $22, $24, $F9, $00, $21, $10, $12, $62, $22, $41 154 | .byte $92, $01, $E1, $05, $22, $24, $F2, $00, $29, $10, $22, $24, $43, $01, $F2, $01 155 | .byte $42, $02, $2B, $40, $2F, $10, $22, $24, $FD, $00, $22, $24, $FF, $11, $01, $04 156 | .byte $00 157 | 158 | 159 | level_16: 160 | .byte $1F, $40, $F2, $04, $FF, $00, $2F, $40, $F2, $04, $FF, $00, $2F, $40, $F2, $04 161 | .byte $14, $10, $14, $10, $12, $40, $17, $40, $36, $10, $2A, $40, $12, $40, $2F, $40 162 | .byte $23, $40, $16, $10, $12, $40, $38, $40, $62, $14, $A1, $04, $24, $04, $D2, $04 163 | .byte $2F, $73, $94, $30, $2F, $40, $F2, $04, $92, $04, $2C, $10, $14, $10, $29, $41 164 | .byte $F6, $00, $2F, $40, $41, $01, $51, $01, $42, $04, $F7, $00, $35, $50, $2F, $41 165 | .byte $F1, $14 166 | .byte $00 167 | 168 | 169 | level_17: 170 | .byte $1F, $40, $D4, $04, $FF, $00, $2F, $40, $F2, $04, $FF, $00, $25, $40, $12, $50 171 | .byte $13, $50, $12, $50, $15, $50, $11, $50, $16, $50, $22, $41, $72, $70, $72, $70 172 | .byte $31, $70, $22, $70, $22, $14, $21, $76, $62, $70, $72, $70, $62, $70, $22, $74 173 | .byte $92, $70, $31, $70, $32, $70, $51, $76, $22, $07, $22, $47, $25, $07, $22, $07 174 | .byte $52, $30, $62, $70, $22, $74, $11, $07, $25, $07, $27, $07, $26, $07, $22, $07 175 | .byte $22, $47, $25, $07, $27, $07, $12, $07, $14, $67, $22, $07, $22, $47, $24, $07 176 | .byte $12, $60, $E3, $70, $22, $74, $22, $70, $52, $70, $F2, $70, $22, $74, $FF, $00 177 | .byte $2F, $40, $B4, $01, $2F, $41, $F1, $14 178 | .byte $00 179 | 180 | 181 | level_18: 182 | .byte $1F, $40, $C1, $04, $22, $04, $FF, $00, $2F, $40, $F2, $04, $FF, $00, $2F, $40 183 | .byte $F2, $04, $22, $01, $23, $01, $28, $01, $23, $01, $24, $01, $2F, $40, $F2, $04 184 | .byte $2F, $10, $D2, $04, $B8, $01, $23, $01, $22, $01, $22, $04, $22, $01, $23, $01 185 | .byte $F6, $00, $2F, $40, $D2, $01, $2F, $40, $F2, $04, $22, $01, $23, $01, $28, $01 186 | .byte $23, $01, $22, $01, $22, $04, $2F, $10, $D2, $04, $FF, $00, $1F, $41, $F2, $11 187 | .byte $00 188 | 189 | 190 | level_19: 191 | .byte $3F, $40, $D2, $04, $FF, $00, $2F, $40, $F2, $04, $FF, $00, $2F, $40, $F2, $04 192 | .byte $22, $01, $46, $01, $97, $01, $2F, $40, $F2, $04, $F3, $00, $39, $10, $22, $41 193 | .byte $A3, $01, $87, $01, $2F, $40, $F2, $04, $F3, $00, $2A, $10, $24, $41, $F4, $00 194 | .byte $72, $14, $64, $03, $35, $01, $C2, $04, $FF, $00, $4F, $40, $D4, $04, $F5, $11 195 | .byte $17, $41, $01, $04 196 | .byte $00 197 | 198 | 199 | level_20: 200 | .byte $FF, $00, $FF, $00, $FF, $00, $FF, $00, $FB, $00, $EF, $40, $31, $04, $21, $04 201 | .byte $91, $04, $F3, $00, $12, $40, $19, $40, $F5, $44, $21, $04, $92, $04, $FD, $00 202 | .byte $22, $14, $FF, $00, $2F, $43, $73, $30, $14, $70, $28, $40, $12, $50, $14, $50 203 | .byte $13, $50, $16, $50, $12, $10, $22, $41, $FD, $00, $24, $40, $2F, $10, $92, $04 204 | .byte $FF, $00, $2F, $41, $F1, $14 205 | .byte $00 206 | 207 | ;----------------------------------------------------------------------------- 208 | levelsL: 209 | .byte level_01, >level_02, >level_03, >level_04, >level_05, >level_06 216 | .byte >level_07, >level_08, >level_09, >level_10, >level_11, >level_12 217 | .byte >level_13, >level_14, >level_15, >level_16, >level_17, >level_18 218 | .byte >level_19, >level_20 219 | 220 | 221 | ;----------------------------------------------------------------------------- 222 | ; This table maps tiles (1-8) to tile representations, per level. 223 | ; space is 00, floor1 is 1 and collapse is 7 & key is 8. Only 0 through 7 224 | ; encoded in the level data. Key, door and switch added programatically to 225 | ; the level. The switch looks the same in both kong levels (only place it 226 | ; is present) so it doesn't need/get an entry. The door "tile" isn't seen, 227 | ; so isn't rendered. It's below the door sprite for collision purposes only. 228 | ; 229 | ; floor1, floor2, conveyor, wall, bush, rock, collapse, key - door & switch not here 230 | levelTiles: 231 | .byte $31, $00, $1C, $1F, $0D, $28, $27, $09 ; Level 1 232 | .byte $31, $00, $1C, $1F, $0D, $28, $27, $10 ; Level 2 233 | .byte $2E, $03, $1E, $15, $0D, $05, $2D, $09 ; Level 3 234 | .byte $31, $00, $1C, $1F, $0D, $34, $27, $09 ; Level 4 235 | .byte $31, $00, $07, $1F, $0D, $13, $27, $08 ; Level 5 236 | .byte $2F, $00, $1C, $1F, $0D, $0B, $31, $09 ; Level 6 237 | .byte $31, $00, $16, $1F, $0D, $17, $26, $09 ; Level 7 238 | .byte $31, $35, $1D, $1F, $0D, $13, $27, $14 ; Level 8 239 | .byte $31, $00, $07, $18, $0D, $13, $27, $09 ; Level 9 240 | .byte $12, $30, $1C, $1B, $0D, $0F, $20, $01 ; Level 10 241 | .byte $31, $33, $21, $1A, $04, $03, $27, $0A ; Level 11 242 | .byte $31, $35, $1D, $1F, $0D, $13, $27, $14 ; Level 12 243 | .byte $29, $23, $07, $18, $0D, $13, $31, $06 ; Level 13 244 | .byte $2C, $2B, $07, $22, $00, $00, $31, $19 ; Level 14 245 | .byte $32, $2A, $21, $1A, $04, $03, $27, $11 ; Level 15 246 | .byte $31, $31, $1D, $1B, $0E, $13, $27, $02 ; Level 16 247 | .byte $31, $00, $1C, $25, $0D, $0C, $26, $09 ; Level 17 248 | .byte $31, $00, $1C, $24, $0D, $13, $27, $09 ; Level 18 249 | .byte $31, $00, $1C, $1F, $0D, $13, $27, $09 ; Level 19 250 | .byte $31, $00, $1C, $1F, $04, $13, $27, $09 ; Level 20 251 | 252 | ; This table says which color index to use, to color a 253 | ; tile (1-8), per level. The colors are: 254 | ; 00 White - 01 Green - 02 Orange - 03 Purple - 04 Blue 255 | ; The keys are done as a color animation so at inctance 256 | ; time they are left white. 257 | ; 258 | ; floor1, floor2, conveyor, wall, bush, rock, collapse, key - door & switch not here 259 | levelMasks: 260 | .byte $01, $02, $03, $02, $01, $02, $04, $00 ; Level 1 261 | .byte $03, $02, $03, $02, $01, $02, $04, $00 ; Level 2 262 | .byte $04, $00, $03, $04, $01, $03, $01, $00 ; Level 3 263 | .byte $02, $00, $03, $04, $01, $00, $04, $00 ; Level 4 264 | .byte $04, $02, $02, $01, $00, $02, $01, $00 ; Level 5 265 | .byte $01, $00, $04, $02, $01, $00, $04, $00 ; Level 6 266 | .byte $03, $02, $01, $04, $01, $00, $02, $00 ; Level 7 267 | .byte $02, $00, $01, $00, $01, $02, $04, $00 ; Level 8 268 | .byte $03, $02, $01, $02, $01, $02, $04, $00 ; Level 9 269 | .byte $01, $00, $01, $02, $01, $01, $02, $00 ; Level 10 270 | .byte $04, $00, $03, $02, $02, $00, $01, $00 ; Level 11 271 | .byte $03, $00, $00, $02, $01, $02, $04, $00 ; Level 12 272 | .byte $04, $03, $01, $02, $01, $02, $04, $00 ; Level 13 273 | .byte $04, $03, $00, $04, $01, $02, $04, $00 ; Level 14 274 | .byte $04, $02, $00, $00, $02, $00, $04, $00 ; Level 15 275 | .byte $02, $03, $00, $03, $01, $02, $04, $00 ; Level 16 276 | .byte $01, $02, $01, $02, $01, $00, $04, $00 ; Level 17 277 | .byte $02, $02, $03, $03, $01, $02, $04, $00 ; Level 18 278 | .byte $01, $02, $00, $02, $01, $02, $04, $00 ; Level 19 279 | .byte $02, $02, $04, $01, $00, $02, $04, $00 ; Level 20 280 | 281 | ; The number of bytes to skip when calling a tileDrawP*R* routine for a collapsing platform 282 | collapseHeight: 283 | .byte 12*0, 12*1, 12*2, 12*3, 12*4, 12*5, 12*6, 12*7 284 | 285 | conveyorDirections: 286 | .byte 2, 1, 2, 1, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2 287 | .byte 2, 2, 1, 2, 2, 1 288 | 289 | skylabXPos: 290 | .byte $01, $15, $0B, $09, $1D, $13, $11, $05 291 | .byte $1B, $19, $0D, $03 292 | 293 | willyx: 294 | .byte $02, $02, $02, $1D, $01, $0F, $02, $02 295 | .byte $01, $01, $03, $02, $1D, $1D, $02, $02 296 | .byte $01, $1D, $0E, $1B, $0F 297 | 298 | willyy: 299 | .byte $68, $68, $68, $68, $18, $18, $68, $68 300 | .byte $68, $20, $08, $68, $68, $68, $68, $68 301 | .byte $18, $68, $50, $68, $58 302 | 303 | willyStartDir: 304 | .byte 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 305 | .byte 0, 0, 1, 1, 0, 1, 0 306 | 307 | doorL: 308 | .byte <(levelLayout + $1BE) ; level 0 309 | .byte <(levelLayout + $1DE) ; level 1 310 | .byte <(levelLayout + $17E) ; level 2 311 | .byte <(levelLayout + $03E) ; level 3 312 | .byte <(levelLayout + $1CF) ; level 4 313 | .byte <(levelLayout + $01E) ; level 5 314 | .byte <(levelLayout + $1CF) ; level 6 315 | .byte <(levelLayout + $1CF) ; level 7 316 | .byte <(levelLayout + $001) ; level 8 317 | .byte <(levelLayout + $1CC) ; level 9 318 | .byte <(levelLayout + $021) ; level 10 319 | .byte <(levelLayout + $1CF) ; level 11 320 | .byte <(levelLayout + $1C1) ; level 12 321 | .byte <(levelLayout + $010) ; level 13 322 | .byte <(levelLayout + $061) ; level 14 323 | .byte <(levelLayout + $0CC) ; level 15 324 | .byte <(levelLayout + $03E) ; level 16 325 | .byte <(levelLayout + $03E) ; level 17 326 | .byte <(levelLayout + $041) ; level 18 327 | .byte <(levelLayout + $0B3) ; level 19 328 | 329 | doorH: 330 | .byte >(levelLayout + $1BE) ; level 0 331 | .byte >(levelLayout + $1DE) ; level 1 332 | .byte >(levelLayout + $17E) ; level 2 333 | .byte >(levelLayout + $03E) ; level 3 334 | .byte >(levelLayout + $1CF) ; level 4 335 | .byte >(levelLayout + $01E) ; level 5 336 | .byte >(levelLayout + $1CF) ; level 6 337 | .byte >(levelLayout + $1CF) ; level 7 338 | .byte >(levelLayout + $001) ; level 8 339 | .byte >(levelLayout + $1CC) ; level 9 340 | .byte >(levelLayout + $021) ; level 10 341 | .byte >(levelLayout + $1CF) ; level 11 342 | .byte >(levelLayout + $1C1) ; level 12 343 | .byte >(levelLayout + $010) ; level 13 344 | .byte >(levelLayout + $061) ; level 14 345 | .byte >(levelLayout + $0CC) ; level 15 346 | .byte >(levelLayout + $03E) ; level 16 347 | .byte >(levelLayout + $03E) ; level 17 348 | .byte >(levelLayout + $041) ; level 18 349 | .byte >(levelLayout + $0B3) ; level 19 350 | 351 | door_color1: 352 | .byte $01, $03, $01, $02, $03, $04, $01, $02 353 | .byte $03, $04, $00, $01, $01, $01, $01, $01 354 | .byte $01, $01, $01, $01, $01 355 | 356 | door_color2: 357 | .byte $02, $04, $04, $03, $02, $01, $01, $02 358 | .byte $03, $04, $01, $02, $02, $02, $02, $02 359 | .byte $02, $02, $02, $02, $02 360 | 361 | keyx: 362 | .byte $09, $1D, $10, $18, $1E 363 | .byte $07, $18, $1A, $03, $13 364 | .byte $06, $0F, $17, $1E, $15 365 | .byte $01, $0C, $19, $10, $1E 366 | .byte $1E, $0A, $1D, $07, $09 367 | .byte $0F, $11, $1E, $01, $0D 368 | .byte $1E, $14, $1B, $13, $1E 369 | .byte $0D, $0E, $02, $1D, $FF 370 | .byte $10, $FF, $FF, $FF, $FF 371 | .byte $15, $0E, $0C, $12, $1E 372 | .byte $18, $1E, $01, $13, $1E 373 | .byte $0F, $10, $02, $1D, $1A 374 | .byte $1A, $0A, $13, $1A, $0B 375 | .byte $17, $03, $1B, $10, $FF 376 | .byte $19, $0C, $1A, $FF, $FF 377 | .byte $1E, $0D, $01, $11, $FF 378 | .byte $18, $0F, $01, $13, $1A 379 | .byte $10, $FF, $FF, $FF, $FF 380 | .byte $1E, $01, $1E, $FF, $FF 381 | .byte $17, $1E, $0A, $0E, $13 382 | 383 | keyyL: 384 | .byte $00, $00, $20, $80, $C0 385 | .byte $20, $20, $E0, $20, $80 386 | .byte $00, $00, $00, $C0, $C0 387 | .byte $00, $20, $20, $C0, $C0 388 | .byte $20, $C0, $E0, $80, $80 389 | .byte $C0, $C0, $E0, $40, $60 390 | .byte $60, $C0, $E0, $40, $60 391 | .byte $40, $C0, $00, $A0, $E0 392 | .byte $20, $E0, $E0, $E0, $E0 393 | .byte $40, $20, $C0, $00, $20 394 | .byte $00, $20, $80, $C0, $A0 395 | .byte $60, $E0, $C0, $A0, $A0 396 | .byte $60, $C0, $20, $20, $80 397 | .byte $40, $00, $E0, $E0, $E0 398 | .byte $40, $C0, $C0, $E0, $E0 399 | .byte $40, $E0, $00, $40, $E0 400 | .byte $A0, $E0, $20, $40, $60 401 | .byte $20, $E0, $E0, $E0, $E0 402 | .byte $20, $A0, $80, $E0, $E0 403 | .byte $A0, $C0, $60, $60, $60 404 | 405 | keyyH: 406 | .byte $00, $00, $00, $00, $00 407 | .byte $00, $00, $00, $01, $01 408 | .byte $00, $00, $00, $00, $00 409 | .byte $00, $00, $00, $00, $00 410 | .byte $00, $00, $00, $01, $01 411 | .byte $00, $00, $00, $01, $01 412 | .byte $00, $00, $00, $01, $01 413 | .byte $00, $00, $01, $01, $FF 414 | .byte $00, $FF, $FF, $FF, $FF 415 | .byte $00, $00, $00, $01, $00 416 | .byte $00, $00, $00, $00, $01 417 | .byte $00, $00, $00, $01, $00 418 | .byte $00, $00, $01, $01, $01 419 | .byte $00, $01, $00, $00, $FF 420 | .byte $00, $00, $01, $FF, $FF 421 | .byte $00, $00, $00, $01, $FF 422 | .byte $00, $00, $01, $01, $01 423 | .byte $00, $FF, $FF, $FF, $FF 424 | .byte $00, $00, $01, $FF, $FF 425 | .byte $00, $00, $01, $01, $01 426 | 427 | sprites_x: 428 | .byte $08, $1D, $12, $1D, $1D, $13, $10, $12 429 | .byte $1D, $01, $07, $1D, $0C, $04, $0F, $0F 430 | .byte $06, $0E, $08, $18, $1D, $0F, $0A, $11 431 | .byte $0F, $09, $0B, $12, $0F, $0F, $0C, $10 432 | .byte $05, $0A, $14, $19, $01, $09, $0C, $08 433 | .byte $12, $0C, $0F, $0E, $0F, $0C, $03, $15 434 | .byte $1A, $01, $09, $0B, $19, $0F, $0F, $07 435 | .byte $10, $14, $12, $05, $01, $00, $00, $00 436 | .byte $0F, $11, $09, $0F, $15, $01, $09, $01 437 | .byte $12, $1A, $0C, $05, $0C, $03, $0A, $13 438 | .byte $1B, $1D, $0C, $10, $10, $10, $05, $0A 439 | .byte $14, $19, $1D, $18, $1C, $1D, $10, $05 440 | .byte $0B, $10, $01, $07, $18, $13, $13, $0F 441 | .byte $0F 442 | 443 | sprites_y: 444 | .byte $38, $68, $18, $68, $68, $68, $18, $18 445 | .byte $58, $68, $68, $08, $18, $38, $01, $68 446 | .byte $40, $40, $68, $68, $00, $08, $40, $68 447 | .byte $68, $68, $58, $38, $00, $68, $18, $50 448 | .byte $08, $08, $08, $08, $00, $38, $50, $68 449 | .byte $28, $68, $18, $38, $68, $08, $20, $30 450 | .byte $30, $08, $68, $58, $30, $00, $68, $08 451 | .byte $20, $38, $50, $08, $68, $00, $00, $00 452 | .byte $00, $68, $28, $40, $50, $18, $68, $50 453 | .byte $38, $28, $28, $68, $68, $40, $40, $30 454 | .byte $00, $08, $18, $50, $30, $68, $08, $08 455 | .byte $08, $08, $00, $18, $30, $48, $68, $40 456 | .byte $38, $50, $08, $68, $30, $28, $28, $00 457 | .byte $68 458 | 459 | sprites_min: 460 | .byte $08, $1D, $01, $0C, $1D, $01, $01, $12 461 | .byte $1D, $01, $06, $1D, $01, $04, $01, $0F 462 | .byte $06, $0E, $08, $18, $1D, $0F, $02, $11 463 | .byte $0F, $01, $0B, $12, $00, $0F, $0C, $0C 464 | .byte $05, $05, $05, $05, $01, $09, $08, $04 465 | .byte $11, $0C, $0F, $0E, $05, $02, $20, $30 466 | .byte $04, $01, $01, $0B, $19, $00, $0F, $07 467 | .byte $07, $0A, $07, $08, $01, $00, $00, $00 468 | .byte $0F, $11, $24, $24, $20, $01, $01, $01 469 | .byte $12, $19, $0C, $05, $0C, $40, $03, $01 470 | .byte $04, $1D, $0C, $0C, $0C, $0C, $05, $05 471 | .byte $05, $05, $1D, $17, $16, $17, $0D, $02 472 | .byte $30, $04, $01, $07, $28, $13, $13, $00 473 | .byte $0F 474 | 475 | sprites_max: 476 | .byte $10, $1E, $13, $1E, $1E, $14, $11, $1E 477 | .byte $1E, $0B, $10, $1E, $0D, $0D, $58, $10 478 | .byte $0E, $16, $15, $1E, $1E, $1E, $0B, $1E 479 | .byte $10, $0A, $10, $16, $00, $10, $13, $13 480 | .byte $65, $65, $65, $65, $02, $0F, $0F, $1B 481 | .byte $16, $0D, $19, $13, $14, $39, $65, $65 482 | .byte $65, $02, $0A, $10, $1D, $00, $10, $1E 483 | .byte $1E, $1B, $1E, $65, $02, $49, $39, $21 484 | .byte $10, $14, $67, $67, $69, $02, $13, $08 485 | .byte $18, $1E, $0D, $09, $1A, $67, $61, $41 486 | .byte $61, $1E, $13, $12, $12, $13, $69, $69 487 | .byte $69, $69, $1E, $1E, $1E, $1E, $1E, $67 488 | .byte $67, $51, $02, $17, $68, $14, $14, $59 489 | .byte $10 490 | 491 | sprites_speed: 492 | .byte $02, $04, $02, $02, $04, $02, $02, $02 493 | .byte $04, $02, $02, $04, $02, $02, $01, $04 494 | .byte $02, $02, $02, $02, $04, $02, $02, $02 495 | .byte $04, $02, $01, $02, $04, $04, $02, $01 496 | .byte $01, $02, $01, $02, $04, $02, $01, $02 497 | .byte $02, $04, $02, $01, $02, $02, $01, $01 498 | .byte $02, $04, $02, $01, $02, $04, $04, $02 499 | .byte $01, $02, $01, $02, $04, $03, $02, $01 500 | .byte $04, $02, $02, $01, $02, $04, $02, $02 501 | .byte $02, $01, $04, $01, $02, $02, $02, $01 502 | .byte $04, $04, $01, $01, $02, $02, $03, $02 503 | .byte $04, $01, $04, $02, $02, $01, $02, $03 504 | .byte $02, $01, $04, $02, $01, $04, $04, $02 505 | .byte $04 506 | 507 | sprites_dir: 508 | .byte $00, $00, $01, $01, $00, $01, $01, $00 509 | .byte $00, $00, $00, $00, $01, $00, $00, $00 510 | .byte $00, $01, $01, $01, $00, $00, $01, $00 511 | .byte $00, $01, $00, $00, $00, $00, $00, $00 512 | .byte $00, $00, $00, $00, $00, $00, $00, $00 513 | .byte $00, $00, $00, $00, $01, $00, $00, $00 514 | .byte $01, $00, $01, $00, $00, $00, $00, $00 515 | .byte $00, $01, $00, $00, $00, $00, $00, $00 516 | .byte $00, $00, $00, $00, $01, $00, $00, $00 517 | .byte $00, $00, $00, $00, $00, $00, $01, $00 518 | .byte $00, $00, $00, $00, $00, $01, $00, $00 519 | .byte $00, $00, $00, $00, $00, $01, $00, $00 520 | .byte $01, $00, $00, $00, $00, $00, $00, $00 521 | .byte $00 522 | 523 | sprites_bitmaps: 524 | .byte $00, $93, $09, $09, $94, $12, $12, $12 525 | .byte $95, $1A, $1A, $96, $23, $23, $22, $97 526 | .byte $2B, $2B, $2B, $2B, $98, $33, $33, $33 527 | .byte $99, $3F, $3F, $3F, $3B, $9A, $47, $47 528 | .byte $43, $43, $43, $43, $9B, $4B, $4B, $4B 529 | .byte $4B, $9C, $57, $57, $57, $53, $53, $53 530 | .byte $53, $9D, $3F, $3F, $3F, $3B, $9E, $5F 531 | .byte $5F, $5F, $5F, $5B, $9F, $63, $63, $63 532 | .byte $A0, $6F, $6B, $6B, $6B, $A1, $73, $73 533 | .byte $73, $73, $A2, $7F, $7F, $7B, $7B, $7B 534 | .byte $7B, $A3, $47, $47, $47, $47, $83, $83 535 | .byte $83, $83, $A4, $8B, $8B, $8B, $8B, $87 536 | .byte $87, $87, $A5, $8F, $5B, $A6, $A7, $11 537 | .byte $08 538 | 539 | sprites_colors: 540 | .byte $02, $00, $02, $04, $00, $01, $03, $02 541 | .byte $00, $02, $01, $00, $02, $00, $00, $00 542 | .byte $02, $03, $04, $02, $00, $04, $03, $02 543 | .byte $00, $01, $03, $04, $01, $00, $01, $04 544 | .byte $03, $01, $04, $02, $00, $02, $00, $03 545 | .byte $04, $00, $02, $01, $00, $03, $01, $02 546 | .byte $04, $00, $01, $02, $04, $01, $00, $03 547 | .byte $01, $02, $04, $00, $00, $00, $02, $04 548 | .byte $00, $04, $02, $00, $01, $00, $01, $02 549 | .byte $03, $04, $00, $00, $04, $04, $02, $00 550 | .byte $03, $00, $01, $04, $03, $02, $03, $01 551 | .byte $04, $00, $00, $00, $04, $03, $00, $00 552 | .byte $02, $04, $00, $02, $00, $00, $04, $00 553 | .byte $00 554 | 555 | sprites_class: 556 | .byte $00, $82, $00, $00, $82, $00, $00, $00 557 | .byte $82, $00, $00, $82, $00, $00, $13, $82 558 | .byte $00, $00, $00, $00, $82, $00, $00, $00 559 | .byte $82, $02, $06, $02, $2B, $82, $02, $02 560 | .byte $03, $03, $03, $03, $82, $00, $00, $00 561 | .byte $00, $82, $02, $02, $02, $03, $03, $03 562 | .byte $03, $82, $02, $06, $02, $2B, $82, $02 563 | .byte $02, $02, $02, $03, $82, $49, $49, $49 564 | .byte $82, $02, $03, $03, $03, $82, $00, $00 565 | .byte $00, $00, $82, $02, $02, $03, $03, $03 566 | .byte $03, $82, $02, $02, $02, $02, $03, $03 567 | .byte $03, $03, $82, $02, $02, $02, $02, $03 568 | .byte $03, $03, $82, $02, $03, $82, $00, $19 569 | .byte $82 570 | 571 | level_sprites_offset: 572 | .byte $00, $02, $05, $09, $0C, $10, $15, $19 573 | .byte $1E, $25, $2A, $32, $37, $3D, $41, $46 574 | .byte $4B, $52, $5B, $63, $67 575 | 576 | level_sprites_count: 577 | .byte $02, $03, $04, $03, $04, $05, $04, $05 578 | .byte $07, $05, $08, $05, $06, $04, $05, $05 579 | .byte $07, $09, $08, $04, $02 580 | -------------------------------------------------------------------------------- /src/apple2/rosystem.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; rosystem.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "RODATA" 10 | 11 | ;----------------------------------------------------------------------------- 12 | ; the lo byte for the memory at the start of every screen row 13 | rowL: 14 | .repeat $C0, Row 15 | .byte Row & $08 << 4 | Row & $C0 >> 1 | Row & $C0 >> 3 16 | .endrep 17 | 18 | ;----------------------------------------------------------------------------- 19 | ; the hi byte for the memory at the start of every screen row 20 | rowH: 21 | .repeat $C0, Row 22 | .byte >$0000 | Row & $07 << 2 | Row & $30 >> 4 23 | .endrep 24 | 25 | ;----------------------------------------------------------------------------- 26 | ; Jump table for screen tile rendering, per row - unrolled loops 27 | rowDrawL: 28 | .byte tileDrawP0R0 63 | .byte >tileDrawP0R1 64 | .byte >tileDrawP0R2 65 | .byte >tileDrawP0R3 66 | .byte >tileDrawP0R4 67 | .byte >tileDrawP0R5 68 | .byte >tileDrawP0R6 69 | .byte >tileDrawP0R7 70 | .byte >tileDrawP0R8 71 | .byte >tileDrawP0R9 72 | .byte >tileDrawP0RA 73 | .byte >tileDrawP0RB 74 | .byte >tileDrawP0RC 75 | .byte >tileDrawP0RD 76 | .byte >tileDrawP0RE 77 | .byte >tileDrawP0RF 78 | .byte >tileDrawP1R0 ; Page 1 79 | .byte >tileDrawP1R1 80 | .byte >tileDrawP1R2 81 | .byte >tileDrawP1R3 82 | .byte >tileDrawP1R4 83 | .byte >tileDrawP1R5 84 | .byte >tileDrawP1R6 85 | .byte >tileDrawP1R7 86 | .byte >tileDrawP1R8 87 | .byte >tileDrawP1R9 88 | .byte >tileDrawP1RA 89 | .byte >tileDrawP1RB 90 | .byte >tileDrawP1RC 91 | .byte >tileDrawP1RD 92 | .byte >tileDrawP1RE 93 | .byte >tileDrawP1RF 94 | 95 | ;----------------------------------------------------------------------------- 96 | mult64H: 97 | .repeat MAX_SPRITE_IFRAMES, Row 98 | .byte >(Row * 64) 99 | .endrep 100 | 101 | mult64L: 102 | .repeat MAX_SPRITE_IFRAMES, Row 103 | .byte <(Row * 64) 104 | .endrep 105 | 106 | mult32H: 107 | .repeat 15, Row 108 | .byte >(Row * 32) 109 | .endrep 110 | 111 | mult32L: 112 | .repeat 15, Row 113 | .byte <(Row * 32) 114 | .endrep 115 | 116 | mult16: 117 | .repeat 9, Row 118 | .byte Row * 16 119 | .endrep 120 | 121 | mult8: 122 | .repeat 24, Row 123 | .byte Row * 8 124 | .endrep 125 | 126 | ;----------------------------------------------------------------------------- 127 | ; color masks 128 | masksLeft: 129 | .byte %11111111 ; 00 MSB-BITS-11..1 White 130 | .byte %00101010 ; 01 0-BITS-01..0 Green 131 | .byte %10101010 ; 04 1-BITS-01..0 Orange 132 | .byte %01010101 ; 01 0-BITS-10..1 Purple 133 | .byte %11010101 ; 02 1-BITS-10..1 Blue 134 | 135 | masksRight: 136 | .byte %11111111 ; 00 MSB-BITS-11 White 137 | .byte %01010101 ; 01 0-BITS-10..1 Green 138 | .byte %11010101 ; 04 1-BITS-10..1 Orange 139 | .byte %00101010 ; 01 0-BITS-01..0 Purple 140 | .byte %10101010 ; 02 1-BITS-01..0 Blue 141 | 142 | maskGreen: 143 | .byte %00101010, %01010101 ; Green 144 | 145 | maskOrange: 146 | .byte %10101010, %11010101 ; Orange 147 | 148 | maskGreenHi: 149 | .byte %00100000, %01000000 ; Green 150 | 151 | maskNewTip: 152 | .byte %01011111, %00111111 ; White air graph tip (reversed order) 153 | 154 | ;----------------------------------------------------------------------------- 155 | ; sprite 32 to 64 byte bit-double helper table 156 | binDouble: 157 | .byte %00000000 158 | .byte %00000011 159 | .byte %00001100 160 | .byte %00001111 161 | .byte %00110000 162 | .byte %00110011 163 | .byte %00111100 164 | .byte %00111111 165 | .byte %11000000 166 | .byte %11000011 167 | .byte %11001100 168 | .byte %11001111 169 | .byte %11110000 170 | .byte %11110011 171 | .byte %11111100 172 | .byte %11111111 173 | -------------------------------------------------------------------------------- /src/apple2/rotext.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; rotext.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "RODATA" 10 | 11 | roTextLevel: 12 | .BYTE " CENTRAL CAVERN " 13 | .BYTE " THE COLD ROOM " 14 | .BYTE " THE MENAGERIE " 15 | .BYTE " ABANDONED URANIUM WORKINGS " 16 | .BYTE " EUGENE+S LAIR " 17 | .BYTE " PROCESSING PLANT " 18 | .BYTE " THE VAT " 19 | .BYTE "MINER WILLY MEETS THE KONG BEAST" 20 | .BYTE " WACKY AMOEBATRONS " 21 | .BYTE " THE ENDORIAN FOREST " 22 | .BYTE "ATTACK OF THE MUTANT TELEPHONES " 23 | .BYTE " RETURN OF THE ALIEN KONG BEAST " 24 | .BYTE " ORE REFINERY " 25 | .BYTE " SKYLAB LANDING BAY " 26 | .BYTE " THE BANK " 27 | .BYTE " THE SIXTEENTH CAVERN " 28 | .BYTE " THE WAREHOUSE " 29 | .BYTE " AMOEBATRONS+ REVENGE " 30 | .BYTE " SOLAR POWER GENERATOR " 31 | .BYTE " THE FINAL BARRIER " 32 | roTextAir: 33 | .byte "AIR " 34 | roTextScore: 35 | .byte "SCORE" 36 | roTextHighScore: 37 | .byte "HIGH" 38 | roTextGame: 39 | .byte "GAME" 40 | roTextOver: 41 | .byte "OVER" 42 | roTextPressEnter: 43 | .byte "PRESS ENTER TO START" 44 | roTextEnter := (roTextPressEnter + 6) 45 | roTextAppleIIVersion: 46 | .BYTE " APPLE II V1.1A BY " 47 | roTextStefan: 48 | .BYTE "STEFAN WESSELS, 2020" 49 | roTextIntro: 50 | .BYTE ". . . . . . . MANIC MINER . . [ BUG-BYTE LTD. 1983 . . BY " 51 | .BYTE "MATTHEW SMITH . . . Q, O * W, P = LEFT * RIGHT . . SPACE = JUMP . . M = MUSIC " 52 | .BYTE "ON/OFF . . S = IN GAME SOUND ON/OFF . . B = MONOCHROME/COLOR . . " 53 | .BYTE "C = LEVEL SCROLL MODE . . ESC = QUIT . . . GUIDE MINER WILLY THROUGH 20 LETHAL " 54 | .BYTE "CAVERNS . . . . . . ", 0 55 | roTextMono: 56 | .byte " MONO" 57 | roTextColor: 58 | .byte "COLOR" 59 | roTextCheatCode: 60 | .byte "6031769" 61 | -------------------------------------------------------------------------------- /src/apple2/sprite.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; sprite.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "CODE" 10 | 11 | ;----------------------------------------------------------------------------- 12 | ; Copy bytes from srcPtr to dstPtr 13 | ; IN: - srcPtr - start of copy 14 | ; dstPtr - start of non-overlapping destination 15 | ; size - number of bytes to copy 16 | ; Clobbers: a, y 17 | .proc spriteCopyMemSrcToDst 18 | 19 | ldy #0 20 | hiCopy: 21 | dec sizeH 22 | bmi lowCopy 23 | : 24 | lda (srcPtrL), y 25 | sta (dstPtrL), y 26 | dey 27 | bne :- 28 | inc srcPtrH 29 | inc dstPtrH 30 | bne hiCopy 31 | 32 | lowCopy: 33 | ldy sizeL 34 | dey 35 | bmi done 36 | : 37 | lda (srcPtrL), y 38 | sta (dstPtrL), y 39 | dey 40 | bpl :- 41 | 42 | done: 43 | rts 44 | 45 | .endproc 46 | 47 | ;----------------------------------------------------------------------------- 48 | ; Look up the color, and apply those masks to the frames 49 | ; IN: x = start frame 50 | ; y = num frames 51 | ; a = color index 52 | ; Clobbers: a, y 53 | .proc spriteApplyMaskToFrames 54 | 55 | instanceIdx = tmpBot + 0 56 | colMaskL = tmpBot + 1 57 | colMaskR = tmpBot + 2 58 | numFrames = tmpBot + 3 59 | 60 | sty numFrames ; save y 61 | tay ; put color index in y 62 | 63 | lda masksLeft, y ; look up the masks for the color 64 | sta colMaskL 65 | lda masksRight, y 66 | sta colMaskR 67 | 68 | lda mult64L, x ; build a pointer to the frame 69 | sta srcPtrL 70 | lda mult64H, x 71 | adc #>spriteInstances 72 | sta srcPtrH 73 | 74 | ldy numFrames ; convert frames to bytes 75 | lda mult64H, y 76 | sta sizeH 77 | lda mult64L, y 78 | sta sizeL 79 | 80 | ldy #0 ; do blocks of 256 81 | hiPass: 82 | dec sizeH ; see if there's a hi block to do 83 | bmi lowPass ; if none (remain) to do, move on 84 | : 85 | lda (srcPtrL), y ; do a left hand byte 86 | and colMaskL 87 | sta (srcPtrL), y 88 | dey 89 | lda (srcPtrL), y ; then a right hand byte 90 | and colMaskR 91 | sta (srcPtrL), y 92 | dey 93 | bne :- ; for 256 bytes 94 | inc dstPtrH ; advance the dest ptr Hi 95 | bne hiPass ; BRA 96 | 97 | lowPass: 98 | ldy sizeL ; do bytes remaining lt 256 99 | dey 100 | bmi done ; keep going till all done 101 | : 102 | lda (srcPtrL), y 103 | and colMaskL 104 | sta (srcPtrL), y 105 | dey 106 | lda (srcPtrL), y 107 | and colMaskR 108 | sta (srcPtrL), y 109 | dey 110 | bpl :- 111 | 112 | done: 113 | rts 114 | 115 | .endproc 116 | 117 | ;----------------------------------------------------------------------------- 118 | ; Copy one frame to another 119 | ; IN: 120 | ; originalFrame = tmpBot + 4 121 | ; newFrame = tmpBot + 5 122 | ; Clobbers: a, x, y 123 | .proc spriteCopyFrameToFrame 124 | 125 | originalFrame = tmpBot + 4 126 | newFrame = tmpBot + 5 127 | 128 | clc 129 | 130 | ldx originalFrame ; point srcPtr at the original frame 131 | lda mult64L, x 132 | sta srcPtrL 133 | lda mult64H, x 134 | adc #>spriteInstances 135 | sta srcPtrH 136 | 137 | ldx newFrame ; point dstPtr at the target frame 138 | lda mult64L, x 139 | sta dstPtrL 140 | lda mult64H, x 141 | adc #>spriteInstances 142 | sta dstPtrH 143 | 144 | lda #0 ; 256 or less bytes (no Hi) 145 | sta sizeH 146 | lda #64 ; just 64 bytes to copy 147 | sta sizeL 148 | jsr spriteCopyMemSrcToDst ; use copymem to copy a 64 byte frame 149 | 150 | rts 151 | 152 | .endproc 153 | 154 | ;----------------------------------------------------------------------------- 155 | ; Invert the pixels of a frame 156 | ; IN: 157 | ; x = frame number 158 | .proc spriteInvertFrame 159 | 160 | clc 161 | lda mult64L, x ; make srcPtr point at the frame 162 | sta srcPtrL 163 | lda mult64H, x 164 | adc #>spriteInstances 165 | sta srcPtrH 166 | 167 | ldy #SPRITE_BYTES - 1 ; do for a whole frame (0 based) 168 | : 169 | lda (srcPtrL), y ; get the frame byte 170 | eor #%01111111 ; invert except for the MSB 171 | sta (srcPtrL), y ; and save the byte 172 | dey ; one less byte to do 173 | bpl :- ; do for all bytes 174 | 175 | rts 176 | 177 | .endproc 178 | 179 | ;----------------------------------------------------------------------------- 180 | ; Clear all the pixels of a frame 181 | ; IN: 182 | ; x = frame number 183 | ; y = number of frames to clear 184 | .proc spriteClearFrames 185 | 186 | count = sizeL 187 | 188 | lda mult64L, y ; turn the number of frames into num bytes 189 | sta count ; save as a count 190 | 191 | clc ; and clear carry 192 | lda mult64L, x ; make srcPtr point at the 193 | sta srcPtrL 194 | lda mult64H, x 195 | adc #>spriteInstances 196 | sta srcPtrH 197 | 198 | ldy count ; how many bytes to clear 199 | dey ; make zero based 200 | lda #0 ; value to write 201 | : 202 | sta (srcPtrL), y ; write 0 to frame 203 | dey ; previous byte in frame 204 | bpl :- ; do for all, incl. zero'th byte 205 | 206 | rts 207 | 208 | .endproc 209 | 210 | ;----------------------------------------------------------------------------- 211 | ; Make an instance of the bitmap (index in a) into the spriteInstance buffer 212 | ; while expanding the bitmap from 32 byte 1bpp into 64 byte 2bpp and masking 213 | ; the instance for the required color. 214 | ; IN: 215 | ; a - bitmapIdx 216 | ; x - instanceIdx 217 | .proc spriteInstanceSpriteFrames 218 | 219 | ; count = tmpBot + 0 220 | ; spriteIdx = tmpBot + 1 221 | instanceIdx = tmpBot + 2 222 | srcIdx = tmpBot + 3 223 | dstIdx = tmpBot + 4 224 | colMaskL = tmpBot + 5 225 | colMaskR = tmpBot + 6 226 | 227 | ldy #0 228 | sty srcPtrH 229 | sty dstPtrH 230 | 231 | ldy #5 ; mult * 32 (shl 5 times) since each src frame is 32 bytes 232 | : 233 | asl ; shift the low 234 | rol srcPtrH ; and the hi, and move carry into hi if needed 235 | dey ; do for all 6 iterations 236 | bne :- 237 | 238 | adc #sprite08 241 | adc srcPtrH 242 | sta srcPtrH ; src ptr now points at the 1st frame 243 | 244 | lda spriteFramesIdx, x ; get the sprite dest frame 245 | ldy #6 ; mult * 64 (shl 6 times) since each frame is 64 bytes 246 | : 247 | asl ; shift the lo 248 | rol dstPtrH ; and the hi, and move carry into hi if needed 249 | dey ; do for all 6 iterations 250 | bne :- 251 | sta dstPtrL ; save the lo 252 | lda dstPtrH ; get the hi 253 | adc #>spriteInstances ; and make relative to the buffer 254 | sta dstPtrH ; and save the hi 255 | 256 | lda #1 ; assume 4 frames = 256 bytes 257 | sta sizeH 258 | lda spriteClass, x ; get the class 259 | bit bit1Mask ; CLASS_FOUR_FRAME is it 4 frames 260 | bne :+ ; yes, all set 261 | inc sizeH ; 8 frames = 512 bytes = 2 Hi 262 | 263 | : 264 | ldy instanceIdx ; get the instance 265 | lda spriteColor, y ; and get the color for the instance 266 | tay ; put the color index in y 267 | lda masksLeft, y ; get the color mask 268 | sta colMaskL 269 | lda masksRight, y ; do the same for the right 270 | sta colMaskR ; SQW - look at comments but save for second (inverse) buffer 271 | 272 | ldy #0 273 | sty dstIdx ; set the src and dst indices to start at 0 274 | sty srcIdx ; srcIndex moves at 1/2 of dstIndex 275 | 276 | copyFrames: 277 | ldy srcIdx ; get the source index 278 | lda (srcPtrL), y ; get a (left) src byte at the source index 279 | pha ; save so the right nibble can be expanded later 280 | lsr ; make the left nibble the low nibble 281 | lsr 282 | lsr 283 | lsr 284 | tax ; put the value in x 285 | lda binDouble, x ; look up the "pixel doubled" value 286 | ora #$80 ; and set the color msb "on" 287 | and colMaskL ; mask it with appropriate color 288 | ldy dstIdx ; get the destination offset 289 | sta (dstPtrL), y ; and save to instance destination 290 | iny ; move the destination along one 291 | pla ; get the source byte 292 | and #$0f ; mask so only right nibble 293 | tax ; put it in x 294 | lda binDouble, x ; and look up the "doubled" pixel values 295 | sec ; set carry so a rotate will be equiv to or #$80 296 | ror ; and rotate, making it a "right" byte 297 | and colMaskR ; mask it with appropriate color 298 | sta (dstPtrL), y ; and save to instance destination 299 | iny ; move the dest index along again 300 | sty dstIdx ; and save the index 301 | bne :+ ; if the index rolled over 302 | inc dstPtrH ; move the hi byte along 303 | dec sizeH ; check if moved all required 4 * 32 src byte blocks 304 | beq done 305 | : 306 | inc srcIdx ; move the src index along 307 | bne copyFrames ; do max 256 bytes = 8 * 32 byte src frames 308 | 309 | done: 310 | rts 311 | 312 | .endproc 313 | 314 | ;----------------------------------------------------------------------------- 315 | ; Copy the door frame, invert the copy, mask both and combine 316 | .proc spriteDoorSetup 317 | 318 | originalFrame = tmpBot + 4 319 | newFrame = tmpBot + 5 320 | 321 | ldx numSprites ; numSprites is the door sprite index 322 | lda spriteFramesIdx, x 323 | tax 324 | stx originalFrame 325 | inx 326 | stx newFrame 327 | jsr spriteCopyFrameToFrame ; make a copy y = instanceIdx, a = srcFrame, x = dstFrame 328 | 329 | ldx newFrame 330 | jsr spriteInvertFrame ; invert the instance frame bits (leave msb alone) 331 | 332 | ldx currLevel 333 | lda door_color1, x 334 | beq :+ 335 | ldx originalFrame 336 | ldy #1 337 | jsr spriteApplyMaskToFrames 338 | 339 | ldx currLevel 340 | lda door_color2, x 341 | beq :+ 342 | ldx newFrame 343 | ldy #1 344 | jmp spriteApplyMaskToFrames 345 | 346 | : 347 | rts 348 | 349 | .endproc 350 | 351 | ;----------------------------------------------------------------------------- 352 | ; Make 4 copies of Eugene frame 0 into frames 1-4 and apply a different 353 | ; color mask to the additional frames 354 | .proc spriteEugeneSetup 355 | 356 | originalFrame = tmpBot + 4 357 | newFrame = tmpBot + 5 358 | count = tmpBot + 6 359 | 360 | ldx numSprites 361 | dex ; eugene is sprite before the door 362 | 363 | lda spriteFramesIdx, x ; get the frame where eugene is 364 | tax ; put in x 365 | stx originalFrame ; and call it the original 366 | inx ; and go to the next frame 367 | stx newFrame ; and call it the new frame 368 | ldx #3 ; and set the loop count to 3 369 | stx count 370 | : 371 | jsr spriteCopyFrameToFrame ; make a copy of eugene, x = ori, y = new 372 | 373 | lda count ; get the count in a (as a color mask index) 374 | ldx newFrame ; the frame in x 375 | ldy #1 ; and the number of frames to process in Y 376 | jsr spriteApplyMaskToFrames ; mask the new eugene to the "count" color 377 | 378 | inc newFrame ; go up a frame 379 | dec count ; and dec the loop counter 380 | bne :- ; and do for the number of loops (3) 381 | 382 | rts ; there are now 4 eugenes, White, Green, Orange & Purple 383 | 384 | .endproc 385 | -------------------------------------------------------------------------------- /src/apple2/text.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; text.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "CODE" 10 | 11 | ;----------------------------------------------------------------------------- 12 | .proc textSHowText 13 | 14 | xpos = srcPtrL 15 | ypos = srcPtrH 16 | rows = sizeH 17 | strIndex = sizeL 18 | color = dstPtrL ; and H 19 | fontL = charGfx + 1 20 | fontH = charGfx + 2 21 | 22 | stx xpos 23 | sty ypos 24 | 25 | loop: 26 | lda #0 27 | sta fontH ; init the pointer hi to 0 for later mult rol's 28 | ldx strIndex ; get the index into the string 29 | 30 | read: 31 | lda PLACEHOLDER, x ; get the character in the string 32 | sec 33 | sbc #32 ; the font only starts at space (char 32 or $20) 34 | beq :+ 35 | sbc #9 ; gap after space 36 | asl ; mult by 16 as that's how wide a char is in bytes 37 | asl 38 | rol fontH 39 | asl 40 | rol fontH 41 | asl 42 | rol fontH ; srcPtr now points at the char but $0000 based 43 | : 44 | adc #font 48 | sta fontH ; srcPtr now point at the actual character memory 49 | 50 | lda #8 ; font is 8 rows high 51 | sta rows 52 | ldy ypos ; start each character on the same line 53 | 54 | iLoop: 55 | lda strIndex ; strIndex is also col offset from xpos 56 | asl ; but font is 2 cols wide 57 | adc xpos ; add the xpos to get screen col 58 | adc rowL, y ; and add the row 59 | sta write + 1 ; lo byte of where to write to screen buffer 60 | lda currPageH ; get the hi page 61 | adc rowH, y ; add the row 62 | sta write + 2 ; and complete the screen buffer write address 63 | 64 | ldx #1 ; set up for copying 2 font bytes (1 char) 65 | charGfx: 66 | lda PLACEHOLDER, x ; get the font 67 | eorMask: 68 | eor #$00 ; eor to invert if necessary 69 | and color, x ; and to get the color needed 70 | write: 71 | sta PLACEHOLDER, x ; and write to screen memory 72 | dex ; go left a byte 73 | bpl charGfx ; and repeat to do 2 bytes 74 | dec rows ; done one of the 8 rows needed 75 | beq nextChar ; repeat for all 8 rows, then done 76 | iny ; more rows - next row in y 77 | lda fontL ; move along 2 in the font 78 | adc #2 79 | sta fontL 80 | bcc iLoop 81 | inc fontH 82 | bcs iLoop ; BRA 83 | 84 | nextChar: 85 | dec strIndex ; string done from the back, so move left in string 86 | bpl loop ; if not all of string done, loop 87 | 88 | done: 89 | rts 90 | 91 | .endproc 92 | 93 | ;----------------------------------------------------------------------------- 94 | ; Macro that takes a lo and hi for the text with optional color and invert wanted 95 | ; sets up what's needed to call textShow which does the printing 96 | .macro printXYlh xpos, ypos, textL, textH, len, colorMask, inverse 97 | 98 | .local color, strIndex 99 | 100 | color = dstPtrL ; textShow expects color masks in dstPtr(L and H) 101 | strIndex = sizeL 102 | 103 | .ifblank colorMask 104 | lda #$ff ; no color (white) is a mask of $ff left and right 105 | sta color 106 | sta color + 1 107 | .else 108 | ldx colorMask ; this is an index into mask[Left|Right] 109 | lda masksLeft, x 110 | sta color 111 | lda masksRight, x 112 | sta color + 1 113 | .endif 114 | 115 | .ifblank inverse 116 | lda #0 ; eor o is not inverse 117 | .else 118 | lda #$7f ; eor $7f inverts the color (leave MSB) 119 | .endif 120 | 121 | sta textSHowText::eorMask + 1 ; set the eor in the code 122 | 123 | lda textL ; set the string pointer in the code 124 | sta textSHowText::read + 1 125 | lda textH 126 | sta textSHowText::read + 2 127 | ldx len ; 0 based 128 | stx strIndex 129 | lda xpos ; and x/y coords in x and y registers 130 | asl 131 | tax 132 | ldy ypos 133 | jsr textSHowText ; print that string 134 | 135 | .endmacro 136 | 137 | ;----------------------------------------------------------------------------- 138 | ; macro to take text address and split it into lo and hi (shorthand) 139 | .macro printXY xpos, ypos, text, len, colorMask, inverse 140 | printXYlh xpos, ypos, #text, len, colorMask, inverse 141 | .endmacro 142 | 143 | ;----------------------------------------------------------------------------- 144 | ; Build a memory cache of the level name since this is re-drawn much more - 145 | ; every time the level scrolls left or right and copying from a cache is 146 | ; much faster than doing a text print 147 | .proc textSetLevelText 148 | 149 | lda #0 150 | sta sizeL ; index into string 151 | sta read + 2 ; hi byte of string address 152 | 153 | lda currLevel ; start with the level 154 | asl ; multiply by 32 155 | asl 156 | asl 157 | asl 158 | rol read + 2 159 | asl 160 | rol read + 2 161 | adc #roTextLevel 164 | adc read + 2 165 | sta read + 2 ; read + 1 as a ptr now points at the string for this level 166 | 167 | lda #levelNameGfx0 170 | sta write + 2 171 | 172 | lda #32 ; These strings are 32 characters wide 173 | sta dstPtrL 174 | 175 | loop: 176 | lda #0 177 | sta srcPtrH ; init the pointer hi to 0 for later mult rol's 178 | ldx sizeL ; get the index into the string 179 | read: 180 | lda PLACEHOLDER, x ; get the character in the string 181 | sec 182 | sbc #32 ; the font only starts at space (char 32 or $20) 183 | beq :+ ; gap after space 184 | sbc #9 185 | asl ; mult by 16 as that's how wide a char is in bytes 186 | asl 187 | rol srcPtrH 188 | asl 189 | rol srcPtrH 190 | asl 191 | rol srcPtrH ; srcPtr now points at the char but $0000 based 192 | : 193 | adc #font 197 | sta srcPtrH ; srcPtr now point at the actual character memory 198 | 199 | lda #8 ; copy 8 rows / character 200 | sta sizeH 201 | ldy #1 ; start at the right hand side character 202 | lrLoop: 203 | ldx #1 ; load x for the right hand side as well 204 | copyLoop: 205 | lda (srcPtrL), y ; get the line pixel 206 | eor #$7f ; invert 207 | maskName: 208 | and maskGreen, x ; mask for color (always green but left vs right hand side) 209 | write: 210 | sta PLACEHOLDER, x ; store to the cache 211 | dey ; back up a byte 212 | dex 213 | bpl copyLoop ; do 2 bytes, right and left hand 214 | lda write + 1 ; get the cache ptr low 215 | clc 216 | step: 217 | adc #64 ; step to the next line - 64 bytes along 218 | sta write + 1 ; update the pointer 219 | bcc :+ 220 | inc write + 2 ; if it wrapped, update the hi byte 221 | : 222 | iny ; y is now at minus 1 the last byte written 223 | iny ; so advance it by 4 to get to the next right hand side byte 224 | iny 225 | iny 226 | dec sizeH ; done one line 227 | bne lrLoop ; if any lines left, keep going 228 | 229 | sec 230 | lda write + 1 231 | sbc #<((64*8)-2) 232 | sta write + 1 233 | lda write + 2 234 | sbc #>((64*8)-2) 235 | sta write + 2 236 | clc 237 | 238 | inc sizeL ; move to the next character in the string 239 | dec dstPtrL ; see if 32 characters were done 240 | bne loop ; if not, keep going 241 | 242 | done: 243 | rts 244 | 245 | .endproc 246 | 247 | ;----------------------------------------------------------------------------- 248 | ; x has digit (5 means 1's, 4 means 10's, etc) 249 | ; a is the number to add 0..9 250 | .proc textAddScore 251 | 252 | clc 253 | adc score, x ; get the current digit 254 | cmp #'9'+1 ; has it rolled over 255 | bcc okay ; then simply increment 256 | sec 257 | sbc #10 258 | sta score, x ; and save over the 9 259 | lda #1 260 | dex ; previous digit 261 | bmi over ; if it rolls over 999999 262 | cpx #1 ; if the digit is now the 010000 (1) 263 | bne textAddScore ; no, then work with this digit 264 | ldy lives 265 | cpy #9 ; max out at 9 lives (keeps cheat boot on-screen) 266 | bcs textAddScore 267 | inc lives ; yes, then add a life 268 | bne textAddScore ; and then work with this digit 269 | okay: 270 | sta score, x ; and store it 271 | over: 272 | lda #UI_COMPONENT_SCORE ; mark the score texts as needing an update 273 | jmp uiUpdateComponent 274 | 275 | done: 276 | rts 277 | 278 | .endproc 279 | 280 | ;----------------------------------------------------------------------------- 281 | .proc textCheckHighScore 282 | 283 | ldx #0 ; start at the most significant digit 284 | : 285 | lda score, x ; get the score 286 | cmp highScore, x ; compare to the high score 287 | bcc done ; if smaller then highscore gt score 288 | bne newHigh ; if ne then highscore gt score 289 | inx ; digits equal so check next digit 290 | cpx #6 ; compare x to max digits (+ 1) 291 | bcc :- ; x is 5 or less, keep checking digits 292 | 293 | done: 294 | rts 295 | 296 | newHigh: 297 | ldx #5 ; copy the 6 score digits over the highscore digits 298 | : 299 | lda score, x 300 | sta highScore, x 301 | dex 302 | bpl :- 303 | lda #UI_COMPONENT_HIGHSCORE 304 | jmp uiUpdateComponent 305 | 306 | .endproc 307 | 308 | ;----------------------------------------------------------------------------- 309 | .proc textColorCycle 310 | 311 | color = tmpBot + 1 312 | xPos = tmpBot + 2 313 | yPos = tmpBot + 3 314 | textL = tmpBot + 4 315 | textH = tmpBot + 5 316 | len = tmpBot + 6 317 | 318 | prntLoop: 319 | printXYlh xPos, yPos, textL, textH, #0, color 320 | 321 | dec len ; one less character to color 322 | beq done ; all characters done? 323 | inc xPos ; move to the next character on screen 324 | inc textL ; and move to the next character in the sting 325 | bne :+ ; did the string wrap a buffer 326 | inc textH ; yes, up the Hi 327 | : 328 | dec color ; prev color 329 | bpl :+ ; still ge 0 330 | lda #4 ; no, wrap to index 4 331 | sta color ; and save that as the color 332 | : 333 | jmp prntLoop ;and print this character 334 | 335 | done: 336 | rts 337 | 338 | .endproc 339 | -------------------------------------------------------------------------------- /src/apple2/tiles.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; tiles.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "CODE" 10 | 11 | ;----------------------------------------------------------------------------- 12 | .proc tilesPrepForLevel 13 | 14 | jsr tilesMakeInstances ; copy tile data to instance area 15 | 16 | lda monochrome ; see if the game should render for monochrome 17 | beq color ; no, apply all masks 18 | mono: 19 | ldy #(DATA_COLLAPSE / $10) - 1 ; apply color mask only to the conveyor tile 20 | ldx #((DATA_COLLAPSE / $10) -1) * TILE_BYTES 21 | lda #1 22 | bne mask 23 | color: 24 | ldy #0 25 | ldx #0 26 | lda #TILES_PER_LEVEL 27 | 28 | mask: 29 | jsr tilesColorInstances ; color instance tiles by applying appropriate color masks 30 | jsr tilesPrepKeysTiles ; make animated keys from the key tile 31 | jmp tilesPrepConveyorTiles ; Make rotated conveyor tiles into conveyorAnimTiles 32 | 33 | .endproc 34 | 35 | ;----------------------------------------------------------------------------- 36 | .proc tilesMakeInstances 37 | 38 | numTiles = sizeL 39 | iLevelTile = sizeH 40 | iWrite = dstPtrL 41 | tileMemL = srcPtrL 42 | tileMemH = srcPtrH 43 | 44 | lda #TILES_PER_LEVEL ; for all tiles used in a level 45 | sta numTiles 46 | 47 | lda #TILE_BYTES - 1 ; start at the end of tile memory 48 | sta iWrite 49 | 50 | ldx currLevel ; find index into tile table 51 | lda mult8, x ; 8 tiles per level 52 | sta iLevelTile ; gives entry for the 1st tile 53 | 54 | loop: 55 | lda #0 ; set the hi for reading tile memory to 0 56 | sta tileMemH 57 | 58 | ldx iLevelTile ; get the index into the table 59 | lda levelTiles, x ; and get the id for the tile from the table 60 | 61 | asl ; mult tile offset * 16 (width of a tile) 62 | asl 63 | asl 64 | rol tileMemH 65 | asl 66 | rol tileMemH ; offset of tile start (from background) now in tileMem ptr 67 | adc #tiles 70 | adc tileMemH 71 | sta tileMemH ; tileMem pointer now points at memory for the tile 72 | 73 | ldy #TILE_BYTES - 1 ; 0 based, copy all the bytes of the tile 74 | ldx iWrite 75 | 76 | : 77 | lda (tileMemL), y ; get the tile byte 78 | sta tilesInstances, x ; and save it to instance 0 79 | dex ; back up the tile write pointer 80 | dey ; and completed one byte 81 | bpl :- ; do for all bytes of a tile 82 | 83 | dec numTiles ; done a tile 84 | beq copyDone ; see if all tiles done 85 | 86 | lda iWrite ; more tiles to do, adjust the tile write ptr 87 | clc 88 | adc #TILE_BYTES ; by moving it to the end of the next tile 89 | sta iWrite 90 | inc iLevelTile ; and advance the index into the table to the next tile 91 | bne loop ; BRA to do the next tile 92 | 93 | copyDone: 94 | rts 95 | 96 | .endproc 97 | 98 | ;----------------------------------------------------------------------------- 99 | ; Color tile instances 100 | ; IN: a - number of tiles to color 101 | ; y - index of first tile to color 102 | ; x - write address of first tile data (16 * y) 103 | .proc tilesColorInstances 104 | 105 | numTiles = sizeL 106 | iLevelTile = sizeH 107 | colMaskL = dstPtrL + 0 108 | colMaskR = dstPtrL + 1 109 | tileMemL = srcPtrL 110 | tileMemH = srcPtrH 111 | 112 | sta numTiles ; save the register parameters 113 | sty iLevelTile 114 | 115 | ldy currLevel ; get the offset of the tiles in the level tile table 116 | lda mult8, y 117 | clc 118 | adc iLevelTile ; and add the parameter offset (0 for color and 3 for mono) 119 | sta iLevelTile 120 | 121 | loop: 122 | ldy iLevelTile ; get the index 123 | lda levelMasks, y ; and extract the masks for this tile 124 | tay 125 | lda masksLeft, y 126 | sta colMaskL 127 | lda masksRight, y 128 | sta colMaskR 129 | 130 | ldy #(TILE_BYTES / 2) ; 2 masks at one time, so bytes / 2 131 | : 132 | lda colMaskL ; instance 0 mask the left side with a left mask 133 | and tilesInstances, x 134 | sta tilesInstances, x 135 | 136 | inx ; next byte 137 | 138 | lda colMaskR ; instance 0 mask the right side with a right mask 139 | and tilesInstances, x 140 | sta tilesInstances, x 141 | 142 | inx ; next byte 143 | 144 | dey ; one more column done 145 | bne :- ; keep going till all columns done 146 | 147 | inc iLevelTile ; go to the next table entry 148 | dec numTiles ; done one more tile 149 | bne loop ; keep going till all tiles done 150 | 151 | rts 152 | 153 | .endproc 154 | 155 | 156 | ;----------------------------------------------------------------------------- 157 | .proc tilesPrepKeysTiles 158 | 159 | keyByte = tmpBot + 0 160 | 161 | lda #4 ; init the key animation to frame 4 162 | sta keysFrameNum 163 | 164 | ldy #0 ; start at the 1st key byte 165 | 166 | maskLoop: 167 | lda tilesInstances + DATA_KEY - TILE_BYTES, y 168 | sta keyByte ; get a byte and save it 169 | 170 | clc 171 | ldx #4 ; make 4 color variations 172 | colorLoopLeft: 173 | lda keyByte ; start with the white key byte 174 | and masksLeft, x ; mask it for left 175 | sta keyAnimTiles, y ; and save it to the first key frame instance 0 176 | 177 | tya ; move y along to the next key instance 178 | adc #TILE_BYTES 179 | tay 180 | dex ; and do the next color for that instance 181 | bne colorLoopLeft ; repeat for all 4 color frames 182 | tya ; move Y back 63 bytes, to the next white key byte 183 | sec 184 | sbc #63 185 | tay 186 | 187 | lda tilesInstances + DATA_KEY - TILE_BYTES, y 188 | sta keyByte ; get the second (right hand byte) 189 | 190 | clc 191 | ldx #4 ; repeat the above but swap color masks 192 | colorLoopRight: 193 | lda keyByte 194 | and masksRight, x 195 | sta keyAnimTiles, y 196 | 197 | tya 198 | adc #TILE_BYTES 199 | tay 200 | dex 201 | bne colorLoopRight 202 | 203 | tya 204 | sec 205 | sbc #63 206 | tay ; y is now pointing at the next left byte 207 | cpy #TILE_BYTES ; see if the whole key has been processes 208 | bne maskLoop 209 | 210 | rts 211 | 212 | .endproc 213 | 214 | ;----------------------------------------------------------------------------- 215 | ; Makes animated copies of the conveyor tile. Instead of making full tile 216 | ; copies, this should really just do the two lines affected (so use 1 tile 217 | ; of memory, not 7 * TILE_BYTES) 218 | .proc tilesPrepConveyorTiles 219 | 220 | frame = sizeL 221 | count = sizeH 222 | dir = tmpBot + 0 223 | carry = tmpBot + 1 224 | 225 | lda #6 ; init the conveyor frame counter 226 | sta conveyorFrameNum 227 | 228 | ldx currLevel 229 | lda conveyorDirections, x ; get a local copy of the conveyor direction 230 | sta dir 231 | 232 | clc ; point srcPtr at the conveyor tile 233 | lda #DATA_CONVEYOR - TILE_BYTES 234 | sta srcPtrL 235 | lda #>tilesInstances 236 | sta srcPtrH 237 | 238 | lda #conveyorAnimTiles 241 | sta dstPtrH 242 | 243 | ldy #TILE_BYTES - 1 ; copy the tile to the animated tiles 244 | : 245 | lda (srcPtrL), y 246 | and #$7F ; clear the MSB, will be fixed later 247 | sta (dstPtrL), y 248 | dey 249 | bpl :- 250 | 251 | ldy #CONVEYOR_FRAMES - 1 ; set a counter for how many frames to process 252 | 253 | processTile: 254 | sty frame 255 | 256 | clc ; move srcPtr to dstPtr (the new src) 257 | lda dstPtrL ; and move dstPtr to the next frame to animate 258 | sta srcPtrL 259 | adc #TILE_BYTES 260 | sta dstPtrL 261 | lda dstPtrH 262 | sta srcPtrH 263 | bcc :+ 264 | inc dstPtrH 265 | : 266 | ldy #TILE_BYTES - 1 ; process a tile's bytes (0 based) 267 | : 268 | lda (srcPtrL), y ; copy the source 269 | sta (dstPtrL), y ; save to the dest 270 | dey ; copy the whole tile 271 | bpl :- 272 | 273 | lda dir ; different algorithm for each direction 274 | cmp #2 275 | beq left 276 | 277 | right: 278 | ldy #0 ; top row 279 | jsr shiftLeft ; move "left" which is "wrong", the processing is going 0..CONVEYOR_FRAMES 280 | ldy #4 ; 3rd row down, 0 based (2) * 2 byes/row is 4 281 | jsr shiftRight ; but the animation plays back CONVEYOR_FRAMES..0 282 | jmp nextFrame ; so this is all reversed 283 | 284 | left: 285 | ldy #0 286 | jsr shiftRight 287 | ldy #4 288 | jsr shiftLeft 289 | 290 | nextFrame: 291 | ldy frame 292 | dey ; another frame processed 293 | bne processTile ; have all tiles been animated (0 is original the rest shifted) 294 | 295 | jmp finalFix ; fix the MSB 296 | 297 | shiftRight: 298 | lda (dstPtrL), y ; left byte scroll right (apple pixels are reversed, so asl) 299 | asl ; shift msb away, bit 0 clear 300 | asl ; shift 1/2 a pixel, bit 1 and 0 clear 301 | sta (dstPtrL), y ; save left shifted. carry has a pixel bit that needs to move 302 | 303 | iny 304 | lda (dstPtrL), y 305 | rol ; shift right byte once, carry goes in, MSB bit out in carry 306 | asl ; shift second time, bit 0 is now a zero, carry bit needs moving left 307 | sta (dstPtrL), y 308 | 309 | dey 310 | lda #0 ; start fresh 311 | rol ; move carry into bit 0 312 | asl ; carry into bit 1, carry clear, bit 0 now 0 313 | ora (dstPtrL), y ; add bit 1 to the left byte 314 | sta (dstPtrL), y ; save the left byte 315 | and #$80 ; extract bit 8 left that should be in right bit0 316 | asl ; put bit 8 in carry and acc now clear 317 | rol ; put bit 8 in bit 0 318 | iny 319 | ora (dstPtrL), y ; add it to the right byte 320 | sta (dstPtrL), y ; store the right byte 321 | and #$80 ; extract bit 8 right which should be bit 0 left 322 | asl ; move into carry 323 | rol ; move carry into bit 0, carry now clear 324 | dey 325 | ora (dstPtrL), y ; add to bit0 of left byte 326 | sta (dstPtrL), y ; and save 327 | 328 | rts 329 | 330 | shiftLeft: 331 | ldx #1 332 | lda (dstPtrL), y ; get left byte 333 | : 334 | lsr ; move bit out 335 | sta (dstPtrL), y ; save byte 336 | 337 | lda #0 ; move bit in for next byte to bit 6 338 | ror 339 | lsr 340 | sta carry 341 | iny 342 | lda (dstPtrL), y 343 | ror ; move right byte one over (carry needs to go left now) 344 | ora carry ; add bit from left 345 | sta (dstPtrL), y ; save 346 | lda #0 ; start fresh 347 | ror ; move carry to bit 6 348 | lsr 349 | dey 350 | ora (dstPtrL), y ; add to left byte 351 | dex 352 | bpl :- 353 | sta (dstPtrL), y ; save 354 | 355 | rts 356 | 357 | finalFix: 358 | ldy #(CONVEYOR_FRAMES * TILE_BYTES) - 1 359 | lda tilesInstances + (DATA_CONVEYOR - TILE_BYTES) 360 | and #$80 ; load a byte from original 361 | beq clear ; and see if color bit is set 362 | : 363 | lda #$80 ; if set, set color on for all frames 364 | ora conveyorAnimTiles, y 365 | sta conveyorAnimTiles, y 366 | dey 367 | bpl :- 368 | bmi done 369 | 370 | clear: 371 | lda #$7f ; if not set, make sure color isn't on for all 372 | and conveyorAnimTiles, y 373 | sta conveyorAnimTiles, y 374 | dey 375 | bpl clear 376 | 377 | done: 378 | rts 379 | 380 | .endproc 381 | 382 | ;----------------------------------------------------------------------------- 383 | ; Copy a pre-animated key tile into the tilesInstances so it looks as 384 | ; though the key is animating 385 | .proc tilesAnimateKeys 386 | 387 | ldx keysFrameNum ; get the frame 388 | dex ; step 389 | bpl :+ ; underflow 390 | ldx #3 ; reset - frames are 0 to 6, but go to render + 1 391 | : 392 | stx keysFrameNum ; save the new frame 393 | inx ; go one frame past 394 | lda mult16, x ; get the byte offset to the frame 395 | tax ; put it in x 396 | dex ; and go to the last byte of the frame needed 397 | 398 | ldy #TILE_BYTES - 1 ; set y to copy a whole tile 399 | 400 | : 401 | lda keyAnimTiles, x ; read the frame and write to key tile 402 | sta tilesInstances + DATA_KEY - TILE_BYTES, y 403 | dex 404 | dey 405 | bpl :- 406 | 407 | rts 408 | 409 | .endproc 410 | 411 | ;----------------------------------------------------------------------------- 412 | ; Copy a pre-animated conveyor lines over the lines in the conveyor frame tile, 413 | ; in the tilesInstances area 414 | .proc tilesAnimateConveyor 415 | 416 | ldx conveyorFrameNum ; get the frame 417 | dex ; step 418 | bpl :+ ; underflow 419 | ldx #6 ; reset - frames are 0 to 6, but go to render + 1 420 | : 421 | stx conveyorFrameNum ; save the new frame 422 | lda mult16, x ; get the byte offset to the frame 423 | tax ; put it in x 424 | 425 | lda conveyorAnimTiles, x ; copy the 4 animated bytes for 426 | sta tilesInstances + DATA_CONVEYOR - TILE_BYTES 427 | 428 | inx 429 | lda conveyorAnimTiles, x ; instance 0 over 430 | sta tilesInstances + DATA_CONVEYOR - TILE_BYTES + 1 431 | 432 | inx ; to the tile area 0 433 | inx 434 | inx 435 | lda conveyorAnimTiles, x 436 | sta tilesInstances + DATA_CONVEYOR - TILE_BYTES + 4 437 | 438 | inx 439 | lda conveyorAnimTiles, x 440 | sta tilesInstances + DATA_CONVEYOR - TILE_BYTES + 5 441 | 442 | rts 443 | 444 | .endproc 445 | -------------------------------------------------------------------------------- /src/apple2/ui.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; ui.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "CODE" 10 | 11 | ;----------------------------------------------------------------------------- 12 | .proc uiUpdateComponent 13 | 14 | ora uiComponent 15 | sta uiComponent 16 | lda #2 17 | sta updateUICount 18 | 19 | rts 20 | 21 | .endproc 22 | 23 | ;----------------------------------------------------------------------------- 24 | .proc uiUpdate 25 | 26 | lda uiComponent 27 | 28 | bit bit0Mask ; UI_COMPONENT_NAME 29 | beq :+ 30 | jsr screenDrawLevelName 31 | lda uiComponent 32 | : 33 | bit bit1Mask ; UI_COMPONENT_AIR_NAME 34 | beq :+ 35 | printXY #0, #(17*8), roTextAir, #4, #2, 1 36 | jsr screenDrawAirFrame 37 | lda uiComponent 38 | : 39 | bit bit2Mask ; UI_COMPONENT_AIR 40 | beq :+ 41 | jsr screenDrawAirRemaining 42 | lda uiComponent 43 | : 44 | bit bit3Mask ; UI_COMPONENT_SCORE_NAME 45 | beq :+ 46 | printXY #4, #(19*8), roTextHighScore, #3 47 | printXY #11, #(19*8), roTextScore, #4 48 | lda uiComponent 49 | : 50 | bit bit4Mask ; UI_COMPONENT_SCORE 51 | beq :+ 52 | printXY #11, #(20*8), score, #5 53 | lda uiComponent 54 | : 55 | bit bit5Mask ; UI_COMPONENT_LIVES 56 | beq :+ 57 | jsr screenDrawLives 58 | lda uiComponent 59 | : 60 | bit bit6Mask ; UI_COMPONENT_HIGHSCORE 61 | beq :+ 62 | printXY #3, #(20*8), highScore, #5 63 | : 64 | dec updateUICount 65 | bne :+ 66 | lda #0 67 | sta uiComponent 68 | : 69 | rts 70 | 71 | .endproc 72 | 73 | ;----------------------------------------------------------------------------- 74 | ; 28209 cycle delay from start till after rts 75 | .proc uiDelay 76 | 77 | ldy #$80 78 | ySet: 79 | ldx #0 80 | : 81 | dex 82 | bne :- 83 | dey 84 | bne :- 85 | 86 | rts 87 | 88 | .endproc 89 | 90 | ;----------------------------------------------------------------------------- 91 | ; This routine shows the MANIC and MINER text on screen. 92 | ; the macro uiShowMMTextXY puts specific values appropriate for each right 93 | ; into the routine (such as the address of the character width array) 94 | ; The Manic & Miner words are stored in a "compressed" form, this also decodes 95 | ; that. Each letter "pixel" is stored as a bit. 96 | .proc uiShowMMText 97 | 98 | row = srcPtrL 99 | col = srcPtrH 100 | offset = dstPtrL 101 | index = dstPtrH 102 | colMaskL = sizeL 103 | colMaskR = sizeH 104 | width = tmpBot + 0 105 | height = tmpBot + 1 106 | dataByte = tmpBot + 2 107 | dr = tmpBot + 3 108 | dc = tmpBot + 4 109 | dw = tmpBot + 5 110 | pr = tmpBot + 6 111 | 112 | sty row 113 | stx col 114 | lda #0 115 | sta offset 116 | sta index 117 | 118 | strLoop: 119 | ldx index 120 | colorLoc: 121 | lda PLACEHOLDER, x ; unpack the color masks 122 | tay 123 | lda masksLeft, y 124 | sta colMaskL 125 | lda masksRight, y 126 | sta colMaskR 127 | lda #6 ; the text is 6 rows high 128 | sta height 129 | lda row 130 | sta dr ; set the working counter to the row 131 | 132 | charLoop: 133 | lda col 134 | sta dc ; set the working counter to the column 135 | ldx index ; get the index to the character in the string 136 | widthLoc: 137 | lda PLACEHOLDER, x ; get the width of the character 138 | sta width ; save 139 | sta dw ; and init the working width 140 | ldx offset ; the offset is into the encoded data 141 | dataLoc: 142 | lda PLACEHOLDER, x ; get the next encoded character 143 | inc offset ; and move the index 144 | sta dataByte ; save the character 145 | colLoop: 146 | lda dataByte ; load the character 147 | asl ; shift out the MSB to get the bit 148 | sta dataByte ; save the new shifted encoded character 149 | bcc skipPlot ; if bit was 0, blank, nothing to do 150 | lda #8 ; 1-bit to be drawn. "pixel" is 8 pixel-rows high 151 | sta pr ; save row draw counter 152 | lda dr ; load the screen draw row where drawing should happen 153 | asl ; * 8 154 | asl 155 | asl 156 | tay ; store the row in Y 157 | lda dc ; get the column 158 | and #1 ; and see if the column is odd or even (for color mask) 159 | tax 160 | : 161 | lda dc ; start with the column 162 | adc rowL, y ; and calculate the hi-res row/col address 163 | sta write + 1 164 | lda rowH, y 165 | adc currPageH ; add the page to draw to 166 | sta write + 2 167 | lda #$ff ; assume all bits on 168 | and colMaskL, x ; but then mask to get appropriate color 169 | write: 170 | sta PLACEHOLDER ; write to the screen 171 | iny ; next hi-res pixel-row 172 | dec pr ; and one less row to draw 173 | bne :- ; do all 8 rows 174 | 175 | skipPlot: 176 | inc dc ; go to the next column 177 | dec dw ; one less to do in the width of the character 178 | bpl colLoop ; if not done the whole character, keep going 179 | inc dr ; move down to the next row of the character 180 | dec height ; one less row to do 181 | bpl charLoop ; keep going till the whole character height done 182 | lda width ; move the draw col by the width of the character just drawn 183 | clc 184 | adc col 185 | sta col 186 | inc index ; and move the string index to the next character 187 | lda index 188 | cmp #6 189 | bcc strLoop ; if not all 5 characters (MANIC or MINER) done, keep going 190 | 191 | rts 192 | 193 | .endproc 194 | 195 | ;----------------------------------------------------------------------------- 196 | ; Prep uiShowMMText internal variables with data relevant for the 197 | ; specific text the macro is called with 198 | .macro uiShowMMTextXY column, row, data, widths, colors 199 | 200 | lda #data 203 | sta uiShowMMText::dataLoc + 2 204 | lda #widths 207 | sta uiShowMMText::widthLoc + 2 208 | lda #colors 211 | sta uiShowMMText::colorLoc + 2 212 | ldx column ; screen location passed in x/y 213 | ldy row 214 | jsr uiShowMMText ; show the word on-screen 215 | 216 | .endmacro 217 | 218 | ;----------------------------------------------------------------------------- 219 | .proc uiTitleScreen 220 | 221 | scrollPtrL = 0 ;tmpBot + 0 222 | scrollPtrH = 1 ;tmpBot + 1 223 | scrollLen = 2 ;tmpBot + 2 224 | scrollIdx = 3 ;tmpBot + 3 225 | titleState = 4 ;currLevel 226 | 227 | 228 | lda #0 229 | sta leftEdge ; reset the left edge 230 | 231 | lda #11*8 ; position willy for the UI 232 | sta willyYPos 233 | lda #0 234 | sta willyFrame 235 | lda #18 236 | sta willyXPos 237 | 238 | lda #titleMusic 241 | sta musicH 242 | 243 | ldx #1 ; do a full screen clear of both buffers 244 | jsr screenClear 245 | jsr screenSwap 246 | ldx #1 247 | jsr screenClear 248 | 249 | lda #0 250 | sta titleState ; set to bounce manic miner / audio 251 | sta uiComponent ; and init the ui update to nothing 252 | lda #UI_COMPONENT_SCORE_NAME | UI_COMPONENT_SCORE | UI_COMPONENT_HIGHSCORE 253 | jsr uiUpdateComponent ; add an update for bottom part of screen 254 | 255 | jsr screenDrawWilly ; show willy on this screen 256 | uiShowMMTextXY #6, #6, manicText, manicCharWidth, manicColors 257 | printXY #0, #22*8, roTextAppleIIVersion, #19 258 | printXY #0, #23*8, roTextStefan, #19 259 | jsr uiUpdate ; show the UI on the Manic screen 260 | jsr screenSwap 261 | 262 | jsr screenDrawWilly ; show willy on the other screen 263 | uiShowMMTextXY #6, #6, minerText, minerCharWidth, minerColors 264 | printXY #0, #22*8, roTextAppleIIVersion, #19 265 | printXY #0, #23*8, roTextStefan, #19 266 | jsr uiUpdate ; show the UI on the Miner screen 267 | 268 | lda KBDSTRB 269 | 270 | mainLoop: 271 | jsr screenSwap ; swap screens (manic/miner) or scroll text 272 | jsr inputUI ; read keys -1 - quit, 0 - no key, 1 - go to game 273 | beq stayInUI ; no key 274 | bpl playGame ; go to game 275 | lda #EVENT_EXIT_GAME ; quit 276 | bne exit 277 | playGame: 278 | lda #EVENT_OK 279 | exit: 280 | jmp done 281 | 282 | stayInUI: 283 | lda titleState ; get the state (bounce vs scroll) 284 | beq bounce 285 | jmp introScroll ; 0 - bounce, 1 - scroll 286 | 287 | bounce: 288 | jsr audioPlayTitleNote ; play the tune 289 | bcc mainLoop ; tune done when carry is set, else bounce 290 | 291 | lda #2 292 | sta titleState ; use this as a temporary counter 293 | : 294 | ldx #0 ; clear the upper portion of the screen 295 | jsr screenClear ; show both manic & miner 296 | uiShowMMTextXY #6, #0, manicText, manicCharWidth, manicColors 297 | uiShowMMTextXY #6, #8, minerText, minerCharWidth, minerColors 298 | jsr screenDrawWilly ; show willy as well 299 | 300 | jsr screenSwap 301 | dec titleState ; do for both buffers 302 | lda titleState 303 | bne :- 304 | 305 | lda #1 ; set the state to the scrolling screen 306 | sta titleState 307 | 308 | lda #roTextIntro 311 | sta scrollPtrH 312 | lda #0 313 | sta scrollLen 314 | lda #19 315 | sta scrollIdx 316 | 317 | introScroll: ; show the scrolling message 318 | ldy willyYPos ; start by erasing willy so the eor draw works 319 | lda #16 ; 16 rows from his Y 320 | sta sizeL ; track rows in sizeL 321 | : 322 | clc 323 | lda willyXPos ; start with his X 324 | asl ; * 2 for screen coordinates 325 | adc rowL, y ; get the hires coordinates 326 | sta writeZero + 1 327 | lda rowH, y 328 | adc currPageH 329 | sta writeZero + 2 330 | lda #0 ; write 4 zero-bytes to each row 331 | ldx #3 332 | writeZero: 333 | sta PLACEHOLDER, x 334 | dex 335 | bpl writeZero 336 | iny ; next hi-res row 337 | dec sizeL ; one more row done 338 | bne :- 339 | 340 | lda willyFrame ; get the current frame 341 | clc 342 | adc #2 ; advance by 2 343 | and #7 ; and keep in the frame range 344 | sta willyFrame ; update the frame 345 | jsr screenDrawWilly ; and draw willy in the new pose 346 | 347 | printXYlh scrollIdx, #16*8, scrollPtrL, scrollPtrH, scrollLen 348 | lda scrollIdx ; see if scrolled all the way into the screen 349 | beq :+ ; if printing at 0, then scrolled all the way in 350 | dec scrollIdx ; not scrolled in all the way, so move the start left 351 | inc scrollLen ; and increase the length to print by 1 352 | bne :++ ; and skip moving the start of the message 353 | : 354 | inc scrollPtrL ; move the scroller so the left disappears off 355 | bne :+ ; the left end of the screen 356 | inc scrollPtrH 357 | : 358 | jsr uiDelay ; wait a bit so the message can be read 359 | 360 | ldy scrollLen ; see if this is the end of the message 361 | lda (scrollPtrL), y 362 | bne :+ ; if not a zero, still in message 363 | dec scrollLen ; start printing less of the message so the tail scrolls across 364 | bmi demoTime ; when completely done, go to demo mode 365 | : 366 | jmp mainLoop ; repeat till fully scrolled 367 | 368 | demoTime: 369 | lda #DEMO_TIMER_INITAL ; set up the demo variables 370 | sta demoTimer 371 | lda #1 372 | sta demoDirection 373 | 374 | done: 375 | sta demoMode 376 | rts 377 | 378 | .endproc 379 | 380 | ;----------------------------------------------------------------------------- 381 | .proc uiWaitForIntroEnter 382 | 383 | iter = tmpBot + 0 ; how many times to loop 384 | color = tmpBot + 1 ; the starting color for the string 385 | xPos = tmpBot + 2 ; x for string 386 | yPos = tmpBot + 3 ; y for string 387 | textL = tmpBot + 4 ; string pointer 388 | textH = tmpBot + 5 389 | len = tmpBot + 6 ; how many characters (0 based) 390 | 391 | lda KBDSTRB ; clear the keyboard 392 | 393 | jsr screenSwap::valueSwap 394 | printXY #0, #22*8, roTextPressEnter, #19 395 | lda #$28 ; intentionally outside the range 396 | sta color ; it gives an interesting "materialize" effect 397 | lda #22*8 398 | sta yPos 399 | 400 | cycleLoop: 401 | lda #6 ; print ENTER at x 4 402 | sta xPos 403 | lda #roTextEnter 406 | sta textH 407 | lda #5 408 | sta len 409 | jsr textColorCycle ; show the text in color 410 | ldy #$40 411 | jsr uiDelay::ySet 412 | 413 | lda KBD ; hold the load screen graphic till a key is pressed 414 | bpl cycleLoop 415 | 416 | lda KBDSTRB ; clear the keyboard 417 | jsr screenSwap::valueSwap 418 | 419 | ldx #1 ; for clear-screen, x = 1 is all clear, x = 0 is partial 420 | jsr screenClear 421 | jsr screenSwap ; swap to see page 1 422 | ldx #1 423 | jmp screenClear ; all clear page 2 424 | 425 | .endproc 426 | -------------------------------------------------------------------------------- /src/apple2/variables.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; variables.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "ZEROPAGE" 10 | 11 | ;----------------------------------------------------------------------------- 12 | currPageH: .res 1 ; $02 or $04 - Hi for screen buffer to draw t0 13 | backPage: .res 1 ; 0 or 1 - just used to toggle HISCR or LOSCR 14 | monochrome: .res 1 ; 0 for color, 1 for black and white 15 | leftEdge: .res 1 ; distance the screen has scrolled to the right 16 | cameraMode: .res 1 17 | tilesRendered: .res 1 ; count the number of tiles shown in a screen. Used to time music delay 18 | 19 | willyXPos: .res 1 ; column for willy 20 | willyYPos: .res 1 ; pixel-row for willy 21 | willyYRow: .res 1 ; row (YPos / 8) for willy 22 | willyFrame: .res 1 ; willy animation frame 23 | willyDir: .res 1 ; direction willy is facing 24 | willyJumpCounter: .res 1 ; what phase of a jump willy is in 25 | willyFallFromJump: .res 1 ; 0 or 1. 1 when willy starts jumping. Affects falling death 26 | 27 | numSprites: .res 1 ; how many sprites (excl. willy) to draw in a level (door is last) 28 | spriteXPos: .res MAX_SPRITES ; col position of the sprite 29 | spriteYPos: .res MAX_SPRITES ; pixel row 30 | spriteDir: .res MAX_SPRITES ; facing direction 31 | spriteMin: .res MAX_SPRITES ; min (turn/hold) point for path 32 | spriteMax: .res MAX_SPRITES ; max point for path 33 | spriteSpeed: .res MAX_SPRITES ; speed (frames/pixels) to move the sprite 34 | spriteTick: .res MAX_SPRITES ; counter how often a sprite animates (spritespeed as init) 35 | spriteFramesIdx: .res MAX_SPRITES ; Index into 36 | spriteFrame: .res MAX_SPRITES ; which frame the sprite is on 37 | spriteClass: .res MAX_SPRITES ; see CLASS_* in defs.inc for masks 38 | spriteColor: .res MAX_SPRITES ; index into masks in rosystem.inc 39 | 40 | movementMask: .res 1 ; movement that happened in willyMove. See MASK_* in defs.inc 41 | userKeyMask: .res 1 ; user desire to move based on keys pressed. See MASK_* in defs.inc 42 | conveyorMask: .res 1 ; 0, willy not on conveyor, 2 for left, 1 for right 43 | 44 | currLevel: .res 1 ; level that's active 45 | lives: .res 1 ; lives in reserve 46 | airCols: .res 1 ; screen columns that have an air bar in them 47 | airTipGfx: .res 1 ; the bit pattern for the tip of the air bar 48 | airFlow: .res 1 ; the "tick" till a unit of air is decreased 49 | livesFrame: .res 1 ; anim frame for the walking willy's at the bottom 50 | keysFrameNum: .res 1 ; animation frame for keys in the level (color) 51 | conveyorFrameNum: .res 1 ; animation frame for conveyor tile to be active 52 | keysToCollect: .res 1 ; number of keys that remain to be collected 53 | 54 | eventState: .res 1 ; see EVENT_* in defs.inc for bitmask values 55 | updateUICount: .res 1 ; updateUI is called when non-zero 56 | uiComponent: .res 1 ; See UI_COMPONENT_* in defs.inc for mask values. What UI to update 57 | fullScreenClearCount: .res 1 ; 0 - clear only top, non-zero all. Is counted down by gameAI 58 | 59 | musicL: .res 1 ; pointer or index for playing music 60 | musicH: .res 1 ; hi pointer for playing UI music 61 | audioMask: .res 1 ; see AUDIO_* in defs.inc 62 | 63 | demoMode: .res 1 ; 0 - not a demo, 1 when demo mode active 64 | demoTimer: .res 1 ; in demo mode, scroll when counter is 0 65 | demoDirection: .res 1 ; direction the demo scroll will move the screen 66 | 67 | cheatIndex: .res 1 ; count cheat code entry or when active, if 6 is down 68 | cheatActive: .res 1 ; non-zero when the cheat was successfully entered 69 | 70 | ;----------------------------------------------------------------------------- 71 | tempBlock: .res 13 ; widely used z-page general memory 72 | srcPtrL := tempBlock + 0 ; often a pointer Lo 73 | srcPtrH := tempBlock + 1 ; often a pointer Hi 74 | dstPtrL := tempBlock + 2 ; often a pointer Lo 75 | dstPtrH := tempBlock + 3 ; often a pointer Hi 76 | sizeL := tempBlock + 4 ; sometimes a size used in ptr operations 77 | sizeH := tempBlock + 5 ; sometimes a size used in ptr operations 78 | tmpBot := tempBlock + 6 ; start of block of 6 zp values used randomly 79 | 80 | ;----------------------------------------------------------------------------- 81 | bitMasks: ; constant - used mostly for bit instruction 82 | bit0Mask: .res 1 ; 1 83 | bit1Mask: .res 1 ; 2 84 | bit2Mask: .res 1 ; 4 85 | bit3Mask: .res 1 ; 8 86 | bit4Mask: .res 1 ; 16 87 | bit5Mask: .res 1 ; 32 88 | bit6Mask: .res 1 ; 64 89 | bit7Mask: .res 1 ; 128 90 | 91 | ;----------------------------------------------------------------------------- 92 | .segment "LOWMEM" 93 | 94 | ; These are instance buffers for sprites / tiles. They are copied into this 95 | ; area and then masked for color from where they are rendered 96 | levelLayout: .res PLAY_COLS * PLAY_ROWS ; Unpacked level (tile) info - keep 1st for alignment 97 | 98 | ; Instances for display 99 | spriteInstances: .res MAX_SPRITE_IFRAMES * SPRITE_BYTES 100 | tilesInstances: .res TILES_PER_LEVEL * TILE_BYTES ; instances of tiles in use bitmaps 101 | keyAnimTiles: .res KEYS_FRAMES * TILE_BYTES ; color instances of key tile 102 | conveyorAnimTiles: .res CONVEYOR_FRAMES * TILE_BYTES ; instances of conveyor animated tiles 103 | 104 | ; cache of 8 pixel-rows for 32 double-byte characters. The level name is centered in here, and drawn from here 105 | levelNameGfx0: .res PLAY_COLS * 2 * 8 106 | 107 | ;----------------------------------------------------------------------------- 108 | .segment "DATA" 109 | 110 | ; The scores are updated in these text strings directly 111 | highScore: 112 | .byte "000000" 113 | score: 114 | .byte "000000" 115 | -------------------------------------------------------------------------------- /src/apple2/willy.inc: -------------------------------------------------------------------------------- 1 | ;----------------------------------------------------------------------------- 2 | ; willy.inc 3 | ; Part of manic miner, the zx spectrum game, made for Apple II 4 | ; 5 | ; Stefan Wessels, 2020 6 | ; This is free and unencumbered software released into the public domain. 7 | 8 | ;----------------------------------------------------------------------------- 9 | .segment "CODE" 10 | 11 | ;----------------------------------------------------------------------------- 12 | ; Moves willy. Incorporates user desire (userKeyMask) and conveyor direction 13 | ; in movement and does movement and collision resolution. This code was juggled a 14 | ; whole lot to make the outcome match that of the original game. It's quite 15 | ; possibly sub-optimal, but it does what it is meant to do. 16 | .proc willyMove 17 | 18 | willyYPosBackup = tmpBot + 1 19 | willyXPosBackup = tmpBot + 2 20 | willyFrameBackup = tmpBot + 3 21 | willyFloor = tmpBot + 4 22 | 23 | lda movementMask 24 | bit bit2Mask ; MASK_AIR 25 | beq checkHorzKeys ; on ground - check horizontal keys 26 | jmp selectDirection ; in air - check horizontal motion 27 | 28 | checkHorzKeys: 29 | lda userKeyMask ; get the user Key 30 | ldx conveyorMask ; and get the conveyor direction 31 | beq notOnConveyor ; ignore conveyor code if not on a conveyor 32 | 33 | and conveyorMask ; and user desire with conveyor 34 | bne onConveyor ; if same, user conveyor direction for actual 35 | lda userKeyMask ; get the user mask and 36 | and #<~MASK_AIR ; if it's got no horizontal component 37 | beq onConveyor ; willy is following the conveyor 38 | 39 | lda movementMask ; user key horiz and not same as conveyor 40 | jmp selectDirection ; so simply keep the current movement going 41 | 42 | onConveyor: 43 | lda movementMask ; actual is now conveyor direction 44 | and #<~(MASK_LEFT | MASK_RIGHT) ; clear current 45 | ora conveyorMask ; add conveyor direction 46 | sta movementMask ; store as actual 47 | jmp selectDirection ; use the conveyor for sideways 48 | 49 | notOnConveyor: 50 | and #<~MASK_AIR ; clear the jump desire 51 | sta movementMask ; make actual user desire 52 | 53 | selectDirection: 54 | and #(MASK_LEFT | MASK_RIGHT) ; see if willy is moving horizontally 55 | bne :+ ; yes - handle horizontal movement 56 | jmp vertical 57 | : 58 | and #MASK_RIGHT ; check MASK_RIGHT 59 | bne right ; if set, move to the right 60 | 61 | left: 62 | lda willyDir ; see if already heading left 63 | bne moveLeft ; if so, keep moving left 64 | lda #0 ; when turning, no direction 65 | sta movementMask 66 | lda #1 ; was facing right, so turn around 67 | sta willyDir 68 | lda willyFrame ; flip the facing frame to left 69 | ora #4 70 | sta willyFrame 71 | bne vertical 72 | 73 | moveLeft: 74 | ldx willyFrame ; get the frame 75 | stx willyFrameBackup ; back it up 76 | dex ; move one left 77 | cpx #4 ; wrapped? 78 | bcc :+ ; yes, move to previous column 79 | stx willyFrame ; not wrapped, save the frame 80 | bcc :+ 81 | jmp vertical 82 | : 83 | ldx #7 ; keep going, load right most frame 84 | stx willyFrame ; set frame 85 | ldx willyXPos ; get the column 86 | stx willyXPosBackup ; save it 87 | dex ; previous column 88 | stx willyXPos ; make that current 89 | ldy #0 ; check side (left) collisions column 90 | beq hCollision ; BRA 91 | 92 | right: 93 | lda willyDir ; see if willy's already facing right 94 | beq moveRight ; if so, move 95 | lda #0 ; turn willy to the right 96 | sta willyDir 97 | sta movementMask ; and clear the movement mask 98 | lda willyFrame 99 | and #3 ; set right facing frame 100 | sta willyFrame 101 | jmp vertical 102 | 103 | moveRight: 104 | ldx willyFrame ; back up the animation frame 105 | stx willyFrameBackup 106 | inx ; next frame (to the right) 107 | cpx #4 ; wrapped? 108 | bcs :+ ; yes, move to next (right) column 109 | stx willyFrame ; save the frame 110 | bcc vertical 111 | : 112 | ldx #0 113 | stx willyFrame ; set frame 0 114 | ldx willyXPos ; back up the column 115 | stx willyXPosBackup 116 | inx ; move right 117 | stx willyXPos ; save this column 118 | ldy #1 ; check the right-hand column for collisions 119 | 120 | hCollision: 121 | jsr willySetWorldPtr ; set up the world pointer to check collisions 122 | ldx #2 ; assume checking 2 rows 123 | lda willyYPos ; get the height in pixels 124 | and #7 ; see if willy is aligned 125 | beq colLoop ; yes, go with 2 rows 126 | inx ; if not aligned, willy crosses 3 rows 127 | colLoop: 128 | lda (srcPtrL), y ; load the world byte 129 | beq :+ ; if air then no collision 130 | jsr willyWorldCollision ; resolve the collision if needed 131 | bcc :+ ; if carry clear, can move (didn't hit a wall) 132 | lda willyXPosBackup ; hit a wall, so restore position (column) 133 | sta willyXPos 134 | lda willyFrameBackup ; and also restore the frame 135 | sta willyFrame 136 | jmp vertical 137 | : 138 | tya ; Y is 0 or 1 (left or right), put in a 139 | adc #32 ; move down a row 140 | tay ; and put back in y 141 | dex ; x has the number of rows remaining to check 142 | bne colLoop ; do till all rows checked 143 | 144 | vertical: 145 | lda movementMask 146 | bit bit2Mask ; MASK_AIR 147 | bne vertMove ; in the air already then move vertically 148 | 149 | lda userKeyMask ; get the user desire 150 | bit bit2Mask ; MASK_AIR see if user wants to jump 151 | beq willyCollisionFeet ; if not then check feet 152 | 153 | and #<~MASK_AIR ; clear jump from desire 154 | sta userKeyMask ; and save 155 | 156 | lda movementMask ; update actual 157 | ora #MASK_AIR ; by adding a jump 158 | sta movementMask 159 | 160 | lda #1 ; mark this as a fall starting 161 | sta willyFallFromJump ; from a jump - willy dies easier 162 | 163 | vertMove: 164 | lda willyYPos ; save the current Y position 165 | sta willyYPosBackup 166 | 167 | lda willyJumpCounter ; get the current jump counter for height calculation 168 | cmp #18 ; see sbc #4 below for why 18 (up/down curve length) 169 | bcs falling ; if jump counter gt 17 then falling (0-17 is jump) 170 | 171 | lsr ; / 2 172 | sec 173 | sbc #4 ; -4, so -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 174 | clc 175 | adc willyYPos ; adjust willy height 176 | sta willyYPos 177 | 178 | ldx willyJumpCounter ; get the current jump counter for height calculation 179 | lda jumpFreq, x 180 | ldy #$08 ; duration for the freq 181 | jsr audioPlayNote::freq ; make a sound of this freq and duration 182 | inc willyJumpCounter 183 | 184 | lda willyJumpCounter 185 | cmp #9 186 | bcs willyCollisionFeet ; and move the jump counter along 187 | 188 | willyCollisionTop: 189 | lda willyYPosBackup ; see where willy was 190 | and #7 ; if aligned with a row, now entering a new row so check collision 191 | bne willyCollisionFeet ; not aligned, no collision, go to feet check 192 | jsr willySetWorldPtr ; set the srcPtr to point into the world 193 | ldy #0 ; start with the head left 194 | lda (srcPtrL), y ; read the world byte 195 | beq :+ ; if air then do nothing 196 | jsr willyWorldCollision ; not air so handle the potential collision 197 | bcs hitHead ; carry set means wall collision, so fall 198 | : 199 | ldy #1 ; check the right as well 200 | lda (srcPtrL), y 201 | beq willyCollisionFeet ; if air, ignore 202 | jsr willyWorldCollision 203 | bcc willyCollisionFeet ; carry clear means no collision 204 | 205 | hitHead: 206 | lda willyYPosBackup 207 | sta willyYPos 208 | lda #18 209 | sta willyJumpCounter 210 | jmp willyCollisionFeet ; and check for landing immediately 211 | 212 | falling: 213 | lda willyJumpCounter ; get the current jump counter for height calculation 214 | and #$0f 215 | tax 216 | lda fallFreq, x 217 | ldy #$08 ; duration for the freq 218 | jsr audioPlayNote::freq ; make a sound of this freq and duration 219 | inc willyJumpCounter ; and move the jump counter along 220 | 221 | lda movementMask ; strip horizontal from actual 222 | and #<~(MASK_LEFT | MASK_RIGHT) 223 | sta movementMask 224 | lda willyYPos ; get willy's height 225 | clc 226 | adc #4 ; move him 4 down (fall speed) 227 | sta willyYPos 228 | 229 | willyCollisionFeet: 230 | lda #0 ; assume not on a conveyor 231 | sta conveyorMask 232 | 233 | lda willyYPos ; see if willy is level with a floor 234 | and #7 235 | bne positionScreen ; if not, don't check the feet 236 | 237 | lda willyJumpCounter ; see if still going up in jump 238 | beq :+ ; if 0 then not jumping 239 | cmp #10 ; if still going up in the jump, don't check the feet 240 | bcc positionScreen 241 | 242 | : 243 | jsr willySetWorldPtr ; set up the world pointer 244 | ldy #64 ; check the ground below willy, left 245 | lda (srcPtrL), y 246 | sta willyFloor ; store the byte 247 | beq :+ ; if air, don't resolve 248 | jsr willyFloorCollision ; resolve the collision 249 | : 250 | ldy #65 ; check the floor right 251 | lda (srcPtrL), y 252 | beq checkFloor ; if air, done with feet 253 | tax ; save the byte 254 | ora willyFloor ; and update the floor 255 | sta willyFloor 256 | txa ; restore the world byte 257 | jsr willyFloorCollision ; and resolve it 258 | 259 | checkFloor: 260 | lda willyFloor ; was there something under willy's feet 261 | bne positionScreen ; if yes, then position willy 262 | 263 | lda willyJumpCounter ; see if willy is in a jump or fall 264 | bne positionScreen ; yes, position screen 265 | 266 | lda #18 ; willy was walking, so set him to fall 267 | sta willyJumpCounter 268 | lda movementMask ; strip horizontal from actual 269 | and #<~(MASK_LEFT | MASK_RIGHT) ; clear left and right 270 | ora #MASK_AIR ; set the mask that he's now in the air (fall) 271 | sta movementMask 272 | 273 | 274 | positionScreen: ; this is also called from gameInitStage 275 | lda cameraMode ; see which "camera mode" is active 276 | bne cameraScroll ; non zero is scroll mode 277 | 278 | camera3Zone: ; zone 1: 0-19, 2: 6-25, 3: 12-31 279 | lda willyXPos ; see where Willy is 280 | cmp #10 ; 10 divides zones 1 and 2 281 | bcs zone2or3 ; ge 10 means zone 2 or 3 282 | lda leftEdge ; zone 1 - see where the edge is 283 | cmp #0 ; if it's at 0 all is well 284 | beq done 285 | cameraLess: 286 | dec leftEdge ; edge is gt 0, so move it left 287 | bpl moveName ; BRA 288 | zone2or3: 289 | cmp #20 ; 20 divides zones 2 and 3 290 | bcs cameraRight ; ge 20 means zone 3 291 | lda leftEdge ; see where the edge is (in zone 2) 292 | cmp #6 ; zone 2 edge wants to be at 6 293 | beq done ; if it's there, all is well 294 | bcs cameraLess ; if it's gt 6, then move it left 295 | inc leftEdge ; move the edge right 296 | bne moveName ; BRA 297 | cameraRight: 298 | lda leftEdge ; zone 3, see where the edge is 299 | cmp #$0c ; if it's at 12 300 | beq done ; then it's all good 301 | inc leftEdge ; otherwise move it right towards 12 302 | 303 | moveName: ; if the edge moves the text needs to move as well 304 | lda #UI_COMPONENT_NAME ; and mark the name as needing to scroll too 305 | jsr uiUpdateComponent 306 | 307 | rts 308 | 309 | cameraScroll: 310 | lda willyXPos 311 | sec ; col is in accumulator 312 | sbc #$0a ; see if willy is past column 10 313 | bcs :+ 314 | lda #0 ; not, so set the left edge to the left 315 | bne :++ ; BRA 316 | : 317 | cmp #$0d ; see if the col is less than 13 318 | bcc :+ 319 | lda #$0c ; col is 13 or greater, so clamp to 12 320 | : 321 | cmp leftEdge ; see if the edge needs to move 322 | beq done ; don't move 323 | sta leftEdge ; set the new left edge 324 | lda #UI_COMPONENT_NAME ; and mark the name as needing to scroll too 325 | jsr uiUpdateComponent 326 | done: 327 | rts ; done with willy's movement 328 | 329 | .endproc 330 | 331 | ;----------------------------------------------------------------------------- 332 | ; resolves collisions for willy. Used by feet but foot collision entry is 333 | ; from willyFloorCollision. on exit, carry set means wall collision 334 | .proc willyWorldCollision 335 | 336 | clc 337 | cmp #DATA_BUSH ; bushes kill willy 338 | beq willyDies 339 | cmp #DATA_ROCK ; rocks kill willy 340 | bne :+ 341 | 342 | willyDies: 343 | lda #EVENT_DIED ; simply set the die event 344 | ora eventState 345 | sta eventState 346 | rts 347 | 348 | : 349 | cmp #DATA_WALL ; walls block willy 350 | bne :+ 351 | rts ; carry was set by the cmp and equality. set means wall collision 352 | 353 | : 354 | cmp #DATA_KEY ; key's need to be counted 355 | bne :+ 356 | jmp willyHitKey ; clears carry 357 | 358 | : 359 | cmp #DATA_DOOR ; added dynamically when last key found 360 | bne :+ 361 | lda #EVENT_NEXT_LEVEL ; set event to move to next cavern/level 362 | ora eventState 363 | sta eventState 364 | rts 365 | 366 | : 367 | cmp #DATA_SWITCH1 ; added at level init for kong screens 368 | bne :+ 369 | jmp willyHitSwitch1 370 | 371 | : 372 | cmp #DATA_SWITCH2 ; added at level init for kong screens 373 | bne done 374 | jmp willyHitSwitch2 375 | 376 | done: 377 | clc ; for unhandled (floor tiles), just clear carry 378 | rts 379 | 380 | .endproc 381 | 382 | ;----------------------------------------------------------------------------- 383 | .proc willyFloorCollision 384 | 385 | cmp #DATA_FLOOR1 ; floors are landed on 386 | beq landed 387 | 388 | cmp #DATA_WALL ; walls can be walked on 389 | beq landed 390 | 391 | cmp #DATA_FLOOR2 ; special can be walked on 392 | bne :+ 393 | 394 | landed: 395 | lda movementMask ; see landing from a jump/fall 396 | and #MASK_AIR 397 | beq notFromAir ; just a foot-fall from walking, no action 398 | clc 399 | lda willyFallFromJump ; see if a jump 400 | adc willyJumpCounter ; and how far 401 | cmp #18+9 ; compare to death height 402 | bcc fellNotTooFar ; not too far 403 | jmp willyWorldCollision::willyDies ; fell to far, kill willy 404 | fellNotTooFar: 405 | lda #0 ; reset the fall from jump 406 | sta willyFallFromJump 407 | sta willyJumpCounter ; and reset the willy jump counter 408 | lda #<~MASK_AIR ; clear the air bit 409 | and movementMask 410 | sta movementMask 411 | notFromAir: 412 | rts 413 | 414 | : 415 | cmp #DATA_CONVEYOR ; landed on a conveyor 416 | bne :+ 417 | ldx currLevel ; get the level 418 | lda conveyorDirections, x ; get the direction of the conveyor 419 | sta conveyorMask ; set it as the conveyor mask (which is reset each frame) 420 | bne landed ; and do landing code 421 | 422 | : 423 | cmp #DATA_COLLAPSE ; check for collapsing tiles 424 | bcc willyWorldCollision ; less than, not collapsing 425 | cmp #DATA_COLLAPSE + 9 ; in the collapse range 426 | bcs willyWorldCollision ; no, check non-walk/floor tiles 427 | jsr willyCollapse ; collapse a platform tile, returns with carry clear 428 | bcc landed ; BRA to land code 429 | 430 | .endproc 431 | 432 | ;----------------------------------------------------------------------------- 433 | .proc willyHitSwitch1 434 | 435 | clc 436 | adc #1 ; DATA_SWITCH1 becomes DATA_SWITCH1_OPEN 437 | sta (srcPtrL), y ; make this switch draw open 438 | 439 | lda #0 440 | sta levelLayout+11*32+17 ; make a hole in the wall 441 | sta levelLayout+12*32+17 442 | 443 | ldx #1 ; sprite 1 barrel needs to go further 444 | lda #19 ; this is the new max for return-kong 445 | extend: 446 | sta spriteMax, x ; make the new max active 447 | clc ; must leave with carry clear - not a wall collision 448 | rts 449 | 450 | .endproc 451 | 452 | ;----------------------------------------------------------------------------- 453 | .proc willyHitSwitch2 454 | 455 | clc 456 | adc #1 ; DATA_SWITCH2 becomes DATA_SWITCH2_OPEN 457 | sta (srcPtrL), y ; make this switch draw open 458 | 459 | lda #0 460 | sta levelLayout+2*32+15 ; remove kong's platform 461 | sta levelLayout+2*32+16 462 | 463 | ldx #3 ; kong is at index 3 464 | lda #14*8-5 ; put the fallen-down destination for kong in place 465 | sta spriteMax, x 466 | 467 | lda #2 ; turn kong upside down 468 | sta spriteFrame, x 469 | 470 | lda #0 ; set kong's direction to down 471 | sta spriteDir, x 472 | 473 | rts 474 | 475 | .endproc 476 | 477 | ;----------------------------------------------------------------------------- 478 | .proc willyCollapse 479 | 480 | clc 481 | adc #1 ; move the tile-top one down 482 | cmp #DATA_COLLAPSE + 7 ; is it all the way down 483 | bcc :+ ; not yet 484 | lda #0 ; yes, so erase the tile 485 | clc 486 | : 487 | sta (srcPtrL), y ; make the change in the level 488 | rts 489 | 490 | .endproc 491 | 492 | ;----------------------------------------------------------------------------- 493 | .proc willyHitKey 494 | 495 | tempX = tmpBot + 5 496 | 497 | stx tempX ; save x - it may be an index in left/right col 498 | lda #0 ; erase the key tile in the level 499 | sta (srcPtrL), y 500 | ldx #3 ; add 100 (digit 3 of 6, zero based 000100) 501 | lda #1 502 | jsr textAddScore 503 | dec keysToCollect ; 1 less key to collect 504 | bne done ; all keys collected? 505 | 506 | ldx currLevel ; get the level 507 | lda doorL, x ; get the door location in the level (lo) 508 | sta putDoor + 1 509 | lda doorH, x ; and hi 510 | sta putDoor + 2 511 | lda #DATA_DOOR ; get the door tile 512 | putDoor: 513 | sta PLACEHOLDER ; and add the door tile to the level 514 | 515 | done: 516 | ldx tempX ; restore the saved x 517 | clc ; make sure carry is clear 518 | rts 519 | 520 | .endproc 521 | 522 | ;----------------------------------------------------------------------------- 523 | .proc willySetWorldPtr 524 | 525 | lda willyYPos ; get the height 526 | lsr ; divide by 8 527 | lsr 528 | lsr 529 | sta willyYRow ; save the row willy's in 530 | tax ; put the row in Y 531 | clc 532 | lda mult32L, x ; row * 32 533 | adc willyXPos ; and add the X position (levelLayout is aligned so no need to add lo) 534 | sta srcPtrL ; the low byte of the pos in the level 535 | lda mult32H, x ; get the high byte 536 | adc #>levelLayout ; and offset into the level 537 | sta srcPtrH ; and now srcPtr points at willy in the level 538 | 539 | rts 540 | 541 | .endproc 542 | -------------------------------------------------------------------------------- /src/mmm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is abysmal. I can't stand working with these 3rd party libraries because, for me at 2 | # least, nothing ever works as advertised. If you know what you are doing and you want to build 3 | # this, what's here can be a guide to get you going. I built this on macOS (not this file) and 4 | # WIN32. After I modified it for macOS the WIN32 broke so I modified it for WIN32 and now the 5 | # APPLE is broken. Probably an easy fix if you knwo what you are doing which I cleary don't when 6 | # it comes to SDL 7 | 8 | cmake_minimum_required(VERSION 3.16) 9 | 10 | # Set project name 11 | project(mmm LANGUAGES C) 12 | 13 | # Set the C++ standard 14 | set(CMAKE_CXX_STANDARD 11) 15 | set(CMAKE_CXX_STANDARD_REQUIRED True) 16 | 17 | # Find SDL2, SDL2_image, and SDL2_mixer using pkg-config for macOS 18 | if (APPLE) 19 | find_package(PkgConfig REQUIRED) 20 | pkg_check_modules(SDL2 REQUIRED sdl2) 21 | pkg_check_modules(SDL2_image REQUIRED sdl2_image) 22 | pkg_check_modules(SDL2_mixer REQUIRED sdl2_mixer) 23 | else() 24 | # Find SDL2, SDL2_image, and SDL2_mixer for other platforms 25 | find_package(SDL2 REQUIRED) 26 | find_package(SDL2_image REQUIRED) 27 | find_package(SDL2_mixer REQUIRED) 28 | endif() 29 | 30 | # Add the executable 31 | add_executable(${PROJECT_NAME} 32 | src/6502.c 33 | src/mminer.c 34 | src/mmm.c 35 | ) 36 | 37 | # Include SDL2 directories 38 | target_include_directories(${PROJECT_NAME} PRIVATE 39 | ${SDL2_INCLUDE_DIRS} 40 | ${SDL2_IMAGE_INCLUDE_DIRS} 41 | ${SDL2_MIXER_INCLUDE_DIRS} 42 | ) 43 | 44 | # Link SDL2, SDL2_image, and SDL2_mixer libraries 45 | target_link_libraries(${PROJECT_NAME} 46 | ${SDL2_LIBRARIES} 47 | ${SDL2_MIXER_LIBRARIES} 48 | ${SDL2_IMAGE_LIBRARIES} 49 | ) 50 | 51 | # Link libraries depending on platform 52 | if (APPLE) 53 | target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES} ${SDL2_image_LIBRARIES} ${SDL2_mixer_LIBRARIES}) 54 | elseif(WIN32) 55 | target_link_libraries(${PROJECT_NAME} 56 | "C:/Users/swessels/develop/github/external/vcpkg/installed/x64-windows/lib/SDL2.lib" 57 | "C:/Users/swessels/develop/github/external/vcpkg/installed/x64-windows/lib/SDL2_image.lib" 58 | "C:/Users/swessels/develop/github/external/vcpkg/installed/x64-windows/lib/SDL2_mixer.lib" 59 | ) 60 | 61 | # Glob all DLL files in the directory 62 | file(GLOB DLLS "C:/Users/swessels/develop/github/external/vcpkg/installed/x64-windows/bin/*.dll") 63 | 64 | # Iterate over the DLLs and copy them to the target output directory 65 | foreach(DLL ${DLLS}) 66 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 67 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 68 | "${DLL}" 69 | $ 70 | ) 71 | endforeach() 72 | else() 73 | # Link libraries for non-Apple, non-Windows platforms (Linux, etc.) 74 | target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES} ${SDL2_image_LIBRARIES} ${SDL2_mixer_LIBRARIES}) 75 | endif() 76 | -------------------------------------------------------------------------------- /src/mmm/README.md: -------------------------------------------------------------------------------- 1 | # The Manic Miner Machine 2 | The Manic Minder Machine is a very, very small Apple II emulator that I built specifically to run, on a modern computer, the unmodified 6502 version of Manic Miner that I made for the Apple II. 3 | 4 | Manic Miner uses only the Keyboard ($C000), Keyboard Strobe ($C010), the speaker ($C030) and 2 hires banks (with toggles at $C054 and $C055). So building an emulator should be very easy. Well, it was. 5 | 6 | Turn audio off with M or S (Music & Sound). ESC will quit. O & P for left and right and SPACE to jump. The original ZX Spectum cheat will also work. 7 | 8 | # The Files 9 | | File | Description 10 | | --- | --- 11 | | 6502.c | The 6502 Machine 12 | | mminer.c | The 6502 Manic Miner compiled code 13 | | mmm.c | The Apple II emulator (around 400 lines :) 14 | 15 | # Building the code 16 | The CMakeLists.txt file works for my installation. This is my note at the top which sums up my feelings and frustrations. 17 | ``` 18 | # This file is abysmal. I can't stand working with these 3rd party libraries because, for me at 19 | # least, nothing ever works as advertised. If you know what you are doing and you want to build 20 | # this, what's here can be a guide to get you going. I built this on macOS (not this file) and 21 | # WIN32. After I modified it for macOS the WIN32 broke so I modified it for WIN32 and now the 22 | # APPLE is broken. Probably an easy fix if you know what you are doing which I clearly don't when 23 | # it comes to SDL 24 | ``` 25 | 26 | # The Speed 27 | The game runs equally well on my M3 Mac Mini as well as on my Windows PC. I did not build for Linux. 28 | 29 | # The Future 30 | I will probably not do too much with this. 31 | 32 | Feel free to contact me at swessels@email.com if you have thoughts or suggestions. 33 | 34 | Thank you 35 | Stefan Wessels 36 | 9 September 2024 - Initial Revision -------------------------------------------------------------------------------- /src/mmm/src/6502.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RAM_SIZE (64*1024) 4 | #define PAGE_SIZE RAM_SIZE 5 | #define NUM_PAGES ((RAM_SIZE)/(PAGE_SIZE)) 6 | 7 | /* A memory region tracks a block of bytes, be that ROM, RAM or IO Port*/ 8 | struct MEMORY_REGION { 9 | uint32_t address; 10 | uint32_t length; 11 | uint8_t *memory; 12 | }; 13 | 14 | /* RAM contains an array of MEMORY_REGIONS which may (or not) be mapped into the 6502's 64K*/ 15 | typedef struct MEMORY_REGION RAM_BANK; 16 | struct RAM { 17 | RAM_BANK *ram_banks; 18 | uint16_t num_ram_banks; 19 | }; 20 | typedef struct RAM RAM; 21 | 22 | /* ROMS contain rom "chips" */ 23 | typedef struct MEMORY_REGION ROM; 24 | struct ROMS { 25 | ROM *rom; 26 | uint16_t num_roms; 27 | }; 28 | typedef struct ROMS ROMS; 29 | 30 | /* PAGE points to a block of bytes, with a length of PAGE_SIZE (implied) */ 31 | struct PAGE { 32 | uint8_t *memory; 33 | }; 34 | typedef struct PAGE PAGE; 35 | 36 | /* PAGES is an array of PAGE structures. These can be mapped (or not) into teh 6502 address space */ 37 | struct PAGES { 38 | PAGE *pages; 39 | uint16_t num_pages; 40 | }; 41 | typedef struct PAGES PAGES; 42 | 43 | /* The 6502 internals*/ 44 | struct CPU { 45 | uint16_t pc; // Program counter 46 | uint16_t sp; // Stack pointer 47 | uint8_t A, X, Y; // 8 bit registers 48 | union { 49 | struct { 50 | uint8_t C: 1; // carry 51 | uint8_t Z: 1; // zero 52 | uint8_t I: 1; // Interrupt Disable 53 | uint8_t D: 1; // BCD mode 54 | uint8_t B: 1; // Break 55 | uint8_t E: 1; // Extra (almost unused) 56 | uint8_t V: 1; // Overflow 57 | uint8_t N: 1; // Negative 58 | }; 59 | uint8_t flags; 60 | }; 61 | union { 62 | struct { 63 | uint8_t address_lo; 64 | uint8_t address_hi; 65 | } ; 66 | uint16_t address_16; // For Emulation - Usually where bytes will be fetched 67 | }; 68 | union { 69 | struct { 70 | uint8_t scratch_lo; 71 | uint8_t scratch_hi; 72 | } ; 73 | uint16_t scratch_16; // For Emulation - Placeholder 74 | }; 75 | struct { 76 | uint8_t page_fault: 1; // During stages where a page-fault could happen, denotes fault 77 | }; 78 | uint8_t instruction; // Current instruction being executed 79 | int16_t instruction_cycle; // Stage (value 0, instruction fetched, is cycle 1 of execution, -1 need instruction) 80 | uint64_t cycles; // Total count of cycles the cpu has executed 81 | } ; 82 | typedef struct CPU CPU; 83 | 84 | // Forward declarations 85 | struct MACHINE; 86 | typedef struct MACHINE MACHINE; 87 | 88 | // Function pointer prototype that point at the steps, each individual cycle, of a 6502 instruction 89 | typedef void (*opcode_steps)(MACHINE *m); 90 | 91 | // Prototypes for callbacks when cpu accesses a port 92 | typedef uint8_t (*IO_READ)(MACHINE *m, uint16_t address); 93 | typedef void (*IO_WRITE)(MACHINE *m, uint16_t address, uint8_t value); 94 | 95 | // The emulated machine (computer) 96 | struct MACHINE 97 | { 98 | CPU cpu; // 6502 99 | PAGES read_pages; // Up to 64K of memory currently visible to CPU when reading 100 | PAGES write_pages; // Up to 64K of memory currently visible to CPU when writing 101 | PAGES io_pages; // Up to 64K of bytes where 0 means this is RAM/ROM and 1 means it is a port 102 | IO_READ io_read; // The callback when reading from a port 103 | IO_WRITE io_write; // The callback when writing to a port 104 | RAM ram; // All RAM in the system (may be > 64K but up to 64K) mapped in throug pages 105 | ROMS roms; // All ROMS in the system, may be mapped into 64K, through read_pages 106 | }; 107 | typedef struct MACHINE MACHINE; 108 | 109 | // The 256 possible opcodes 110 | extern opcode_steps *opcodes[256]; 111 | // The UNDEFINED step (cycle) is for the unimplemented opcodes 112 | extern opcode_steps UNDEFINED[]; 113 | 114 | // Configure the ram, ROMS and memory setup (what is mapped in) 115 | uint8_t ram_init(RAM *ram, uint16_t num_ram_banks); 116 | void ram_add(RAM *ram, uint8_t ram_bank_num, uint32_t address, uint32_t length, uint8_t *memory); 117 | uint8_t roms_init(ROMS *roms, uint16_t num_roms); 118 | void rom_add(ROMS *roms, uint8_t rom_num, uint32_t address, uint32_t length, uint8_t *memory); 119 | uint8_t pages_init(PAGES *pages, uint16_t num_pages); 120 | void pages_map(PAGES *pages, uint32_t start_page, uint32_t num_pages, uint8_t *memory); 121 | 122 | // 1 time init 123 | void cpu_init(CPU *cpu); 124 | 125 | // Step the machine a single CPU cycle 126 | void machine_step(MACHINE *m); 127 | 128 | -------------------------------------------------------------------------------- /src/mmm/src/mminer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern uint8_t manic_miner[]; 4 | extern const int manic_miner_size; -------------------------------------------------------------------------------- /src/mmm/src/mmm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "6502.h" 8 | #include "mminer.h" 9 | 10 | // Apple II sim related 11 | #define CPU_FREQUENCY 1022727 // 1 MHz (cycles/sec) NTSC = 14.31818 MHz / 14, PAL = 14.25 MHz / 14 12 | #define TARGET_FPS (1000000 / 60) // (cycles/sec) / (updates/sec) = cycles/update 13 | 14 | MACHINE m; // The container for the CPU, RAM, etc 15 | uint8_t RAM_MAIN[RAM_SIZE]; // The 64K RAM 16 | uint8_t RAM_IO[RAM_SIZE]; // 64K of IO port "mask" (0 = is not a port) 17 | 18 | #define MLI 0xBF00 // The Machine Language Interface (MM only calls it to quit) 19 | #define KBD 0xC000 // Port where the Apple II reads what keys are down 20 | #define KBDSTRB 0xC010 // Port where the Apple II acknowledges a key press (clears it) 21 | #define SPEAKER 0xC030 // Port that toggles the speaker 22 | #define LOWSCR 0xC054 // Port that draw Hires from $2000 23 | #define HISCR 0xC055 // Port that draws Hires from $4000 24 | 25 | // The start of a line of pixels (192 rows) in HGR memory. 26 | // (Add to active Hires page ie $2000 or $4000) 27 | uint16_t row_start[] = { 28 | 0x0000,0x0400,0x0800,0x0C00,0x1000,0x1400,0x1800,0x1C00, 29 | 0x0080,0x0480,0x0880,0x0C80,0x1080,0x1480,0x1880,0x1C80, 30 | 0x0100,0x0500,0x0900,0x0D00,0x1100,0x1500,0x1900,0x1D00, 31 | 0x0180,0x0580,0x0980,0x0D80,0x1180,0x1580,0x1980,0x1D80, 32 | 0x0200,0x0600,0x0A00,0x0E00,0x1200,0x1600,0x1A00,0x1E00, 33 | 0x0280,0x0680,0x0A80,0x0E80,0x1280,0x1680,0x1A80,0x1E80, 34 | 0x0300,0x0700,0x0B00,0x0F00,0x1300,0x1700,0x1B00,0x1F00, 35 | 0x0380,0x0780,0x0B80,0x0F80,0x1380,0x1780,0x1B80,0x1F80, 36 | 0x0028,0x0428,0x0828,0x0C28,0x1028,0x1428,0x1828,0x1C28, 37 | 0x00A8,0x04A8,0x08A8,0x0CA8,0x10A8,0x14A8,0x18A8,0x1CA8, 38 | 0x0128,0x0528,0x0928,0x0D28,0x1128,0x1528,0x1928,0x1D28, 39 | 0x01A8,0x05A8,0x09A8,0x0DA8,0x11A8,0x15A8,0x19A8,0x1DA8, 40 | 0x0228,0x0628,0x0A28,0x0E28,0x1228,0x1628,0x1A28,0x1E28, 41 | 0x02A8,0x06A8,0x0AA8,0x0EA8,0x12A8,0x16A8,0x1AA8,0x1EA8, 42 | 0x0328,0x0728,0x0B28,0x0F28,0x1328,0x1728,0x1B28,0x1F28, 43 | 0x03A8,0x07A8,0x0BA8,0x0FA8,0x13A8,0x17A8,0x1BA8,0x1FA8, 44 | 0x0050,0x0450,0x0850,0x0C50,0x1050,0x1450,0x1850,0x1C50, 45 | 0x00D0,0x04D0,0x08D0,0x0CD0,0x10D0,0x14D0,0x18D0,0x1CD0, 46 | 0x0150,0x0550,0x0950,0x0D50,0x1150,0x1550,0x1950,0x1D50, 47 | 0x01D0,0x05D0,0x09D0,0x0DD0,0x11D0,0x15D0,0x19D0,0x1DD0, 48 | 0x0250,0x0650,0x0A50,0x0E50,0x1250,0x1650,0x1A50,0x1E50, 49 | 0x02D0,0x06D0,0x0AD0,0x0ED0,0x12D0,0x16D0,0x1AD0,0x1ED0, 50 | 0x0350,0x0750,0x0B50,0x0F50,0x1350,0x1750,0x1B50,0x1F50, 51 | 0x03D0,0x07D0,0x0BD0,0x0FD0,0x13D0,0x17D0,0x1BD0,0x1FD0 52 | }; 53 | 54 | // RGB values 55 | struct mRGB { 56 | uint8_t c[3]; 57 | }; 58 | typedef struct mRGB mRGB; 59 | 60 | // The Apple II color palette as RGB values 61 | mRGB palette[8] = { 62 | {0X00, 0X00, 0X00}, // Black 63 | {0X46, 0XC4, 0X45}, // Green 64 | {0XDD, 0X6D, 0XEA}, // Violet 65 | {0XFF, 0XFF, 0XFF}, // White 66 | {0X00, 0X00, 0X00}, // Black 67 | {0XE2, 0X84, 0X3A}, // Orange 68 | {0X30, 0XA7, 0XEB}, // Blue 69 | {0XFF, 0XFF, 0XFF}, // White 70 | }; 71 | 72 | // Global variables to emulate speaker clicks 73 | #define SAMPLE_RATE 44100 74 | enum { 75 | SAMPLE_PREVIOUS, 76 | OUTPUT_PREVIOUS, 77 | SAMPLE_CURRENT, 78 | NUM_SAMPLES, 79 | }; 80 | struct A2SPEAKER { 81 | float speaker_state; 82 | float sample_rate; 83 | float samples[NUM_SAMPLES]; 84 | }; 85 | typedef struct A2SPEAKER A2SPEAKER; 86 | // Speaker should be part of MACHINE but I don't want to modify 6502.? 87 | A2SPEAKER speaker; 88 | 89 | // Global handles for SDL rendering 90 | SDL_Window *window; 91 | SDL_Renderer *renderer; 92 | SDL_Surface *surface; 93 | SDL_Texture *texture; 94 | int screen_updated = TARGET_FPS; // Counter - at TARGET_FPS forces screen update 95 | int active_page = 0x4000; // 0x2000 or 0x4000 - active hires memory page 96 | 97 | // Present the selected HiRes screen (page) 98 | void show_screen(uint16_t page) { 99 | int x, y; 100 | uint32_t *pixels = (uint32_t *)surface->pixels; 101 | 102 | // Loop through each row 103 | for (y = 0; y < 192; y++) { 104 | // Get the pointer to the start of the row in the SDL surface 105 | uint32_t *p = &pixels[y * surface->w]; 106 | // An index to "walk" the pixels in the surface 107 | int px = 0; 108 | // Get the address where this row starts in HGR memory 109 | int address = page + row_start[y]; 110 | 111 | // Loop through every 2 bytes (40 iterations for each 280-pixel row - 140 color pixels) 112 | for (int x = 0; x < 40; x += 2) { 113 | // Combine two bytes into a 16-bit value for ease of extracting pixels 114 | uint16_t col = (RAM_MAIN[address + x + 1] << 8) | RAM_MAIN[address + x]; 115 | 116 | // Extract the phase bits 117 | int ph1 = (col & 0b0000000010000000) >> 5; 118 | int ph2 = (col & 0b1000000000000000) >> 13; 119 | 120 | // Extract pixels and offset for phase bits 121 | int p1 = ph1 + ((col & 0b0000000000000001) << 1 | (col & 0b0000000000000010) >> 1 ); 122 | int p2 = ph1 + ((col & 0b0000000000000100) >> 1 | (col & 0b0000000000001000) >> 3 ); 123 | int p3 = ph1 + ((col & 0b0000000000010000) >> 3 | (col & 0b0000000000100000) >> 5 ); 124 | int p4 = ph1 + ((col & 0b0000000001000000) >> 5 | (col & 0b0000000100000000) >> 8 ); 125 | int p5 = ph2 + ((col & 0b0000001000000000) >> 8 | (col & 0b0000010000000000) >> 10); 126 | int p6 = ph2 + ((col & 0b0000100000000000) >> 10 | (col & 0b0001000000000000) >> 12); 127 | int p7 = ph2 + ((col & 0b0010000000000000) >> 12 | (col & 0b0100000000000000) >> 14); 128 | 129 | // Set the pixel value from the palette 130 | p[px++] = SDL_MapRGB(surface->format, palette[p1].c[0], palette[p1].c[1], palette[p1].c[2]); 131 | p[px++] = SDL_MapRGB(surface->format, palette[p2].c[0], palette[p2].c[1], palette[p2].c[2]); 132 | p[px++] = SDL_MapRGB(surface->format, palette[p3].c[0], palette[p3].c[1], palette[p3].c[2]); 133 | p[px++] = SDL_MapRGB(surface->format, palette[p4].c[0], palette[p4].c[1], palette[p4].c[2]); 134 | p[px++] = SDL_MapRGB(surface->format, palette[p5].c[0], palette[p5].c[1], palette[p5].c[2]); 135 | p[px++] = SDL_MapRGB(surface->format, palette[p6].c[0], palette[p6].c[1], palette[p6].c[2]); 136 | p[px++] = SDL_MapRGB(surface->format, palette[p7].c[0], palette[p7].c[1], palette[p7].c[2]); 137 | } 138 | } 139 | 140 | // Rendering the texture 141 | SDL_UpdateTexture(texture, NULL, surface->pixels, surface->pitch); 142 | SDL_RenderCopy(renderer, texture, NULL, NULL); 143 | SDL_RenderPresent(renderer); 144 | 145 | // Mark screen as updated now 146 | screen_updated = 0; 147 | } 148 | 149 | // Handle the Apple II ports used by Manic Miner 150 | uint8_t io_read_callback(MACHINE *m, uint16_t address) { 151 | switch(address) { 152 | case KBDSTRB: 153 | RAM_MAIN[KBD] &= 0x7F; 154 | break; 155 | case SPEAKER: 156 | speaker.speaker_state = 1.0f - speaker.speaker_state; 157 | break; 158 | case LOWSCR: 159 | active_page = 0x2000; 160 | show_screen(0x2000); 161 | break; 162 | case HISCR: 163 | active_page = 0x4000; 164 | show_screen(0x4000); 165 | break; 166 | } 167 | return m->read_pages.pages[address / PAGE_SIZE].memory[address % PAGE_SIZE]; 168 | } 169 | 170 | // Writing to these ports is the same as reading from them 171 | void io_write_callback(MACHINE *m, uint16_t address, uint8_t value) { 172 | io_read_callback(m, address); 173 | } 174 | 175 | // Set MACHINE up as an Apple II 176 | int AppleII_configure(MACHINE *m) { 177 | 178 | // Load the Manic Miner logo HGR and the game itself 179 | memcpy(&RAM_MAIN[0x4000], manic_miner, manic_miner_size); 180 | 181 | // RAM 182 | if(!ram_init(&m->ram, 1)) { 183 | return 0; 184 | } 185 | ram_add(&m->ram, 0, 0x0000, RAM_SIZE, RAM_MAIN); 186 | 187 | // PAGES 188 | if(!pages_init(&m->read_pages, RAM_SIZE / PAGE_SIZE)) { 189 | return 0; 190 | } 191 | if(!pages_init(&m->write_pages, RAM_SIZE / PAGE_SIZE)) { 192 | return 0; 193 | } 194 | if(!pages_init(&m->io_pages, RAM_SIZE / PAGE_SIZE)) { 195 | return 0; 196 | } 197 | 198 | // Map main write ram 199 | pages_map(&m->write_pages, m->ram.ram_banks[0].address / PAGE_SIZE, m->ram.ram_banks[0].length / PAGE_SIZE, m->ram.ram_banks[0].memory); 200 | 201 | // Map read ram (same as write ram in this case) 202 | pages_map(&m->read_pages , m->ram.ram_banks[0].address / PAGE_SIZE, m->ram.ram_banks[0].length / PAGE_SIZE, m->ram.ram_banks[0].memory); 203 | 204 | // Install the IO callbacks 205 | m->io_write = io_write_callback; 206 | m->io_read = io_read_callback; 207 | 208 | // Map IO area checks - start with no IO 209 | memset(RAM_IO, 0, RAM_SIZE); 210 | 211 | // Add the IO ports the game uses 212 | RAM_IO[KBDSTRB] = 1; 213 | RAM_IO[SPEAKER] = 1; 214 | RAM_IO[LOWSCR] = 1; // Page 1 in MM speak 215 | RAM_IO[HISCR] = 1; // Page 2 in MM speak 216 | pages_map(&m->io_pages, 0, RAM_SIZE / PAGE_SIZE, RAM_IO); 217 | 218 | cpu_init(&m->cpu); 219 | // The game code starts at $6000 220 | m->cpu.pc = 0x6000; 221 | // Override the JMP instruction there from a cold start 222 | m->cpu.instruction_cycle = -1; 223 | 224 | return 1; 225 | } 226 | 227 | // Set up SDL graphics and Audio 228 | int init_sdl() { 229 | // Initialize SDL with video and audio 230 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) { 231 | printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); 232 | return 0; 233 | } 234 | 235 | // Create window 236 | window = SDL_CreateWindow("Manic Miner Machine", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN); 237 | if (window == NULL) { 238 | printf("Window could not be created! SDL_Error: %s\n", SDL_GetError()); 239 | return 0; 240 | } 241 | 242 | // Create renderer 243 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); 244 | if (renderer == NULL) { 245 | printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError()); 246 | return 0; 247 | } 248 | 249 | // Create RGB surface 250 | surface = SDL_CreateRGBSurface(0, 140, 192, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); 251 | if(surface == NULL) { 252 | printf("Surface could not be created! SDL_Error: %s\n", SDL_GetError()); 253 | return 0; 254 | } 255 | 256 | // Create texture for pixel rendering 257 | texture = SDL_CreateTextureFromSurface(renderer, surface); 258 | if(texture == NULL) { 259 | printf("Texture could not be created! SDL_Error: %s\n", SDL_GetError()); 260 | return 0; 261 | } 262 | 263 | // Now init the Audio 264 | SDL_AudioSpec wanted_spec; 265 | SDL_AudioSpec obtained_spec; 266 | 267 | // Set the desired audio format 268 | wanted_spec.freq = SAMPLE_RATE; 269 | wanted_spec.format = AUDIO_F32SYS; 270 | wanted_spec.channels = 2; // Stereo sound 271 | wanted_spec.samples = 4096; // Buffer size 272 | wanted_spec.callback = NULL; // No callback 273 | 274 | // Open the audio device 275 | if (SDL_OpenAudio(&wanted_spec, &obtained_spec) < 0) { 276 | printf("Couldn't open audio! SDL_Error: %s\n", SDL_GetError()); 277 | return 0; 278 | } 279 | 280 | // Set up the speaker now that the obtained freq is known 281 | memset(&speaker, 0, sizeof(speaker)); 282 | // Should be obtained_spec.freq instead of SAMPLE_RATE, I think, but that doesn't work on macOS 283 | // The 1.5 is a fudge number to make sure the audio doesn't lag 284 | speaker.sample_rate = ((float)CPU_FREQUENCY / SAMPLE_RATE) + 1.5f; 285 | 286 | return 1; 287 | } 288 | 289 | int main(int argc, char* argv[]) { 290 | int quit = 0; 291 | SDL_Event e; 292 | float sample_cycles; 293 | Uint64 start_time, end_time; 294 | Uint64 ticks_per_clock_cycle = SDL_GetPerformanceFrequency() / CPU_FREQUENCY; // Ticks per microsecond 295 | 296 | if(!init_sdl()) { 297 | SDL_Quit(); 298 | return 1; 299 | } 300 | 301 | // Make this machine an Apple II and load Manic Miner into RAM 302 | AppleII_configure(&m); 303 | 304 | // Start the audio 305 | SDL_PauseAudio(0); 306 | 307 | // After init_sdl that sets up speaker.sample_rate 308 | sample_cycles = speaker.sample_rate; 309 | const float alpha = 0.98f; // High pass filter coefficient 310 | const float beta = 0.98f; // Low pass filter coefficient 311 | 312 | // Start running the sim loop 313 | while (!quit) { 314 | // Take note of the time to help sync to Apple II speed 315 | start_time = SDL_GetPerformanceCounter(); 316 | 317 | // Process all SDL events 318 | while (SDL_PollEvent(&e) != 0) { 319 | // Set the last key pressed in the "port" 320 | if (e.type == SDL_KEYDOWN) { 321 | RAM_MAIN[KBD] = 0x80 | e.key.keysym.sym; 322 | } 323 | 324 | // If the user closes the window 325 | if (e.type == SDL_QUIT) { 326 | quit = 1; 327 | } 328 | } 329 | 330 | // Step the sim one instruction 331 | int cycles = 0; 332 | do { 333 | // Step the sim one cycle 334 | machine_step(&m); 335 | cycles++; 336 | speaker.samples[SAMPLE_CURRENT] += speaker.speaker_state; 337 | if(--sample_cycles <= 0.0f) { 338 | float output_previous = speaker.samples[OUTPUT_PREVIOUS]; 339 | float sample_previous = speaker.samples[SAMPLE_PREVIOUS]; 340 | float sample_current = speaker.samples[SAMPLE_CURRENT]; 341 | // Calculate a high pass and low pass filtered sample 342 | float high_pass_result = alpha * (output_previous + sample_current - sample_previous); 343 | float filter_result = beta * high_pass_result + (1 - beta) * output_previous; 344 | // Save the current sample of next time 345 | speaker.samples[SAMPLE_PREVIOUS] = sample_current; 346 | // And make the filtered sample prev and also use as left and right for SDL 347 | speaker.samples[OUTPUT_PREVIOUS] = filter_result; 348 | speaker.samples[SAMPLE_CURRENT] = filter_result; 349 | // Queue the stero samples 350 | SDL_QueueAudio(1, &speaker.samples[OUTPUT_PREVIOUS], 2 * sizeof(float)); 351 | // Start a new sample 352 | speaker.samples[SAMPLE_CURRENT] = 0.0f; 353 | // Reset when the next samples will be queued 354 | sample_cycles += speaker.sample_rate; 355 | 356 | } 357 | } while(m.cpu.instruction_cycle != -1); 358 | 359 | // If a call was made to the MLI, it's to QUIT 360 | if(m.cpu.pc == MLI) { 361 | quit = 1; 362 | } 363 | 364 | // If the page hasn't been "switched", force an update of the current page 365 | if(++screen_updated >= TARGET_FPS) { 366 | show_screen(active_page); 367 | } 368 | 369 | // When the speaker is not being toggled, it needs to turn off 370 | end_time = SDL_GetPerformanceCounter(); 371 | 372 | // Try to lock the SIM to the Apple II 1.023 MHz 373 | while ((end_time - start_time) < (ticks_per_clock_cycle * cycles)) { 374 | end_time = SDL_GetPerformanceCounter(); 375 | } 376 | } 377 | 378 | // Cleanup 379 | SDL_DestroyTexture(texture); 380 | SDL_DestroyWindow(window); 381 | SDL_CloseAudio(); 382 | IMG_Quit(); 383 | SDL_Quit(); 384 | return 0; 385 | } 386 | --------------------------------------------------------------------------------