├── .github └── FUNDING.yml ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── demo ├── demo_3d.z80 ├── demo_colourbars.z80 ├── demo_scroll.z80 ├── demo_sprites.z80 └── demo_vector.z80 └── lib ├── gui.z80 ├── keyboard.z80 ├── macros.z80 ├── math.z80 ├── output.z80 ├── score.z80 ├── screen_buffer.z80 ├── scroll.z80 ├── scroll_attr.z80 ├── sound.z80 ├── sprite.z80 ├── sprite_masked.z80 ├── vector.z80 └── vector_filled.z80 /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: breakintoprogram 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Z80 Debugger", 9 | "type": "dezog", 10 | "request": "launch", 11 | "remoteType": "zrcp", 12 | "zrcp": { 13 | "hostname": "localhost", 14 | "port": 10000 15 | }, 16 | "topOfStack": "Stack_Top", 17 | "rootFolder": "${fileDirname}", 18 | "listFiles": [ 19 | { 20 | "path": "${fileDirname}/${fileBasenameNoExtension}.lst", 21 | "asm": "sjasmplus", 22 | "useFiles": true, 23 | "mainFile": "${fileDirname}/${fileBasenameNoExtension}.z80", 24 | "srcDirs": [ "lib" ] 25 | } 26 | ], 27 | "disassemblerArgs": { 28 | "esxdosRst": true 29 | }, 30 | "load": "${fileDirname}/${fileBasenameNoExtension}.sna", 31 | "skipInterrupt": false, 32 | "startAutomatically": true, 33 | "preLaunchTask": "sjasmplus" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "sjasmplus", 8 | "type": "shell", 9 | "command": "sjasmplus", 10 | "args": [ 11 | "--lst=${fileDirname}/${fileBasenameNoExtension}.lst", 12 | "${file}" 13 | ], 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dean Belfield 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lib-spectrum 2 | #### ZX Spectrum Library Routines 3 | A suite of Z80 modules for the ZX Spectrum (all models) with routines for: 4 | - Reading the keyboard 5 | - Maths routines 6 | - Printing text to screen 7 | - Screen buffering for off-screen rendering 8 | - Fast tiled vertical scroll routine (25fps with sprites) 9 | - Beeper audio 10 | - Sprites 11 | - Vector graphics (fast line, circle, and plot routines) 12 | - Filled vector graphics with simple flat texturing (triangle, quadrilateral and circle) 13 | 14 | ##### Build the demos 15 | - The code is written to cross-assemble in sjasmplus on PC, Mac or Linux 16 | - Visual Studio Code files included in .vscode folder for build and debug 17 | - Demo files in the /Demo directory included to give you a quick-start 18 | 19 | For more information visit [Break Into Program](http://www.breakintoprogram.co.uk/programming/assembly-language/z80/z80-development-toolchain "Break Into Program") 20 | -------------------------------------------------------------------------------- /demo/demo_3d.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: 3D Demo 3 | ; Author: Dean Belfield 4 | ; Started: 01/04/2020 5 | ; Last Updated: 11/04/2020 6 | ; 7 | ; Requires: macros, vector, vector_filled, output, keyboard, screen_buffer, sound, sprite, math 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 11/04/2020: Draws direct to screen if SCREEN_BUFFER is omitted 12 | 13 | DEVICE ZXSPECTRUM48 ; sjasmplus directive for SAVESNA at end 14 | 15 | Stack_Top: EQU 0xFFF0 16 | Code_Start: EQU 0x7FFD 17 | 18 | ORG Code_Start 19 | 20 | JP Main 21 | 22 | SIN_TABLE: LUA ALLPASS 23 | for i = 0, 255 do 24 | s = math.sin(i/128*math.pi) 25 | t = s > 0 and 1 or s < 0 and -1 or 0 26 | s = 2 * math.floor(math.abs(s)*128) 27 | if t == -1 then s = s + 1 end 28 | if s >= 256 then s = 255 end 29 | _pc(string.format("DB %d", s)) 30 | end 31 | ENDLUA 32 | 33 | DEFINE SCREEN_BUFFER 0xE000 ; Plot / draw to off-screen buffer 34 | 35 | SHAPE_BUFFER: DS 256 ; This should be aligned on a byte boundary 36 | POINT_BUFFER: DS 16 37 | POLYGON_BUFFER: DS 8,0 38 | SD: EQU 256 39 | YC: DW 0 40 | XC: DW 0 41 | ZC: DW 256 42 | PHI: DB 0 43 | THE: DB 0 44 | PSI: DB 0 45 | Debounce_Key: DB 0xFF 46 | 47 | include "../lib/macros.z80" 48 | include "../lib/vector.z80" 49 | include "../lib/vector_filled.z80" 50 | include "../lib/output.z80" 51 | include "../lib/keyboard.z80" 52 | include "../lib/screen_buffer.z80" 53 | include "../lib/sound.z80" 54 | include "../lib/sprite.z80" 55 | include "../lib/math.z80" 56 | 57 | Main: DI 58 | LD SP,Stack_Top 59 | LD A,46h 60 | CALL Clear_Screen 61 | LD A,0 62 | OUT (254),A 63 | LD HL,Interrupt 64 | LD IX,0xFFF0 65 | LD (IX+04h),0xC3 ; Opcode for JP 66 | LD (IX+05h),L 67 | LD (IX+06h),H 68 | LD (IX+0Fh),0x18 ; Opcode for JR; this will do JR to FFF4h 69 | LD A,0x39 70 | LD I,A 71 | LD SP,0xFFF0 72 | IM 2 73 | EI 74 | 75 | LOOP: DI 76 | IFDEF SCREEN_BUFFER 77 | LD HL,SCREEN_BUFFER 78 | ELSE 79 | LD HL,0x4000 80 | ENDIF 81 | CALL Clear_Screen_Fast 82 | LD HL,Vector_Texture_03 83 | LD B,150 84 | LD C,88 85 | LD A,55 86 | CALL Draw_Circle_Filled 87 | LD IX,Cube 88 | CALL Translate 89 | CALL Draw_Shape 90 | IFDEF SCREEN_BUFFER 91 | LD HL,SCREEN_BUFFER 92 | CALL Copy_Screen 93 | ENDIF 94 | EI 95 | 96 | LD A,(THE): ADD A,3: LD (THE),A 97 | LD A,(PSI): ADD A,1: LD (PSI),A 98 | LD A,(PHI): ADD A,2: LD (PHI),A 99 | 100 | JR LOOP 101 | 102 | ; Handle user input 103 | ; 104 | User_Interface: CALL Read_Keyboard ; Read the keyboard 105 | PUSH AF ; Stack the keypress 106 | JR Z,User_Interface_End ; No keys pressed; jump to end 107 | 108 | LD HL,Debounce_Key ; Check whether we've pressed this key before 109 | CP (HL) 110 | JR Z,User_Interface_End ; Yes we have, so skip the debounced keys 111 | 112 | User_Interface_End: POP AF ; Unstack the keypress 113 | LD (Debounce_Key),A ; Store for debounce 114 | RET 115 | ; 116 | ; Interrupt routine 117 | ; 118 | Interrupt: DI 119 | PUSH AF 120 | PUSH BC 121 | PUSH DE 122 | PUSH HL 123 | PUSH IX 124 | EXX 125 | EX AF,AF' 126 | PUSH AF 127 | PUSH BC 128 | PUSH DE 129 | PUSH HL 130 | PUSH IY 131 | 132 | LD A,(Debounce_Key) 133 | AND A 134 | JR Z,Interrupt_End 135 | 136 | Interrupt_End: CALL User_Interface 137 | 138 | POP IY 139 | POP HL 140 | POP DE 141 | POP BC 142 | POP AF 143 | EXX 144 | EX AF,AF' 145 | POP IX 146 | POP HL 147 | POP DE 148 | POP BC 149 | POP AF 150 | EI 151 | RET 152 | 153 | ; Render the shape 154 | ; IX: Coordinate data 155 | ; 156 | Draw_Shape: LD IY,POINT_BUFFER 157 | LD B,(IX+1) 158 | 1: LD DE,POINT_BUFFER 159 | PUSH BC 160 | LD A,(IX+2) 161 | ADD A,A 162 | ADD A,A 163 | LD H,high SHAPE_BUFFER 164 | LD L,A 165 | LDI 166 | LDI 167 | LDI 168 | LDI 169 | LD A,(IX+3) 170 | ADD A,A 171 | ADD A,A 172 | LD H,high SHAPE_BUFFER 173 | LD L,A 174 | LDI 175 | LDI 176 | LDI 177 | LDI 178 | LD A,(IX+4) 179 | ADD A,A 180 | ADD A,A 181 | LD H,high SHAPE_BUFFER 182 | LD L,A 183 | LDI 184 | LDI 185 | LDI 186 | LDI 187 | LD A,(IX+5) 188 | ADD A,A 189 | ADD A,A 190 | LD H,high SHAPE_BUFFER 191 | LD L,A 192 | LDI 193 | LDI 194 | LDI 195 | LDI 196 | CALL Backface_Cull 197 | LD L,(IX+6) 198 | LD H,(IX+7) 199 | CALL NZ,Draw_Polygon 200 | LD BC,6 201 | ADD IX,BC 202 | POP BC 203 | DJNZ 1B 204 | RET 205 | 206 | ; Draw a polygon 207 | ; 208 | Draw_Polygon: LD A,(IY+0): LD (POLYGON_BUFFER+0),A 209 | LD A,(IY+2): LD (POLYGON_BUFFER+1),A 210 | LD A,(IY+4): LD (POLYGON_BUFFER+2),A 211 | LD A,(IY+6): LD (POLYGON_BUFFER+3),A 212 | LD A,(IY+8): LD (POLYGON_BUFFER+4),A 213 | LD A,(IY+10): LD (POLYGON_BUFFER+5),A 214 | LD A,(IY+12): LD (POLYGON_BUFFER+6),A 215 | LD A,(IY+14): LD (POLYGON_BUFFER+7),A 216 | PUSH IX 217 | PUSH IY 218 | LD IY,POLYGON_BUFFER 219 | CALL Draw_Quad_Filled 220 | POP IY 221 | POP IX 222 | RET 223 | ; Translate 224 | ; IX: Coordinate data 225 | ; 226 | Translate: LD B,(IX+0) 227 | XOR A 228 | LD (DIVISOR+2),A 229 | LD IY,SHAPE_BUFFER 230 | 0: PUSH BC 231 | LD E,(IX+3) 232 | LD D,(IX+2) 233 | LD A,(PHI) 234 | CALL Trig_Rotate 235 | LD E,A 236 | EX AF,AF 237 | PUSH AF 238 | LD D,(IX+1) 239 | LD A,(THE) 240 | CALL Trig_Rotate 241 | LD HL,(ZC) 242 | CP 128 243 | CCF 244 | LD E,A 245 | SBC A,A 246 | LD D,A 247 | SBC HL,DE 248 | LD (DIVISOR),HL 249 | EX AF,AF 250 | LD D,A 251 | POP AF 252 | LD E,A 253 | LD A,(PSI) 254 | CALL Trig_Rotate 255 | EX AF,AF 256 | PUSH AF 257 | EX AF,AF 258 | 259 | LD HL,(YC) 260 | CALL Perspective 261 | LD DE,96 262 | ADD HL,DE 263 | LD (IY+2),L 264 | LD (IY+3),H 265 | POP AF 266 | LD HL,(XC) 267 | CALL Perspective 268 | LD DE,128 269 | ADD HL,DE 270 | LD (IY+0),L 271 | LD (IY+1),H 272 | 273 | LD BC,3 274 | ADD IX,BC 275 | INC BC 276 | ADD IY,BC 277 | POP BC 278 | DJNZ 0B 279 | RET 280 | 281 | Perspective: CP 128 282 | CCF 283 | LD E,A 284 | SBC A,A 285 | LD D,A 286 | ADD HL,DE 287 | EX DE,HL 288 | BIT 7,D 289 | JR Z,1F 290 | XOR A 291 | LD H,A 292 | LD L,A 293 | SBC HL,DE 294 | EX DE,HL 295 | LD BC,SD 296 | CALL MUL24 297 | LD (DIVIDEND+2),A 298 | LD (DIVIDEND),HL 299 | CALL DIV24 300 | EX DE,HL 301 | LD HL,65535 302 | AND A 303 | SBC HL,DE 304 | CPL 305 | LD DE,1 306 | ADD HL,DE 307 | ADC A,0 308 | RET 309 | 1: LD BC,SD 310 | CALL MUL24 311 | LD (DIVIDEND+2),A 312 | LD (DIVIDEND),HL 313 | JP DIV24 314 | 315 | Backface_Cull: LD BC,(POINT_BUFFER) 316 | LD HL,(POINT_BUFFER+6) 317 | LD DE,(POINT_BUFFER+10) 318 | CALL 1F 319 | PUSH AF 320 | PUSH HL 321 | LD BC,(POINT_BUFFER+4) 322 | LD HL,(POINT_BUFFER+10) 323 | LD DE,(POINT_BUFFER+2) 324 | CALL 1F 325 | POP DE 326 | POP BC 327 | ADD HL,DE 328 | ADC A,B 329 | PUSH AF 330 | PUSH HL 331 | LD BC,(POINT_BUFFER+8) 332 | LD HL,(POINT_BUFFER+2) 333 | LD DE,(POINT_BUFFER+6) 334 | CALL 1F 335 | POP DE 336 | POP BC 337 | ADD HL,DE 338 | ADC A,B 339 | BIT 7,A 340 | RET 341 | 1: OR A 342 | SBC HL,DE 343 | EX DE,HL 344 | JP M,2F 345 | BIT 7,B 346 | JP Z,MUL24 347 | XOR A 348 | LD H,A 349 | LD L,A 350 | SBC HL,BC 351 | LD B,H 352 | LD C,L 353 | JP MUL24_NEG 354 | 2: XOR A 355 | LD H,A 356 | LD L,A 357 | SBC HL,DE 358 | EX DE,HL 359 | BIT 7,B 360 | JP Z,MUL24_NEG 361 | XOR A 362 | LD H,A 363 | LD L,A 364 | SBC HL,BC 365 | LD B,H 366 | LD C,L 367 | JP MUL24 368 | 369 | ; Trig rotation 370 | ; A =COS(D)+SIN(E) 371 | ; A'=COS(D)-SIN(E) 372 | ; 373 | Trig_Rotate: PUSH DE 374 | LD (Trig_Rotate1+1),A 375 | LD C,E 376 | CALL SIN 377 | LD E,A 378 | Trig_Rotate1: LD A,0 379 | LD C,D 380 | CALL COS 381 | SUB E 382 | EX AF,AF 383 | POP DE 384 | LD A,(Trig_Rotate1+1) 385 | LD C,E 386 | CALL COS 387 | LD E,A 388 | LD A,(Trig_Rotate1+1) 389 | LD C,D 390 | CALL SIN 391 | ADD A,E 392 | RET 393 | 394 | ; Basic trig functions 395 | ; A=TRIG(A)*C 396 | ; 397 | COS: ADD A,64 398 | SIN: LD H,high SIN_TABLE 399 | BIT 7,A 400 | JR NZ,1F 401 | LD L,A 402 | LD B,(HL) 403 | LD A,C 404 | AND A 405 | JP P,MUL8_DIV256 406 | NEG 407 | LD C,A 408 | JP MUL8_DIV256_NEG 409 | 1: AND 127 410 | LD L,A 411 | LD B,(HL) 412 | LD A,C 413 | AND A 414 | JP P,MUL8_DIV256_NEG 415 | NEG 416 | LD C,A 417 | JP MUL8_DIV256 418 | 419 | Cube: DB 8 420 | DB -40,40,40 421 | DB 40,40,40 422 | DB 40,-40,40 423 | DB -40,-40,40 424 | DB -40,40,-40 425 | DB 40,40,-40 426 | DB 40,-40,-40 427 | DB -40,-40,-40 428 | DB 6 429 | DB 0,1,2,3: DW Vector_Texture_00 430 | DB 0,4,5,1: DW Vector_Texture_01 431 | DB 7,6,5,4: DW Vector_Texture_02 432 | DB 3,2,6,7: DW Vector_Texture_03 433 | DB 1,5,6,2: DW Vector_Texture_00 434 | DB 0,3,7,4: DW Vector_Texture_01 435 | 436 | Code_Length: EQU $-Code_Start+1 437 | 438 | SAVESNA "demo/demo_3d.sna", Code_Start 439 | 440 | -------------------------------------------------------------------------------- /demo/demo_colourbars.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: Area 51 3 | ; Author: Dean Belfield 4 | ; Started: 20/12/2020 5 | ; Last Updated: 20/12/2020 6 | ; 7 | ; Requires: output, keyboard 8 | ; 9 | ; Modinfo: 10 | ; 11 | 12 | DEVICE ZXSPECTRUM48 ; sjasmplus directive for SAVESNA at end 13 | 14 | Stack_Top: EQU 0xFFF0 15 | Code_Start: EQU 0x8000 16 | 17 | ORG Code_Start 18 | 19 | JP Main 20 | 21 | include "../lib/output.z80" 22 | include "../lib/keyboard.z80" 23 | 24 | Main: DI 25 | LD SP,Stack_Top 26 | LD A,0x00 27 | OUT (254),A 28 | CALL Clear_Screen 29 | LD HL,Interrupt 30 | LD IX,0xFFF0 31 | LD (IX+04h),0xC3 ; Opcode for JP 32 | LD (IX+05h),L 33 | LD (IX+06h),H 34 | LD (IX+0Fh),0x18 ; Opcode for JR; this will do JR to FFF4h 35 | LD A,0x39 36 | LD I,A 37 | LD SP,0xFFF0 38 | IM 2 39 | EI 40 | 41 | LOOP: HALT ; Wait for NMI 42 | JR LOOP ; And loop 43 | 44 | ; 45 | ; Interrupt routine 46 | ; 47 | Interrupt: DI 48 | PUSH AF 49 | PUSH BC 50 | PUSH DE 51 | PUSH HL 52 | PUSH IX 53 | EXX 54 | EX AF,AF' 55 | PUSH AF 56 | PUSH BC 57 | PUSH DE 58 | PUSH HL 59 | PUSH IY 60 | 61 | LD DE,(Colour_Table_Address) ; DE = Colour table address 62 | LD BC,0x226F ; BC = Timing loop values 63 | LD A,192 ; A = Number of rows 64 | CALL Colourbars_Effect 65 | LD HL,Colour_Table_Address ; Increment the low byte of the colour table 66 | INC (HL) ; address to scroll it 67 | 68 | POP IY 69 | POP HL 70 | POP DE 71 | POP BC 72 | POP AF 73 | EXX 74 | EX AF,AF' 75 | POP IX 76 | POP HL 77 | POP DE 78 | POP BC 79 | POP AF 80 | EI 81 | RET 82 | 83 | ; Colourbars Routine 84 | ; DE: Address of colour table (on page boundary) 85 | ; C: Timing loop (course adjustmnt) 86 | ; B: Timing loop (fine adjustment) 87 | ; A: Number of rows to display (multiple of 8) 88 | ; 89 | Colourbars_Effect: LD (Colourbars_Effect_SP+1),SP ; Preserve the stack pointer 90 | 91 | EXX 92 | EX AF,AF' 93 | LD HL,0x5800-5 ; HL' = Attribute Address 94 | LD DE,32 ; DE' = Offset to next row of attributes 95 | LD A,1 ; AF' = Bit counter to count 8 rows 96 | EX AF,AF' 97 | EXX 98 | ; 99 | ; This bit is a timing loop to wait for the raster to get past the border 100 | ; 101 | 1: DJNZ 1B 102 | LD B,0x08 103 | DEC C 104 | JR NZ,1B 105 | ; 106 | ; Start the effect 107 | ; 108 | LD B,A ; Store loop counter in B 109 | 2: LD A,(DE) ; Fetch the colour from DE 110 | INC E ; Increment to next address in colour table 111 | EXX 112 | LD C,A ; BC' is the colour to write out using the stack 113 | LD B,A ; So A->B and C 114 | EX AF,AF' 115 | RRCA ; Rotate the bit counter; this will trip a carry once every 8 times 116 | JR NC,3F ; If there is no carry, then we redraw this row of attributes 117 | ADD HL,DE ; If there is a carry, move to next row of attributes 118 | JR 4F 119 | 3: DUP 4 ; This is to balance the timings if we've not done an ADD HL,DE 120 | NOP 121 | EDUP 122 | 4: LD SP,HL ; Set the SP to the attribute address in HL' 123 | DUP 11 ; Write out 22 bytes of colour 124 | PUSH BC 125 | EDUP 126 | EXX 127 | EX AF,AF' 128 | DJNZ 2B ; Loop to do next scanline 129 | 130 | Colourbars_Effect_SP: LD SP,0 ; Restore the stack pointer 131 | RET 132 | 133 | Colour_Table_Address: DW Colour_Table ; Pointer to the colour table 134 | ALIGN 256 ; Colour table must be page aligned 135 | Colour_Table: DUP 8 136 | DB %00000000,%00001000,%00001000,%01001000,%01001000,%00101000,%01101000,%01111000 137 | DB %01111000,%01101000,%00101000,%01001000,%01001000,%00001000,%00001000,%00000000 138 | DB %00000000,%00010000,%00010000,%01010000,%01010000,%00011000,%01011000,%01111000 139 | DB %01111000,%01011000,%00011000,%01010000,%01010000,%00010000,%00010000,%00000000 140 | EDUP 141 | 142 | Code_Length: EQU $-Code_Start+1 143 | 144 | SAVESNA "Z80/Demo/demo_colourbars.sna", Code_Start 145 | 146 | -------------------------------------------------------------------------------- /demo/demo_scroll.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: Scroll Demo 3 | ; Author: Dean Belfield 4 | ; Created: 27/01/2020 5 | ; Last updated: 06/05/2020 6 | ; 7 | ; Requires: keyboard, sprite_masked, scroll, scroll_attr 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 03/02/2020: Tileset information now stored in map data 12 | ; 11/04/2020: Changed call to RND16 13 | ; 06/05/2020: Added colour scrolling 14 | ; 15 | DEVICE ZXSPECTRUM48 ; sjasmplus directive for SAVESNA at end 16 | 17 | Stack_Top: EQU 0xFFF0 18 | Code_Start: EQU 0x8000 19 | 20 | ORG Code_Start 21 | 22 | JP MAIN ; JP past the included code to MAIN 23 | 24 | include "../lib/macros.z80" 25 | include "../lib/keyboard.z80" 26 | include "../lib/sprite_masked.z80" 27 | include "../lib/output.z80" 28 | include "../lib/scroll.z80" 29 | include "../lib/math.z80" 30 | 31 | ; MAIN 32 | ; - Initialise some stuff 33 | ; - Set up the interrupt routines 34 | ; NB: 35 | ; For details on how this interrupt routine works, read 36 | ; http://www.breakintoprogram.co.uk/computers/zx-spectrum/interrupts 37 | ; Will only work on the 48K Spectrum! 38 | ; 39 | MAIN: DI ; Disable interrupts 40 | LD SP, Stack_Top 41 | LD A,0x00 42 | OUT 254,A 43 | LD A,0x47 44 | CALL Clear_Screen ; Clear the screen 45 | CALL Initialise_Sprites ; Initialise the sprites 46 | LD HL, 16 * (Demo_Map_Length - 16) 47 | LD (Vertical_Scroll_Offset), HL 48 | LD HL,Interrupt 49 | LD IX,0xFFF0 ; This code is to be written at 0xFF 50 | LD (IX+04h),0xC3 ; Opcode for JP 51 | LD (IX+05h),L ; Store the address of the interrupt routine in 52 | LD (IX+06h),H 53 | LD (IX+0Fh),0x18 ; Opcode for JR; this will do JR to FFF4h 54 | LD A,0x39 ; Interrupt table at page 0x3900 (ROM) 55 | LD I,A ; Set the interrupt register to that page 56 | IM 2 ; Set the interrupt mode 57 | EI ; Enable interrupts 58 | 59 | LOOP: HALT ; Just loop round doing nothing now 60 | JR LOOP ; All the work is done in the Interrupt routine 61 | 62 | ; The Interrupt routine 63 | ; NB: 64 | ; The interrupts are disabled to ensure that this interrupt cannot be interrupted. 65 | ; 66 | Interrupt: DI ; Disable interrupts 67 | PUSH AF ; Save all the registers on the stack 68 | PUSH BC ; This is probably not necessary unless 69 | PUSH DE ; we're looking at returning cleanly 70 | PUSH HL ; back to BASIC at some point 71 | PUSH IX 72 | EXX 73 | EX AF,AF' 74 | PUSH AF 75 | PUSH BC 76 | PUSH DE 77 | PUSH HL 78 | PUSH IY 79 | 80 | ; LD A,1 ; Change the border colour 81 | ; OUT (254),A ; Used for timing routines visually 82 | LD HL, Demo_Map ; Initialise the scroll 83 | CALL Initialise_Scroll ; This creates all the self-modding code 84 | ; LD A,0 ; code in here like a music driver 85 | ; OUT (254),A 86 | CALL Scroll ; Render the scroll to the screen 87 | CALL Render_Sprites ; And render the sprites 88 | CALL Move_Scroll ; Move the scroll 89 | CALL Handle_Sprites ; Move the sprites 90 | CALL Scroll_Attr ; Add the colours 91 | ; LD A,0 92 | ; OUT (254),A 93 | 94 | POP IY ; Restore all the registers 95 | POP HL 96 | POP DE 97 | POP BC 98 | POP AF 99 | EXX 100 | EX AF,AF' 101 | POP IX 102 | POP HL 103 | POP DE 104 | POP BC 105 | POP AF 106 | EI ; Enable interrupts 107 | RET ; And return 108 | 109 | ; A simple delay loop 110 | ; 111 | Delay: DEC BC 112 | LD A,B 113 | OR C 114 | JR NZ, Delay 115 | RET 116 | 117 | ; Move the scroll offset 118 | ; 119 | Move_Scroll: LD HL, (Vertical_Scroll_Offset) 120 | Move_Scroll_Speed: LD BC, 1 121 | AND A 122 | SBC HL, BC 123 | JR NC, Move_Scroll_1 ; Boundary check 124 | LD BC, 16 * (Demo_Map_Length - 16) ; If we've reached the top, then 125 | ADD HL, BC ; wrap back to the bottom 126 | Move_Scroll_1: LD (Vertical_Scroll_Offset), HL 127 | RET 128 | 129 | ; Initialise the sprites 130 | ; 131 | Initialise_Sprites: LD IX,Sprite_Data ; Where the sprite data is stored 132 | LD B,11 ; Number of sprites to initialise 133 | LD C,8 ; Sprite Y coordinate 134 | Initialise_Sprites_1: CALL RND16 ; Get a random X coordinate 135 | RES 7, L 136 | LD (IX+Sprite_X), L ; And store in Sprite_X 137 | LD (IX+Sprite_Y), C ; Store the Y coordinate 138 | LD A,C ; And increment by 13 for next sprite 139 | ADD A,16 140 | LD C,A 141 | LD HL,Demo_Sprite_Logic ; Set the address of the sprite movement logic 142 | LD (IX+Sprite_Logic),L 143 | LD (IX+Sprite_Logic+1),H 144 | LD HL,Sprite_Bubble ; Set the address of the sprite graphic data 145 | LD (IX+Sprite_Image),L 146 | LD (IX+Sprite_Image+1),H 147 | LD A,B ; Set an X velocity 148 | AND %00000011 149 | ADD A,1 150 | LD (IX+Sprite_Data_1), A ; And store this in Sprite_Data_1 151 | LD DE,Sprite_Data_Block_Size ; Increment IX to point to the next 152 | ADD IX,DE ; block of sprite data 153 | DJNZ Initialise_Sprites_1 ; And repeat 154 | RET 155 | 156 | ; The demo sprite logic 157 | ; 158 | Demo_Sprite_Logic: LD A,(IX+Sprite_X) ; Move the sprite by 159 | ADD A,(IX+Sprite_Data_1) ; adding its velocity to its X coordinate 160 | LD (IX+Sprite_X),A 161 | RET C ; C set if we're still on screen 162 | CP 176 ; CP with 176 (right pixel boundary - sprite width) 163 | RET C ; C set if we're still on screen 164 | LD A,(IX+Sprite_Data_1) ; Otherwise, negate the velocity 165 | NEG 166 | LD (IX+Sprite_Data_1),A 167 | ADD A,(IX+Sprite_X) ; And move the sprite back on screen 168 | LD (IX+Sprite_X),A 169 | RET 170 | 171 | ; Demo Map 172 | ; Each map row contains 16 byts 173 | ; - Word : Tileset address for this row 174 | ; - Word : Currently unused, could be used for flags - helpful padding to make each row a convenient length 175 | ; - Byte x 12 : A tile number for each column (see below) 176 | ; NB: 177 | ; Ideally this would be pre-processed from some more compressed map data. It is essentially 178 | ; a lookup table that can be quickly converted to PUSH instructions with minimal maths - 179 | ; only use the high nibble so I don't have to shift left 4 times. The bottom nibble may be used 180 | ; for flags or colour information - it is masked out by the scroll routine. 181 | ; 182 | ; Each PUSH instruction corresponds to a tile that can be stored on a given line 183 | ; Push BC = 00 - This is the blank tile 184 | ; Push DE = 10 185 | ; Push HL = 20 186 | ; Push AF = 30 187 | ; Push IX = 40 188 | ; Push IY = 50 189 | ; 190 | MAP_ROW: MACRO tileset,C01,C02,C03,C04,C05,C06,C07,C08,C09,C10,C11,C12 191 | DW tileset, 0x0000 192 | DB C12<<4,C11<<4,C10<<4,C09<<4,C08<<4,C07<<4 193 | DB C06<<4,C05<<4,C04<<4,C03<<4,C02<<4,C01<<4 194 | ENDM 195 | 196 | Demo_Map: MAP_ROW Tileset_07, 0,0,0,0,0,0,0,0,0,0,0,0 197 | MAP_ROW Tileset_08, 4,4,4,4,4,4,4,4,2,3,4,4 198 | MAP_ROW Tileset_02, 3,3,3,3,3,3,3,3,4,5,0,3 199 | MAP_ROW Tileset_07, 3,3,3,3,3,3,3,3,1,1,1,3 200 | MAP_ROW Tileset_08, 5,5,2,3,1,5,5,5,5,5,5,5 201 | MAP_ROW Tileset_01, 3,3,4,5,0,3,1,2,0,3,3,3 202 | MAP_ROW Tileset_02, 3,3,0,0,0,3,4,5,0,3,3,3 203 | MAP_ROW Tileset_07, 3,3,3,3,3,3,1,1,1,3,3,3 204 | MAP_ROW Tileset_09, 4,4,4,4,4,4,4,4,4,4,4,4 205 | MAP_ROW Tileset_02, 0,0,0,0,0,0,0,0,0,0,0,0 206 | MAP_ROW Tileset_11, 4,5,4,5,4,5,4,5,4,5,4,5 207 | MAP_ROW Tileset_11, 1,1,1,1,1,1,1,1,1,1,1,1 208 | MAP_ROW Tileset_09, 4,4,4,4,4,4,4,4,4,4,4,4 209 | MAP_ROW Tileset_08, 2,3,1,1,1,1,1,1,1,1,1,1 210 | MAP_ROW Tileset_02, 4,5,1,2,0,0,0,0,0,0,0,0 211 | MAP_ROW Tileset_06, 3,2,3,5,3,1,1,1,1,1,1,1 212 | MAP_ROW Tileset_03, 5,4,5,4,2,3,0,0,0,0,0,1 213 | MAP_ROW Tileset_04, 1,4,1,2,4,5,0,0,0,0,0,3 214 | MAP_ROW Tileset_06, 4,5,4,5,1,1,1,1,1,1,1,1 215 | MAP_ROW Tileset_09, 4,4,4,4,4,4,4,4,4,4,4,4 216 | MAP_ROW Tileset_07, 0,0,0,0,0,0,0,0,0,0,0,0 217 | MAP_ROW Tileset_05, 3,3,3,3,3,3,3,3,3,3,3,3 218 | MAP_ROW Tileset_06, 0,0,0,0,0,0,0,2,3,0,0,0 219 | MAP_ROW Tileset_05, 2,2,2,2,2,0,1,4,5,1,2,2 220 | MAP_ROW Tileset_06, 1,1,1,1,2,3,5,3,4,5,1,1 221 | MAP_ROW Tileset_06, 1,1,1,1,4,5,4,5,3,1,1,1 222 | MAP_ROW Tileset_07, 1,3,3,3,3,4,2,4,5,1,1,1 223 | MAP_ROW Tileset_07, 1,3,3,3,3,0,4,5,1,1,1,1 224 | MAP_ROW Tileset_07, 1,3,3,3,3,0,1,1,1,1,1,1 225 | MAP_ROW Tileset_07, 1,3,3,3,3,0,1,1,1,1,1,1 226 | MAP_ROW Tileset_08, 1,5,5,5,5,0,1,1,2,3,0,1 227 | MAP_ROW Tileset_09, 1,4,4,4,4,0,1,1,2,3,0,1 228 | MAP_ROW Tileset_09, 1,4,4,4,4,0,1,1,0,0,0,1 229 | MAP_ROW Tileset_08, 1,0,0,0,0,0,1,1,1,1,1,2 230 | MAP_ROW Tileset_10, 2,2,2,0,0,0,0,0,2,2,2,1 231 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,0 232 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,3 233 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,3 234 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,3 235 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,3 236 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,3 237 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,3 238 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,3 239 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,3 240 | MAP_ROW Tileset_11, 1,1,1,0,0,2,0,0,1,1,1,3 241 | 242 | Demo_Map_Length: EQU ($ - Demo_Map + 1) / 16 243 | 244 | Tileset_01: DG ---------------- -111----11111111 -11-----1111111- 11111---11-11111 11-11111-1111111 -1-------------1 245 | DG ---------------- -111111111111111 111111111111111- -1111-1111--1111 -1111111111-111- ---------------1 246 | DG -----------1---- -1-1111111111111 111111111111111- --11--11111-1111 -1111-111-1111-- ---1-----------1 247 | DG --1------------- 1111-11111111111 111111111111111- 11---1111111---- -1-1111-111-1--- 1--------------1 248 | DG ---------------- -111111111111111 1111111111111-1- 1111--1111111--1 111111111111--1- ----1---------1- 249 | DG ------1--------- -11111-111111111 1111111111----11 11111--11-----11 -1-11-11-111-1-- -1----1-------1- 250 | DG ---------------- 111-111111111111 111111111------1 -111111--111-111 11111111111----- ---------------1 251 | DG ------1--------- 11111111-1111111 11111111-------1 ---1----1111-111 11-111-111-----1 ---1---1-------1 252 | DG -------------1-- -1111-1111-11111 11111111------1- 111--11-1111-111 1111111111--1--- ---------------1 253 | DG --1------------- -11-111111111111 11111111------1- 111-111-1111-111 1111-1111-1---1- -1---1---1----11 254 | DG -------1-------- -111-111-1111111 1111111-------1- 11-1111--11--111 -1111111-------- ---1----------1- 255 | DG ---------------- -1111111111-1111 111111---------1 11-11111-1-----1 -1-1111----1---1 -------1-1-1--1- 256 | DG ---------------- 1111111-1-111-11 11111----------1 11-111111-1111-- -11111--1----1-- --1--1------1-11 257 | DG -----------1---- 111-11-111111111 111-----------1- 1---1111-1111111 -1111-----1----- --------1---1111 258 | DG -----1---------- -11111111111111- 11------------1- 1-1--111-1111111 111--111-----111 1--11-----11111- 259 | DG ---------------- 11111-111-11-111 1-------------11 --11--11--111111 -11111-1111111-- -11-111111--111- 260 | 261 | DW 0x0606, 0x0606, 0x0606, 0x0606, 0x0606, 0x0606 262 | DW 0x0606, 0x0606, 0x0606, 0x0606, 0x0606, 0x0606 263 | 264 | Tileset_02: DG ---------------- ---------------- ---------------- 11111---11-11111 11-11111-1111111 -1-------------1 265 | DG ---------------- ---------------- ---------------- -1111-1111--1111 -1111111111-111- ---------------1 266 | DG -----------1---- ---------------- -----1------1--- --11--11111-1111 -1111-111-1111-- ---1-----------1 267 | DG --1------------- --1----1-------1 1--------------- 11---1111111---- -1-1111-111-1--- 1--------------1 268 | DG ---------------- ---------------1 11-------------- 1111--1111111--1 111111111111--1- ----1---------1- 269 | DG ------1--------- -----------1--11 111---1----1---- 11111--11-----11 -1-11-11-111-1-- -1----1-------1- 270 | DG ---------------- --------------11 111------111---- -111111--111-111 11111111111----- ---------------1 271 | DG ------1--------- ------111-----11 1111---1111----- ---1----1111-111 11-111-111-----1 ---1---1-------1 272 | DG -------------1-- ---1--11111----1 1111--1111------ 111--11-1111-111 1111111111--1--- ---------------1 273 | DG --1------------- -------11111---1 1111-1111------- 111-111-1111-111 1111-1111-1---1- -1---1---1----11 274 | DG -------1-------- --------11111--1 1111-1111---1--- 11-1111--11--111 -1111111-------- ---1----------1- 275 | DG ---------------- ---------1111111 111111-1-------- 11-11111-1-----1 -1-1111----1---1 -------1-1-1--1- 276 | DG ---------------- --1---1---111111 11111111-------- 11-111111-1111-- -11111--1----1-- --1--1------1-11 277 | DG -----------1---- ------------1111 111111111111---- 1---1111-1111111 -1111-----1----- --------1---1111 278 | DG -----1---------- ------------1111 1111-111111111-- 1-1--111-1111111 111--111-----111 1--11-----11111- 279 | DG ---------------- ------11111-1111 1-111111-11111-- --11--11--111111 -11111-1111111-- -11-111111--111- 280 | 281 | DW 0x0606, 0x0404, 0x0404, 0x0606, 0x0606, 0x0606 282 | DW 0x0606, 0x0404, 0x0404, 0x0606, 0x0606, 0x0606 283 | 284 | Tileset_03: DG ---------------- -111----11111111 ---------------- ---------------- ----1111111111-1 111-1----11----- 285 | DG ---------------- -111111111111111 ---------------- ---------------- ----111111111111 -1111----------- 286 | DG -----------1---- -1-1111111111111 ---------------- -----1------1--- --------11111111 1-11111--------- 287 | DG --1------------- 1111-11111111111 --1----1-------1 1--------------- ----------11111- 11111111-------- 288 | DG ---------------- -111111111111111 ---------------1 11-------------- --------111111-- 111-11-11------- 289 | DG ------1--------- -11111-111111111 -----------1--11 111---1----1---- ---1---1111-1--- 111---1111------ 290 | DG ---------------- 111-111111111111 --------------11 111------111---- ------111111---- 1111---1111----- 291 | DG ------1--------- 11111111-1111111 ------111-----11 1111---1111----- ------11-11----- -1-1------11---- 292 | DG -------------1-- -1111-1111-11111 ---1--11111----1 1111--1111------ --1--11111------ -111-------1---- 293 | DG --1------------- -11-111111111111 -------11111---1 1111-1111------- -----11--------- -111------------ 294 | DG -------1-------- -111-111-1111111 --------11111--1 1111-1111---1--- ----1----------- --11------------ 295 | DG ---------------- -1111111111-1111 ---------1111111 111111-1-------- ---------------- ---1------------ 296 | DG ---------------- 1111111-1-111-11 --1---1---111111 11111111-------- ---------------- ---------------- 297 | DG -----------1---- 111-11-111111111 ------------1111 111111111111---- ----1--------1-- ---------1------ 298 | DG -----1---------- -11111111111111- ------------1111 1111-111111111-- ---------1------ ---------------- 299 | DG ---------------- 11111-111-11-111 ------11111-1111 1-111111-11111-- ---------------- ---------------- 300 | 301 | DW 0x0606, 0x0606, 0x0404, 0x0404, 0x0404, 0x0404 302 | DW 0x0606, 0x0606, 0x0404, 0x0404, 0x0404, 0x0404 303 | 304 | Tileset_04: DG ---------------- ---------------- ---------------- 11-11111-1111111 ----1111111111-1 111-1----11----- 305 | DG ---------------- ---------------- ---------------- -1111111111-111- ----111111111111 -1111----------- 306 | DG -----------1---- ---------------- -----1------1--- -1111-111-1111-- --------11111111 1-11111--------- 307 | DG --1------------- --1----1-------1 1--------------- -1-1111-111-1--- ----------11111- 11111111-------- 308 | DG ---------------- ---------------1 11-------------- 111111111111--1- --------111111-- 111-11-11------- 309 | DG ------1--------- -----------1--11 111---1----1---- -1-11-11-111-1-- ---1---1111-1--- 111---1111------ 310 | DG ---------------- --------------11 111------111---- 11111111111----- ------111111---- 1111---1111----- 311 | DG ------1--------- ------111-----11 1111---1111----- 11-111-111-----1 ------11-11----- -1-1------11---- 312 | DG -------------1-- ---1--11111----1 1111--1111------ 1111111111--1--- --1--11111------ -111-------1---- 313 | DG --1------------- -------11111---1 1111-1111------- 1111-1111-1---1- -----11--------- -111------------ 314 | DG -------1-------- --------11111--1 1111-1111---1--- -1111111-------- ----1----------- --11------------ 315 | DG ---------------- ---------1111111 111111-1-------- -1-1111----1---1 ---------------- ---1------------ 316 | DG ---------------- --1---1---111111 11111111-------- -11111--1----1-- ---------------- ---------------- 317 | DG -----------1---- ------------1111 111111111111---- -1111-----1----- ----1--------1-- ---------1------ 318 | DG -----1---------- ------------1111 1111-111111111-- 111--111-----111 ---------1------ ---------------- 319 | DG ---------------- ------11111-1111 1-111111-11111-- -11111-1111111-- ---------------- ---------------- 320 | 321 | DW 0x0606, 0x0404, 0x0404, 0x0606, 0x0404, 0x0404 322 | DW 0x0606, 0x0404, 0x0404, 0x0606, 0x0404, 0x0404 323 | 324 | Tileset_05: DG ---------------- ---------------- -1111111-1111111 ---------------- ----1111111111-1 111-1----11----- 325 | DG ---------------- ---------------- -1111111-111111- ---------------- ----111111111111 -1111----------- 326 | DG ---------------- -----1------1--- -1111111-1111111 ---------------- --------11111111 1-11111--------- 327 | DG --1----1-------1 1--------------- ---------------- ---------------- ----------11111- 11111111-------- 328 | DG ---------------1 11-------------- 1111-11111111-11 ---------------- --------111111-- 111-11-11------- 329 | DG -----------1--11 111---1----1---- 1111-11111111-11 ---------------- ---1---1111-1--- 111---1111------ 330 | DG --------------11 111------111---- 1111-11111111-11 ---------------- ------111111---- 1111---1111----- 331 | DG ------111-----11 1111---1111----- ---------------- ----111111------ ------11-11----- -1-1------11---- 332 | DG ---1--11111----1 1111--1111------ -1111111-1111111 ----111111------ --1--11111------ -111-------1---- 333 | DG -------11111---1 1111-1111------- -1111111-1111111 ---------------- -----11--------- -111------------ 334 | DG --------11111--1 1111-1111---1--- -1111111-1111111 ---------------- ----1----------- --11------------ 335 | DG ---------1111111 111111-1-------- ---------------- ---------------- ---------------- ---1------------ 336 | DG --1---1---111111 11111111-------- 1111-11111111-11 ---------------- ---------------- ---------------- 337 | DG ------------1111 111111111111---- 1111-11111111-11 ---------------- ----1--------1-- ---------1------ 338 | DG ------------1111 1111-111111111-- 1111-11111111-11 ---------------- ---------1------ ---------------- 339 | DG ------11111-1111 1-111111-11111-- ---------------- ---------------- ---------------- ---------------- 340 | 341 | DW 0x0404, 0x0404, 0x0606, 0x0707, 0x0404, 0x0404 342 | DW 0x0404, 0x0404, 0x0606, 0x0707, 0x0404, 0x0404 343 | 344 | Tileset_06: DG ---------------- ---------------- ---------------- ---------------- ----1111111111-1 111-1----11----- 345 | DG ---------------- ---------------- ---------------- ---------------- ----111111111111 -1111----------- 346 | DG ---------------- -----------1---- ---------------- -----1------1--- --------11111111 1-11111--------- 347 | DG ---------------- --1------------- --1----1-------1 1--------------- ----------11111- 11111111-------- 348 | DG ---------------- ---------------- ---------------1 11-------------- --------111111-- 111-11-11------- 349 | DG ---------------- ------1--------- -----------1--11 111---1----1---- ---1---1111-1--- 111---1111------ 350 | DG ---------------- ---------------- --------------11 111------111---- ------111111---- 1111---1111----- 351 | DG ---------------- ------1--------- ------111-----11 1111---1111----- ------11-11----- -1-1------11---- 352 | DG ---------------- -------------1-- ---1--11111----1 1111--1111------ --1--11111------ -111-------1---- 353 | DG ---------------- --1------------- -------11111---1 1111-1111------- -----11--------- -111------------ 354 | DG ---------------- -------1-------- --------11111--1 1111-1111---1--- ----1----------- --11------------ 355 | DG ---------------- ---------------- ---------1111111 111111-1-------- ---------------- ---1------------ 356 | DG ---------------- ---------------- --1---1---111111 11111111-------- ---------------- ---------------- 357 | DG ---------------- -----------1---- ------------1111 111111111111---- ----1--------1-- ---------1------ 358 | DG ---------------- -----1---------- ------------1111 1111-111111111-- ---------1------ ---------------- 359 | DG ---------------- ---------------- ------11111-1111 1-111111-11111-- ---------------- ---------------- 360 | 361 | DW 0x0707, 0x0606, 0x0404, 0x0404, 0x0404, 0x0404 362 | DW 0x0707, 0x0606, 0x0404, 0x0404, 0x0404, 0x0404 363 | 364 | Tileset_07: DG ---------------- ---------------- ---------------- 11111---11-11111 ----1111111111-1 111-1----11----- 365 | DG ---------------- ---------------- ---------------- -1111-1111--1111 ----111111111111 -1111----------- 366 | DG ---------------- -----------1---- ---------------- --11--11111-1111 --------11111111 1-11111--------- 367 | DG ---------------- --1------------- --1----1-------1 11---1111111---- ----------11111- 11111111-------- 368 | DG ---------------- ---------------- ---------------1 1111--1111111--1 --------111111-- 111-11-11------- 369 | DG ---------------- ------1--------- -----------1--11 11111--11-----11 ---1---1111-1--- 111---1111------ 370 | DG ---------------- ---------------- --------------11 -111111--111-111 ------111111---- 1111---1111----- 371 | DG ---------------- ------1--------- ------111-----11 ---1----1111-111 ------11-11----- -1-1------11---- 372 | DG ---------------- -------------1-- ---1--11111----1 111--11-1111-111 --1--11111------ -111-------1---- 373 | DG ---------------- --1------------- -------11111---1 111-111-1111-111 -----11--------- -111------------ 374 | DG ---------------- -------1-------- --------11111--1 11-1111--11--111 ----1----------- --11------------ 375 | DG ---------------- ---------------- ---------1111111 11-11111-1-----1 ---------------- ---1------------ 376 | DG ---------------- ---------------- --1---1---111111 11-111111-1111-- ---------------- ---------------- 377 | DG ---------------- -----------1---- ------------1111 1---1111-1111111 ----1--------1-- ---------1------ 378 | DG ---------------- -----1---------- ------------1111 1-1--111-1111111 ---------1------ ---------------- 379 | DG ---------------- ---------------- ------11111-1111 --11--11--111111 ---------------- ---------------- 380 | 381 | DW 0x0707, 0x0606, 0x0404, 0x0606, 0x0404, 0x0404 382 | DW 0x0707, 0x0606, 0x0404, 0x0606, 0x0404, 0x0404 383 | 384 | Tileset_08: DG ---------------- ---------------- -111----11111111 -11-----1111111- -1111111-1111111 11111---11-11111 385 | DG ---------------- ---------------- -111111111111111 111111111111111- -1111111-111111- -1111-1111--1111 386 | DG ---------------- -----------1---- -1-1111111111111 111111111111111- -1111111-1111111 --11--11111-1111 387 | DG ---------------- --1------------- 1111-11111111111 111111111111111- ---------------- 11---1111111---- 388 | DG ---------------- ---------------- -111111111111111 1111111111111-1- 1111-11111111-11 1111--1111111--1 389 | DG ---------------- ------1--------- -11111-111111111 1111111111----11 1111-11111111-11 11111--11-----11 390 | DG ---------------- ---------------- 111-111111111111 111111111------1 1111-11111111-11 -111111--111-111 391 | DG ---------------- ------1--------- 11111111-1111111 11111111-------1 ---------------- ---1----1111-111 392 | DG ---------------- -------------1-- -1111-1111-11111 11111111------1- -1111111-1111111 111--11-1111-111 393 | DG ---------------- --1------------- -11-111111111111 11111111------1- -1111111-1111111 111-111-1111-111 394 | DG ---------------- -------1-------- -111-111-1111111 1111111-------1- -1111111-1111111 11-1111--11--111 395 | DG ---------------- ---------------- -1111111111-1111 111111---------1 ---------------- 11-11111-1-----1 396 | DG ---------------- ---------------- 1111111-1-111-11 11111----------1 1111-11111111-11 11-111111-1111-- 397 | DG ---------------- -----------1---- 111-11-111111111 111-----------1- 1111-11111111-11 1---1111-1111111 398 | DG ---------------- -----1---------- -11111111111111- 11------------1- 1111-11111111-11 1-1--111-1111111 399 | DG ---------------- ---------------- 11111-111-11-111 1-------------11 ---------------- --11--11--111111 400 | 401 | DW 0x0707, 0x0606, 0x0606, 0x0606, 0x0606, 0x0606 402 | DW 0x0707, 0x0606, 0x0606, 0x0606, 0x0606, 0x0606 403 | 404 | Tileset_09: DG ---------------- ---------------- 11-11111-1111111 -1-------------1 11--11--11--11-- ---------------- 405 | DG ---------------- ---------------- -1111111111-111- ---------------1 ---------------- ---------------- 406 | DG ---------------- -----------1---- -1111-111-1111-- ---1-----------1 --11--11--11---- ---------------- 407 | DG ---------------- --1------------- -1-1111-111-1--- 1--------------1 ---------------- ---------------- 408 | DG ---------------- ---------------- 111111111111--1- ----1---------1- 11--11--11--11-- ---------------- 409 | DG ---------------- ------1--------- -1-11-11-111-1-- -1----1-------1- ---------------- ---------------- 410 | DG ---------------- ---------------- 11111111111----- ---------------1 --11--11--11---- ---------------- 411 | DG ---------------- ------1--------- 11-111-111-----1 ---1---1-------1 ---------------- ---------------- 412 | DG ---------------- -------------1-- 1111111111--1--- ---------------1 11--11--11--11-- ---------------- 413 | DG ---------------- --1------------- 1111-1111-1---1- -1---1---1----11 ---------------- ---------------- 414 | DG ---------------- -------1-------- -1111111-------- ---1----------1- --11--11--11---- ---------------- 415 | DG ---------------- ---------------- -1-1111----1---1 -------1-1-1--1- ---------------- ---------------- 416 | DG ---------------- ---------------- -11111--1----1-- --1--1------1-11 11--11--11--11-- ---------------- 417 | DG ---------------- -----------1---- -1111-----1----- --------1---1111 ---------------- ---------------- 418 | DG ---------------- -----1---------- 111--111-----111 1--11-----11111- --11--11--11---- ---------------- 419 | DG ---------------- ---------------- -11111-1111111-- -11-111111--111- ---------------- ---------------- 420 | 421 | DW 0x0707, 0x0606, 0x0606, 0x0606, 0x0606, 0x0707 422 | DW 0x0707, 0x0606, 0x0606, 0x0606, 0x0606, 0x0707 423 | 424 | Tileset_10: DG ---------------- 11-11111-1111111 ---------------- ---------------- ---------------- ---------------- 425 | DG -----1-----1--1- -1111111111-111- 111----11-1111-1 ---------------- ---------------- ---------------- 426 | DG -1------1------- -1111-111-1111-- 1111111111111111 ---------------- ---------------- ---------------- 427 | DG ---------------- -1-1111-111-1--- 11-11-1111111111 ---------------- ---------------- ---------------- 428 | DG ---------------- 111111111111--1- 111111111-1111-1 ---------------- ---------------- ---------------- 429 | DG -111111111111111 -1-11-11-111-1-- 1-11111111111111 ---------------- ---------------- ---------------- 430 | DG -11-----1-----11 11111111111----- 11111-1111111111 ---------------- ---------------- ---------------- 431 | DG -1-1---1-----1-1 11-111-111-----1 111111111111-111 ---------------- ---------------- ---------------- 432 | DG -1--1-1---1-1--1 1111111111--1--- 1-111111-1111111 ---------------- ---------------- ---------------- 433 | DG -1---1-----1---1 1111-1111-1---1- 1111111111111111 ---------------- ---------------- ---------------- 434 | DG -1--1-1---1-1--1 -1111111-------- 1111111111111111 ---------------- ---------------- ---------------- 435 | DG -1-1---1-1---1-1 -1-1111----1---1 1111-11111111111 ---------------- ---------------- ---------------- 436 | DG -11-----1-----11 -11111--1----1-- 1111111111111111 ---------------- ---------------- ---------------- 437 | DG -111111111111111 -1111-----1----- 111111111111-111 ---------------- ---------------- ---------------- 438 | DG ---------------- 111--111-----111 1111111111111111 ---------------- ---------------- ---------------- 439 | DG ---------------- -11111-1111111-- 1111111111111111 ---------------- ---------------- ---------------- 440 | 441 | DW 0x0505, 0x0606, 0x0606, 0x0707, 0x0707, 0x0707 442 | DW 0x0505, 0x0606, 0x0606, 0x0707, 0x0707, 0x0707 443 | 444 | Tileset_11: DG ---------------- 1111111111111111 ---------------- -1111111-1111111 ---------------- ---------------- 445 | DG ---------------- 1111111111111111 ---------------- -1111111-111111- ---------------- ---------------- 446 | DG ---------------- 1111111111111111 ---------------- -1111111-1111111 -----------1---- -----------1---- 447 | DG ---------------- 1111111111111111 ---------------- ---------------- --1------------- --1------------- 448 | DG ---------------- 1111111111111111 ------11-------- 1111-11111111-11 ---------------- ---------------- 449 | DG ---------------- 1111111111111111 ------11-------- 1111-11111111-11 ------1-------1- ------1--------1 450 | DG ---------------- 1111111111111111 ------11-------- 1111-11111111-11 ------------1111 111---------1111 451 | DG ---------------- 1111111111111111 ------11-------- ---------------- 1----------11111 111111111--11111 452 | DG ---------------- 1111111111111111 ------11-------- -1111111-1111111 1111---111111111 1111111111111111 453 | DG ---------------- 1111111111111111 ------11-------- -1111111-1111111 1111111111111111 1111111111111111 454 | DG ---------------- 1111111111111111 ---------------- -1111111-1111111 1111111111111111 1111111111111111 455 | DG ---------------- 1111111111111111 ---------------- ---------------- 1111111111111111 1111111111111111 456 | DG ---------------- 1111111111111111 ---------------- 1111-11111111-11 1111111111111111 1111111111111111 457 | DG ---------------- 1111111111111111 ---------------- 1111-11111111-11 1111111111111111 1111111111111111 458 | DG ---------------- 1111111111111111 ---------------- 1111-11111111-11 1111111111111111 1111111111111111 459 | DG ---------------- 1111111111111111 ---------------- ---------------- 1111111111111111 1111111111111111 460 | 461 | DW 0x0707, 0x0606, 0x0707, 0x4202, 0x0606, 0x0606 462 | DW 0x0707, 0x0606, 0x0707, 0x4202, 0x0606, 0x0606 463 | 464 | ; This is the lookup table for each pixel shift of the sprite - 8 entries 465 | ; We only have 4 shifted definitions to save space (and my time hand-creating the sprite) 466 | ; so each entry is repeated twice 467 | ; 468 | ; The first two bytes are the effective sprite width (in characters) and height (in pixels) 469 | ; 470 | Sprite_Bubble: DB 2,16: DW Sprite_Bubble_0,Sprite_Bubble_0,Sprite_Bubble_2,Sprite_Bubble_2,Sprite_Bubble_4,Sprite_Bubble_4,Sprite_Bubble_6,Sprite_Bubble_6 471 | 472 | ; And the actual graphics that table points to are here - with the mask and data interleaved for 473 | ; efficiency when drawing the sprites 474 | ; 475 | ; Pixel positions 0 476 | ; 477 | Sprite_Bubble_0: DB %11111111, %00000000, %11111111, %00000000, %11111111, %00000000 478 | DB %11110000, %00000011, %00001111, %11000000, %11111111, %00000000 479 | DB %11100000, %00001100, %00000111, %00110000, %11111111, %00000000 480 | DB %11000000, %00010000, %00000011, %00001000, %11111111, %00000000 481 | DB %10000000, %00100110, %00000001, %00000100, %11111111, %00000000 482 | DB %10000000, %00101000, %00000001, %00000100, %11111111, %00000000 483 | DB %00000000, %01001000, %00000000, %00000010, %11111111, %00000000 484 | DB %00000000, %01000000, %00000000, %00000010, %11111111, %00000000 485 | DB %00000000, %01000000, %00000000, %00000010, %11111111, %00000000 486 | DB %00000000, %01000000, %00000000, %00000010, %11111111, %00000000 487 | DB %10000000, %00100000, %00000001, %00000100, %11111111, %00000000 488 | DB %10000000, %00100000, %00000001, %00000100, %11111111, %00000000 489 | DB %11000000, %00010000, %00000011, %00001000, %11111111, %00000000 490 | DB %11100000, %00001100, %00000111, %00110000, %11111111, %00000000 491 | DB %11110000, %00000011, %00001111, %11000000, %11111111, %00000000 492 | DB %11111111, %00000000, %11111111, %00000000, %11111111, %00000000 493 | 494 | ; Pixel position 2 495 | ; 496 | Sprite_Bubble_2: DB %11111111, %00000000, %11111111, %00000000, %11111111, %00000000 497 | DB %11111100, %00000000, %00000011, %11110000, %11111111, %00000000 498 | DB %11111000, %00000011, %00000001, %00001100, %11111111, %00000000 499 | DB %11110000, %00000100, %00000000, %00000010, %11111111, %00000000 500 | DB %11100000, %00001001, %00000000, %10000001, %11111111, %00000000 501 | DB %11100000, %00001010, %00000000, %00000001, %01111111, %00000000 502 | DB %11000000, %00010010, %00000000, %00000000, %00111111, %10000000 503 | DB %11000000, %00010000, %00000000, %00000000, %00111111, %10000000 504 | DB %11000000, %00010000, %00000000, %00000000, %00111111, %10000000 505 | DB %11000000, %00010000, %00000000, %00000000, %00111111, %10000000 506 | DB %11100000, %00001000, %00000000, %00000001, %01111111, %00000000 507 | DB %11100000, %00001000, %00000000, %00000001, %11111111, %00000000 508 | DB %11110000, %00000100, %00000000, %00000010, %11111111, %00000000 509 | DB %11111000, %00000011, %00000001, %00001100, %11111111, %00000000 510 | DB %11111100, %00000000, %00000011, %11110000, %11111111, %00000000 511 | DB %11111111, %00000000, %11111111, %00000000, %11111111, %00000000 512 | 513 | ; Pixel position 4 514 | ; 515 | Sprite_Bubble_4: DB %11111111, %00000000, %11111111, %00000000, %11111111, %00000000 516 | DB %11111111, %00000000, %00000000, %00111100, %11111111, %00000000 517 | DB %11111110, %00000000, %00000000, %11000011, %01111111, %00000000 518 | DB %11111100, %00000001, %00000000, %00000000, %00111111, %10000000 519 | DB %11111000, %00000010, %00000000, %01100000, %00011111, %01000000 520 | DB %11111000, %00000010, %00000000, %10000000, %00011111, %01000000 521 | DB %11110000, %00000100, %00000000, %10000000, %00001111, %00100000 522 | DB %11110000, %00000100, %00000000, %00000000, %00001111, %00100000 523 | DB %11110000, %00000100, %00000000, %00000000, %00001111, %00100000 524 | DB %11110000, %00000100, %00000000, %00000000, %00011111, %00100000 525 | DB %11111000, %00000010, %00000000, %00000000, %00011111, %01000000 526 | DB %11111000, %00000010, %00000000, %00000000, %00011111, %01000000 527 | DB %11111100, %00000001, %00000000, %00000000, %00111111, %10000000 528 | DB %11111110, %00000000, %00000000, %11000011, %01111111, %00000000 529 | DB %11111111, %00000000, %00000000, %00111100, %11111111, %00000000 530 | DB %11111111, %00000000, %11111111, %00000000, %11111111, %00000000 531 | 532 | ; Pixel position 6 533 | ; 534 | Sprite_Bubble_6: DB %11111111, %00000000, %11111111, %00000000, %11111111, %00000000 535 | DB %11111111, %00000000, %11100000, %00001111, %11111111, %00000000 536 | DB %11111111, %00000000, %11000000, %00110000, %00011111, %11000000 537 | DB %11111111, %00000000, %10000000, %01000000, %00001111, %00100000 538 | DB %11111111, %00000000, %00000000, %10011000, %00000111, %00010000 539 | DB %11111110, %00000000, %00000000, %10100000, %00000111, %00010000 540 | DB %11111110, %00000001, %00000000, %00100000, %00000011, %00001000 541 | DB %11111110, %00000001, %00000000, %00000000, %00000011, %00001000 542 | DB %11111110, %00000001, %00000000, %00000000, %00000011, %00001000 543 | DB %11111110, %00000001, %00000000, %00000000, %00000011, %00001000 544 | DB %11111110, %00000000, %00000000, %10000000, %00000111, %00010000 545 | DB %11111111, %00000000, %00000000, %10000000, %00000111, %00010000 546 | DB %11111111, %00000000, %10000000, %01000000, %00001111, %00100000 547 | DB %11111111, %00000000, %11000000, %00110000, %00011111, %11000000 548 | DB %11111111, %00000000, %11100000, %00001111, %11111111, %00000000 549 | DB %11111111, %00000000, %11111111, %00000000, %11111111, %00000000 550 | 551 | Code_Length: EQU $-Code_Start+1 552 | 553 | SAVESNA "Demo/demo_scroll.sna", Code_Start 554 | -------------------------------------------------------------------------------- /demo/demo_sprites.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: Sprite Demo 3 | ; Author: Dean Belfield 4 | ; Created: 20/08/2011 5 | ; Last updated: 20/08/2011 6 | ; 7 | ; Requires: keyboard, output, screen_buffer, sound, sprite, math 8 | ; 9 | ; Modinfo: 10 | ; 11 | 12 | DEVICE ZXSPECTRUM48 ; sjasmplus directive for SAVESNA at end 13 | 14 | Stack_Top: EQU 0xFFF0 15 | Code_Start: EQU 0x8000 16 | 17 | ORG Code_Start 18 | 19 | JP MAIN ; JP past the included code to MAIN 20 | 21 | include "../lib/keyboard.z80" 22 | include "../lib/output.z80" 23 | include "../lib/screen_buffer.z80" 24 | include "../lib/sound.z80" 25 | include "../lib/sprite.z80" 26 | include "../lib/math.z80" 27 | 28 | MAIN: DI 29 | LD SP, Stack_Top 30 | LD A,38h 31 | CALL Clear_Screen 32 | LD IX,Text_Scores 33 | CALL Print_String 34 | CALL Initialise_Sprites 35 | LD HL,Interrupt 36 | LD IX,0xFFF0 37 | LD (IX+04h),0xC3 ; Opcode for JP 38 | LD (IX+05h),L 39 | LD (IX+06h),H 40 | LD (IX+0Fh),0x18 ; Opcode for JR; this will do JR to FFF4h 41 | LD A,0x39 42 | LD I,A 43 | IM 2 44 | EI 45 | 46 | LOOP: HALT 47 | CALL Read_Keyboard 48 | JR LOOP 49 | 50 | Initialise_Sprites: LD IX,Sprite_Data 51 | LD B,Sprite_Max 52 | Initialise_Sprites_1: CALL RND16 53 | LD A,H 54 | AND 0x7F 55 | ADD A,16 56 | LD (IX+Sprite_X),L 57 | LD (IX+Sprite_X_Old),L 58 | LD (IX+Sprite_Y),A 59 | LD (IX+Sprite_Y_Old),A 60 | LD HL,Demo_Sprite_Logic 61 | LD (IX+Sprite_Logic),L 62 | LD (IX+Sprite_Logic+1),H 63 | LD DE,Sprite_Data_Block_Size 64 | ADD IX,DE 65 | DJNZ Initialise_Sprites_1 66 | RET 67 | 68 | Demo_Sprite_Logic: LD A,(IX+Sprite_X) 69 | INC A 70 | CP 240 71 | JR C,Demo_Sprite_Logic_1 72 | LD A,0 73 | Demo_Sprite_Logic_1 LD (IX+Sprite_X),A 74 | LD A,(IX+Sprite_Y) 75 | INC A 76 | CP 176 77 | JR C,Demo_Sprite_Logic_2 78 | LD A,16 79 | Demo_Sprite_Logic_2 LD (IX+Sprite_Y),A 80 | RET 81 | 82 | Interrupt: DI 83 | PUSH AF 84 | PUSH BC 85 | PUSH DE 86 | PUSH HL 87 | PUSH IX 88 | EXX 89 | EX AF,AF' 90 | PUSH AF 91 | PUSH BC 92 | PUSH DE 93 | PUSH HL 94 | PUSH IY 95 | LD A,2 96 | OUT (254),A 97 | LD HL,Sprite_Bubble 98 | CALL Render_Sprites 99 | CALL Handle_Sprites 100 | LD A,7 101 | OUT (254),A 102 | POP IY 103 | POP HL 104 | POP DE 105 | POP BC 106 | POP AF 107 | EXX 108 | EX AF,AF' 109 | POP IX 110 | POP HL 111 | POP DE 112 | POP BC 113 | POP AF 114 | EI 115 | RET 116 | 117 | Text_Scores: DB 0,0,"Demo",0xFE 118 | DB 9,0,"Score 00000000",0xFE 119 | DB 24,0,"Lives 0",0xFF 120 | 121 | Sprite_Bubble: DW Sprite_Bubble_0,Sprite_Bubble_0,Sprite_Bubble_2,Sprite_Bubble_2,Sprite_Bubble_4,Sprite_Bubble_4,Sprite_Bubble_6,Sprite_Bubble_6 122 | 123 | Sprite_Bubble_0: DB %00000000,%00000000,%00000000 124 | DB %00000011,%11000000,%00000000 125 | DB %00001100,%00110000,%00000000 126 | DB %00010000,%00001000,%00000000 127 | DB %00100110,%00000100,%00000000 128 | DB %00101000,%00000100,%00000000 129 | DB %01001000,%00000010,%00000000 130 | DB %01000000,%00000010,%00000000 131 | DB %01000000,%00000010,%00000000 132 | DB %01000000,%00000010,%00000000 133 | DB %00100000,%00000100,%00000000 134 | DB %00100000,%00000100,%00000000 135 | DB %00010000,%00001000,%00000000 136 | DB %00001100,%00110000,%00000000 137 | DB %00000011,%11000000,%00000000 138 | DB %00000000,%00000000,%00000000 139 | 140 | Sprite_Bubble_2: DB %00000000,%00000000,%00000000 141 | DB %00000000,%11110000,%00000000 142 | DB %00000011,%00001100,%00000000 143 | DB %00000100,%00000010,%00000000 144 | DB %00001001,%10000001,%00000000 145 | DB %00001010,%00000001,%00000000 146 | DB %00010010,%00000000,%10000000 147 | DB %00010000,%00000000,%10000000 148 | DB %00010000,%00000000,%10000000 149 | DB %00010000,%00000000,%10000000 150 | DB %00001000,%00000001,%00000000 151 | DB %00001000,%00000001,%00000000 152 | DB %00000100,%00000010,%00000000 153 | DB %00000011,%00001100,%00000000 154 | DB %00000000,%11110000,%00000000 155 | DB %00000000,%00000000,%00000000 156 | 157 | Sprite_Bubble_4: DB %00000000,%00000000,%00000000 158 | DB %00000000,%00111100,%00000000 159 | DB %00000000,%11000011,%00000000 160 | DB %00000001,%00000000,%10000000 161 | DB %00000010,%01100000,%01000000 162 | DB %00000010,%10000000,%01000000 163 | DB %00000100,%10000000,%00100000 164 | DB %00000100,%00000000,%00100000 165 | DB %00000100,%00000000,%00100000 166 | DB %00000100,%00000000,%00100000 167 | DB %00000010,%00000000,%01000000 168 | DB %00000010,%00000000,%01000000 169 | DB %00000001,%00000000,%10000000 170 | DB %00000000,%11000011,%00000000 171 | DB %00000000,%00111100,%00000000 172 | DB %00000000,%00000000,%00000000 173 | 174 | Sprite_Bubble_6: DB %00000000,%00000000,%00000000 175 | DB %00000000,%00001111,%00000000 176 | DB %00000000,%00110000,%11000000 177 | DB %00000000,%01000000,%00100000 178 | DB %00000000,%10011000,%00010000 179 | DB %00000000,%10100000,%00010000 180 | DB %00000001,%00100000,%00001000 181 | DB %00000001,%00000000,%00001000 182 | DB %00000001,%00000000,%00001000 183 | DB %00000001,%00000000,%00001000 184 | DB %00000000,%10000000,%00010000 185 | DB %00000000,%10000000,%00010000 186 | DB %00000000,%01000000,%00100000 187 | DB %00000000,%00110000,%11000000 188 | DB %00000000,%00001111,%00000000 189 | DB %00000000,%00000000,%00000000 190 | 191 | Code_Length: EQU $-Code_Start+1 192 | 193 | SAVESNA "demo/demo_sprites.sna", Code_Start 194 | -------------------------------------------------------------------------------- /demo/demo_vector.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: Vector Demo 3 | ; Author: Dean Belfield 4 | ; Started: 30/06/2012 5 | ; Last Updated: 11/04/2020 6 | ; 7 | ; Requires: vector, output, keyboard, screen_buffer, sound, sprite, math 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 05/07/2012: Pressing Space now toggles cursor between points; added circle draw 12 | ; 11/04/2020: Modified calls to maths routines and Clear_Screen_Fast 13 | ; 14 | 15 | DEVICE ZXSPECTRUM48 ; sjasmplus directive for SAVESNA at end 16 | 17 | Stack_Top: EQU 0xFFF0 18 | Code_Start: EQU 0x8000 19 | 20 | ORG Code_Start 21 | 22 | JP Main 23 | 24 | include "../lib/vector.z80" 25 | include "../lib/output.z80" 26 | include "../lib/keyboard.z80" 27 | include "../lib/screen_buffer.z80" 28 | include "../lib/sound.z80" 29 | include "../lib/sprite.z80" 30 | include "../lib/math.z80" 31 | 32 | Main: DI 33 | LD SP,Stack_Top 34 | LD A,38h 35 | CALL Clear_Screen 36 | LD HL,Interrupt 37 | LD IX,0xFFF0 38 | LD (IX+04h),0xC3 ; Opcode for JP 39 | LD (IX+05h),L 40 | LD (IX+06h),H 41 | LD (IX+0Fh),0x18 ; Opcode for JR; this will do JR to FFF4h 42 | LD A,0x39 43 | LD I,A 44 | LD SP,0xFFF0 45 | IM 2 46 | EI 47 | 48 | LOOP: HALT 49 | JR LOOP 50 | 51 | ; Draw cross, offset 7 pixels so centre is at pixel coord 52 | ; 53 | Draw_Cross: LD A,C 54 | SUB 7 55 | LD C,A 56 | LD A,B 57 | SUB 7 58 | LD B,A 59 | LD DE,Sprite_Cross 60 | JP Render_Sprite 61 | 62 | ; Clear cross, offset 7 pixels so centre is at pixel coord 63 | ; 64 | Clear_Cross: LD A,C 65 | SUB 7 66 | LD C,A 67 | LD A,B 68 | SUB 7 69 | LD B,A 70 | JP Clear_Sprite 71 | 72 | Debounce_Key: DB 0xFF 73 | Position: DW 0x5070,0x6080,0x7060 74 | 75 | ; Handle user input 76 | ; 77 | User_Interface: CALL Read_Keyboard ; Read the keyboard 78 | PUSH AF ; Stack the keypress 79 | JR Z,User_Interface_End ; No keys pressed; jump to end 80 | 81 | LD DE,(Position + 0) ; Get the cross position) 82 | CP "Q" 83 | JR NZ, $+3 ; Saves lots of labels; skips 3 bytes (the JR and the DEC) 84 | DEC D 85 | CP "A" 86 | JR NZ, $+3 87 | INC D 88 | CP "O" 89 | JR NZ, $+3 90 | DEC E 91 | CP "P" 92 | JR NZ, $+3 93 | INC E 94 | LD (Position + 0),DE ; Store the new cross position 95 | 96 | LD HL,Debounce_Key ; Check whether we've pressed this key before 97 | CP (HL) 98 | JR Z,User_Interface_End ; Yes we have, so skip the debounced keys 99 | 100 | CP " " ; If space pressed, then rotate the cross positions 101 | JR NZ,User_Interface_End 102 | LD HL,(Position + 0) 103 | LD DE,(Position + 2) 104 | LD BC,(Position + 4) 105 | LD (Position + 2),HL 106 | LD (Position + 4),DE 107 | LD (Position + 0),BC 108 | 109 | User_Interface_End: POP AF ; Unstack the keypress 110 | LD (Debounce_Key),A ; Store for debounce 111 | RET 112 | 113 | ; 114 | ; Interrupt routine 115 | ; 116 | Interrupt: DI 117 | PUSH AF 118 | PUSH BC 119 | PUSH DE 120 | PUSH HL 121 | PUSH IX 122 | EXX 123 | EX AF,AF' 124 | PUSH AF 125 | PUSH BC 126 | PUSH DE 127 | PUSH HL 128 | PUSH IY 129 | 130 | LD A,(Debounce_Key) 131 | AND A 132 | JR Z,Interrupt_End 133 | 134 | LD A,2 135 | OUT (254),A 136 | LD HL,0x4000 137 | CALL Clear_Screen_Fast 138 | LD A,3 139 | OUT (254),A 140 | LD BC,(Position + 0) 141 | CALL Draw_Cross 142 | LD A,4 143 | OUT (254),A 144 | LD IY,Position 145 | CALL Draw_Triangle 146 | LD A,5 147 | OUT (254),A 148 | 149 | LD A,(Position + 2) 150 | LD H,A 151 | LD A,(Position + 0) 152 | SUB H 153 | JR NC,$+3 154 | NEG 155 | LD D,A 156 | LD E,A 157 | CALL MUL16 158 | PUSH BC 159 | LD A,(Position + 3) 160 | LD H,A 161 | LD A,(Position + 1) 162 | SUB H 163 | JR NC,$+3 164 | NEG 165 | LD D,A 166 | LD E,A 167 | CALL MUL16 168 | POP HL 169 | ADD HL,BC 170 | CALL SQR16 171 | LD BC,(Position + 2) 172 | CALL Draw_Circle 173 | LD A,6 174 | OUT (254),A 175 | 176 | Interrupt_End: CALL User_Interface 177 | LD A,7 178 | OUT (254),A 179 | 180 | POP IY 181 | POP HL 182 | POP DE 183 | POP BC 184 | POP AF 185 | EXX 186 | EX AF,AF' 187 | POP IX 188 | POP HL 189 | POP DE 190 | POP BC 191 | POP AF 192 | EI 193 | RET 194 | 195 | Sprite_Cross: DW Sprite_Cross_0,Sprite_Cross_1,Sprite_Cross_2,Sprite_Cross_3,Sprite_Cross_4,Sprite_Cross_5,Sprite_Cross_6,Sprite_Cross_7 196 | 197 | Sprite_Cross_0: DB %00000001,%00000000,%00000000 198 | DB %00000001,%00000000,%00000000 199 | DB %00000001,%00000000,%00000000 200 | DB %00000001,%00000000,%00000000 201 | DB %00000001,%00000000,%00000000 202 | DB %00000001,%00000000,%00000000 203 | DB %00000000,%00000000,%00000000 204 | DB %11111100,%01111110,%00000000 205 | DB %00000000,%00000000,%00000000 206 | DB %00000001,%00000000,%00000000 207 | DB %00000001,%00000000,%00000000 208 | DB %00000001,%00000000,%00000000 209 | DB %00000001,%00000000,%00000000 210 | DB %00000001,%00000000,%00000000 211 | DB %00000001,%00000000,%00000000 212 | DB %00000000,%00000000,%00000000 213 | 214 | Sprite_Cross_1: DB %00000000,%10000000,%00000000 215 | DB %00000000,%10000000,%00000000 216 | DB %00000000,%10000000,%00000000 217 | DB %00000000,%10000000,%00000000 218 | DB %00000000,%10000000,%00000000 219 | DB %00000000,%10000000,%00000000 220 | DB %00000000,%00000000,%00000000 221 | DB %01111110,%00111111,%00000000 222 | DB %00000000,%00000000,%00000000 223 | DB %00000000,%10000000,%00000000 224 | DB %00000000,%10000000,%00000000 225 | DB %00000000,%10000000,%00000000 226 | DB %00000000,%10000000,%00000000 227 | DB %00000000,%10000000,%00000000 228 | DB %00000000,%10000000,%00000000 229 | DB %00000000,%00000000,%00000000 230 | 231 | Sprite_Cross_2: DB %00000000,%01000000,%00000000 232 | DB %00000000,%01000000,%00000000 233 | DB %00000000,%01000000,%00000000 234 | DB %00000000,%01000000,%00000000 235 | DB %00000000,%01000000,%00000000 236 | DB %00000000,%01000000,%00000000 237 | DB %00000000,%00000000,%00000000 238 | DB %00111111,%00011111,%10000000 239 | DB %00000000,%00000000,%00000000 240 | DB %00000000,%01000000,%00000000 241 | DB %00000000,%01000000,%00000000 242 | DB %00000000,%01000000,%00000000 243 | DB %00000000,%01000000,%00000000 244 | DB %00000000,%01000000,%00000000 245 | DB %00000000,%01000000,%00000000 246 | DB %00000000,%00000000,%00000000 247 | 248 | Sprite_Cross_3: DB %00000000,%00100000,%00000000 249 | DB %00000000,%00100000,%00000000 250 | DB %00000000,%00100000,%00000000 251 | DB %00000000,%00100000,%00000000 252 | DB %00000000,%00100000,%00000000 253 | DB %00000000,%00100000,%00000000 254 | DB %00000000,%00000000,%00000000 255 | DB %00011111,%10001111,%11000000 256 | DB %00000000,%00000000,%00000000 257 | DB %00000000,%00100000,%00000000 258 | DB %00000000,%00100000,%00000000 259 | DB %00000000,%00100000,%00000000 260 | DB %00000000,%00100000,%00000000 261 | DB %00000000,%00100000,%00000000 262 | DB %00000000,%00100000,%00000000 263 | DB %00000000,%00000000,%00000000 264 | 265 | Sprite_Cross_4: DB %00000000,%00010000,%00000000 266 | DB %00000000,%00010000,%00000000 267 | DB %00000000,%00010000,%00000000 268 | DB %00000000,%00010000,%00000000 269 | DB %00000000,%00010000,%00000000 270 | DB %00000000,%00010000,%00000000 271 | DB %00000000,%00000000,%00000000 272 | DB %00001111,%11000111,%11100000 273 | DB %00000000,%00000000,%00000000 274 | DB %00000000,%00010000,%00000000 275 | DB %00000000,%00010000,%00000000 276 | DB %00000000,%00010000,%00000000 277 | DB %00000000,%00010000,%00000000 278 | DB %00000000,%00010000,%00000000 279 | DB %00000000,%00010000,%00000000 280 | DB %00000000,%00000000,%00000000 281 | 282 | Sprite_Cross_5: DB %00000000,%00001000,%00000000 283 | DB %00000000,%00001000,%00000000 284 | DB %00000000,%00001000,%00000000 285 | DB %00000000,%00001000,%00000000 286 | DB %00000000,%00001000,%00000000 287 | DB %00000000,%00001000,%00000000 288 | DB %00000000,%00000000,%00000000 289 | DB %00000111,%11100011,%11110000 290 | DB %00000000,%00000000,%00000000 291 | DB %00000000,%00001000,%00000000 292 | DB %00000000,%00001000,%00000000 293 | DB %00000000,%00001000,%00000000 294 | DB %00000000,%00001000,%00000000 295 | DB %00000000,%00001000,%00000000 296 | DB %00000000,%00001000,%00000000 297 | DB %00000000,%00000000,%00000000 298 | 299 | Sprite_Cross_6: DB %00000000,%00000100,%00000000 300 | DB %00000000,%00000100,%00000000 301 | DB %00000000,%00000100,%00000000 302 | DB %00000000,%00000100,%00000000 303 | DB %00000000,%00000100,%00000000 304 | DB %00000000,%00000100,%00000000 305 | DB %00000000,%00000000,%00000000 306 | DB %00000011,%11110001,%11111000 307 | DB %00000000,%00000000,%00000000 308 | DB %00000000,%00000100,%00000000 309 | DB %00000000,%00000100,%00000000 310 | DB %00000000,%00000100,%00000000 311 | DB %00000000,%00000100,%00000000 312 | DB %00000000,%00000100,%00000000 313 | DB %00000000,%00000100,%00000000 314 | DB %00000000,%00000000,%00000000 315 | 316 | Sprite_Cross_7: DB %00000000,%00000010,%00000000 317 | DB %00000000,%00000010,%00000000 318 | DB %00000000,%00000010,%00000000 319 | DB %00000000,%00000010,%00000000 320 | DB %00000000,%00000010,%00000000 321 | DB %00000000,%00000010,%00000000 322 | DB %00000000,%00000000,%00000000 323 | DB %00000001,%11111000,%11111100 324 | DB %00000000,%00000000,%00000000 325 | DB %00000000,%00000010,%00000000 326 | DB %00000000,%00000010,%00000000 327 | DB %00000000,%00000010,%00000000 328 | DB %00000000,%00000010,%00000000 329 | DB %00000000,%00000010,%00000000 330 | DB %00000000,%00000010,%00000000 331 | DB %00000000,%00000000,%00000000 332 | 333 | Code_Length: EQU $-Code_Start+1 334 | 335 | SAVESNA "demo/demo_vector.sna", Code_Start 336 | 337 | -------------------------------------------------------------------------------- /lib/gui.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: GUI 3 | ; Author: Dean Belfield 4 | ; Created: 06/02/2020 5 | ; Last Updated: 06/02/2020 6 | ; 7 | ; Requires: 8 | ; 9 | ; Modinfo: 10 | ; 11 | 12 | ; DE - Address of character set 13 | ; HL - Screen address 14 | ; C - Box width ( > 2 ) 15 | ; B - Box height ( > 2 ) 16 | ; 17 | Print_Box: DEC C ; Decrement the loop counters to take edges into account 18 | DEC B 19 | DEC B 20 | CALL 3F ; The top and bottom ae the same, so call the end of our code 21 | CALL Char_Address_Down ; Go down to next character line 22 | ; 23 | ; Do the middle bit 24 | ; 25 | 2: PUSH BC 26 | PUSH DE 27 | PUSH HL 28 | PUSH HL 29 | CALL Print_UDG8 30 | POP HL 31 | LD A, C 32 | ADD A, L 33 | LD L, A 34 | CALL Print_UDG8 35 | POP HL 36 | POP DE 37 | POP BC 38 | CALL Char_Address_Down 39 | DJNZ 2B 40 | LD A, 16 41 | ADD_DE_A 42 | ; 43 | ; This code is called at the top and it falls through here to put the top and bottom 44 | ; bits of the panel on 45 | ; 46 | 3: PUSH BC 47 | PUSH HL 48 | PUSH HL 49 | CALL Print_UDG8 50 | POP HL 51 | INC L 52 | DEC C 53 | 4: PUSH DE 54 | PUSH HL 55 | CALL Print_UDG8 56 | POP HL 57 | POP DE 58 | INC L 59 | DEC C 60 | JR NZ, 4B 61 | LD A, 8 62 | ADD_DE_A 63 | CALL Print_UDG8 64 | POP HL 65 | POP BC 66 | RET 67 | -------------------------------------------------------------------------------- /lib/keyboard.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum Keyboard Routines 3 | ; Author: Dean Belfield 4 | ; Created: 29/07/2011 5 | ; Last Updated: 29/07/2011 6 | ; 7 | ; Requires: 8 | ; 9 | ; Modinfo: 10 | ; 11 | 12 | ; Read the in-game controls 13 | ; HL: The control map 14 | ; Returns: 15 | ; A: Input flags - 000UDLRF (Up, Down, Left, Right, Fire) 16 | ; Zero flag set if no key pressed 17 | ; 18 | Read_Controls: LD D, 5 ; Number of controls to check 19 | LD E, 0 ; The output flags 20 | LD C,0xFE ; Low is always 0xFE for reading keyboard 21 | Read_Controls1: LD B,(HL) ; Get the keyboard port address 22 | INC HL 23 | IN A,(C) ; Read the rows in 24 | AND (HL) ; And with the mask 25 | JR NZ, Read_Controls2 ; Skip if not pressed (bit is 0) 26 | SCF ; Set C flag 27 | Read_Controls2: RL E ; Rotate the carry flag into E 28 | INC HL 29 | DEC D 30 | JR NZ, Read_Controls1 ; Loop 31 | LD A,E ; Fetch the key flags 32 | AND A ; Check for 0 33 | RET 34 | 35 | 36 | ; As Read_Keyboard, but with debounce 37 | ; 38 | Read_Keyboard_Debounce: CALL Read_Keyboard ; A debounced versiion - Read the keyboard 39 | AND A ; Quick way to do CP 0 40 | JR NZ, Read_Keyboard_Debounce ; Loop until key released 41 | 1: CALL Read_Keyboard ; And second loop reading the keyboard 42 | AND A ; CP 0 43 | JR Z, 1B ; Loop until key is pressed 44 | RET 45 | 46 | ; Read the keyboard and return an ASCII character code 47 | ; Returns: 48 | ; A: The character code, or 0 if no key pressed 49 | ; BC: The keyboard port (0x7FFE to 0xFEFE) 50 | ; 51 | Read_Keyboard: LD HL,Keyboard_Map ; Point HL at the keyboard list 52 | LD D,8 ; This is the number of ports (rows) to check 53 | LD C,0xFE ; Low is always 0xFE for reading keyboard ports 54 | Read_Keyboard_0: LD B,(HL) ; Get the keyboard port address 55 | INC HL ; Increment to keyboard list of table 56 | IN A,(C) ; Read the row of keys in 57 | AND 0x1F ; We are only interested in the first five bits 58 | LD E,5 ; This is the number of keys in the row 59 | Read_Keyboard_1: SRL A ; Shift A right; bit 0 sets carry bit 60 | JR NC,Read_Keyboard_2 ; If the bit is 0, we've found our key 61 | INC HL ; Go to next table address 62 | DEC E ; Decrement key loop counter 63 | JR NZ,Read_Keyboard_1 ; Loop around until this row finished 64 | DEC D ; Decrement row loop counter 65 | JR NZ,Read_Keyboard_0 ; Loop around until we are done 66 | AND A ; Clear A (no key found) 67 | RET 68 | Read_Keyboard_2: LD A,(HL) ; We've found a key at this point; fetch the character code! 69 | RET 70 | 71 | Keyboard_Map: DB 0xFE,"#","Z","X","C","V" 72 | DB 0xFD,"A","S","D","F","G" 73 | DB 0xFB,"Q","W","E","R","T" 74 | DB 0xF7,"1","2","3","4","5" 75 | DB 0xEF,"0","9","8","7","6" 76 | DB 0xDF,"P","O","I","U","Y" 77 | DB 0xBF,"#","L","K","J","H" 78 | DB 0x7F," ","#","M","N","B" 79 | 80 | Input_Custom: DB 0xFB, %00000001 ; Q (Up) 81 | DB 0xFD, %00000001 ; A (Down) 82 | DB 0xDF, %00000010 ; O (Left) 83 | DB 0xDF, %00000001 ; P (Right) 84 | DB 0x7F, %00000001 ; Space (Fire) 85 | 86 | -------------------------------------------------------------------------------- /lib/macros.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: Macros 3 | ; Author: Dean Belfield 4 | ; Created: 06/02/2020 5 | ; Last updated: 05/04/2020 6 | ; 7 | ; Requires: 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 05/04/2020: Local labels now used in macros. Added MIN and MAX 12 | 13 | MIN: MACRO P1 ; Get min of P1 and A in A 14 | CP P1 ; Compare A with P1 15 | JR C, .S1 ; Skip if P1 > A 16 | LD A, P1 ; Assign P1 to A 17 | .S1 ; 18 | ENDM 19 | 20 | MAX: MACRO P1 ; Get max of P1 and A in A 21 | CP P1 ; Compare A with P1 22 | JR NC, .S1 ; Skip if P1 > A 23 | LD A, P1 ; Assign P1 to A 24 | .S1 ; 25 | ENDM 26 | 27 | LD_BC_A: MACRO 28 | LD C, A ; Turn A into 16 bit number 29 | ADD A, A ; Push sign into carry 30 | SBC A, A ; Turn into 0 or -1 (0xFF) 31 | LD B, A ; Store in MSB 32 | ENDM 33 | 34 | LD_DE_A: MACRO 35 | LD E, A 36 | ADD A, A 37 | SBC A, A 38 | LD D, A 39 | ENDM 40 | 41 | LD_HL_A: MACRO 42 | LD L, A 43 | ADD A, A 44 | SBC A, A 45 | LD H, A 46 | ENDM 47 | 48 | ADD_HL_A: MACRO 49 | ADD A, L 50 | LD L,A 51 | JR NC, .S1 52 | INC H 53 | .S1 ; 54 | ENDM 55 | 56 | ADD_DE_A: MACRO 57 | ADD A, E 58 | LD E, A 59 | JR NC, .S1 60 | INC D 61 | .S1 ; 62 | ENDM 63 | 64 | ADD_BC_A: MACRO 65 | ADD A,C 66 | LD C, A 67 | JR NC, .S1 68 | INC B 69 | .S1 ; 70 | ENDM 71 | 72 | ADD_BC_A_SIGNED: MACRO 73 | OR A 74 | JP P,.S1 75 | DEC B 76 | .S1 ADD A, C 77 | LD C, A 78 | ADC A, B 79 | SUB C 80 | LD B, A 81 | ENDM 82 | 83 | ADD_DE_A_SIGNED: MACRO 84 | OR A 85 | JP P, .S1 86 | DEC D 87 | .S1 ADD A, E 88 | LD E, A 89 | ADC A, D 90 | SUB E 91 | LD D, A 92 | ENDM 93 | 94 | ADD_HL_A_SIGNED: MACRO 95 | OR A 96 | JP P, .S1 97 | DEC H 98 | .S1 ADD A, L 99 | LD L, A 100 | ADC A, H 101 | SUB L 102 | LD H, A 103 | ENDM 104 | 105 | SUB_HL_BC: MACRO 106 | OR A 107 | SBC HL,BC 108 | ENDM 109 | 110 | SUB_HL_DE: MACRO 111 | OR A 112 | SBC HL,DE 113 | ENDM 114 | -------------------------------------------------------------------------------- /lib/math.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum Math Routines 3 | ; Author: Dean Belfield 4 | ; Created: 22/08/2011 5 | ; Last Updated: 08/04/2020 6 | ; 7 | ; Requires: 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 08/04/2020: Added 8, 16 and 24 bit multiply, 16 and 24 bit divide, and 32 bit square root 12 | 13 | ; The tables - these must be on a page boundary 14 | ; 15 | ALIGN 0x100 16 | 17 | QMULTABL LUA ALLPASS 18 | for i = 0, 255, 16 do 19 | s = "" 20 | sep = " 0x" 21 | for j = i, i+15 do 22 | h = math.floor((j * j) / 256) 23 | l = (j * j) - (h * 256) 24 | s = s .. string.format("%s%02X", sep, l) 25 | sep = ",0x" 26 | end 27 | _pc("DB " .. s) 28 | end 29 | ENDLUA 30 | 31 | QMULTABH: LUA ALLPASS 32 | for i = 0, 255, 16 do 33 | s = "" 34 | sep = " 0x" 35 | for j = i, i+15 do 36 | h = math.floor((j * j) / 256) 37 | l = (j * j) - (h * 256) 38 | s = s .. string.format("%s%02X", sep, h) 39 | sep = ",0x" 40 | end 41 | _pc("DB " .. s) 42 | end 43 | ENDLUA 44 | 45 | ; 8-bit unsigned quick multiply, with divide by 256 and negative result 46 | ; Returns A=-(B*C)/256 47 | ; 48 | MUL8_DIV256_NEG: CALL MUL8_DIV256 49 | NEG 50 | RET 51 | 52 | ; 8-bit unsigned quick multiply, with divide by 256 53 | ; Returns A=(B*C)/256 54 | ; 55 | MUL8_DIV256: LD H,high QMULTABH 56 | LD A,B 57 | SUB C 58 | JR NC,1F 59 | NEG 60 | SRL A 61 | LD C,A 62 | ADD A,B 63 | LD L,A 64 | LD A,(HL) 65 | LD L,C 66 | SUB (HL) 67 | RET 68 | 1: SRL A 69 | LD B,A 70 | ADD A,C 71 | LD L,A 72 | LD A,(HL) 73 | LD L,B 74 | SUB (HL) 75 | RET 76 | 77 | ; 16-bit signed multiply 78 | ; Returns BC=D*E 79 | ; Main entry point: QMUL16S 80 | ; 81 | 1: LD A,D 82 | NEG 83 | LD D,A 84 | BIT 7,E 85 | JR Z,MUL16_NEG 86 | LD A,E 87 | NEG 88 | LD E,A 89 | JR MUL16 90 | MUL16S: BIT 7,D 91 | JR NZ,1B 92 | BIT 7,E 93 | JR Z,MUL16 94 | LD A,E 95 | NEG 96 | LD E,A 97 | 98 | ; 16-bit unsigned multiply with negative result 99 | ; Returns BC=D*E 100 | ; 101 | MUL16_NEG: CALL MUL16 102 | XOR A 103 | LD H,A 104 | LD L,A 105 | SBC HL,BC 106 | LD B,H 107 | LD C,L 108 | RET 109 | 110 | ; 16-bit unsigned multiply 111 | ; Returns BC=D*E 112 | ; 113 | MUL16: LD H,high QMULTABL 114 | LD A,D 115 | SUB E 116 | JR C, 2F 117 | SRL A 118 | LD B,A 119 | JR C, 1F 120 | ADD A,E 121 | LD C,A 122 | LD L,C 123 | LD A,(HL) 124 | LD L,B 125 | SUB (HL) 126 | LD L,C 127 | LD C,A 128 | INC H 129 | LD A,(HL) 130 | LD L,B 131 | SBC A,(HL) 132 | LD B,A 133 | RET 134 | 1: ADD A,E 135 | LD C,A 136 | LD L,C 137 | LD A,(HL) 138 | LD L,B 139 | SUB (HL) 140 | LD L,C 141 | LD C,A 142 | INC H 143 | LD A,(HL) 144 | LD L,B 145 | SBC A,(HL) 146 | LD B,A 147 | LD A,C 148 | ADD A,E 149 | LD C,A 150 | RET NC 151 | INC B 152 | RET 153 | 2: NEG 154 | SRL A 155 | LD B,A 156 | JR C,3F 157 | ADD A,D 158 | LD C,A 159 | LD L,C 160 | LD A,(HL) 161 | LD L,B 162 | SUB (HL) 163 | LD L,C 164 | LD C,A 165 | INC H 166 | LD A,(HL) 167 | LD L,B 168 | SBC A,(HL) 169 | LD B,A 170 | RET 171 | 3: ADD A,D 172 | LD C,A 173 | LD L,C 174 | LD A,(HL) 175 | LD L,B 176 | SUB (HL) 177 | LD L,C 178 | LD C,A 179 | INC H 180 | LD A,(HL) 181 | LD L,B 182 | SBC A,(HL) 183 | LD B,A 184 | LD A,C 185 | ADD A,D 186 | LD C,A 187 | RET NC 188 | INC B 189 | RET 190 | 191 | ; Same as MUL24, but the answer is negative 192 | ; AHL=-(DE*BC) 193 | ; 194 | MUL24_NEG: CALL MUL24 195 | XOR 255 196 | EX DE,HL 197 | LD HL,0 198 | SBC HL, DE 199 | CCF 200 | ADC A,0 201 | RET 202 | 203 | ; Multiply (24 bit) 204 | ; AHL=DE*BC 205 | ; 206 | MUL24: XOR A 207 | LD H,A 208 | LD L,A 209 | EX AF,AF 210 | LD A,16 211 | 1: EX AF,AF 212 | ADD HL,HL 213 | RLA 214 | RL C 215 | RL B 216 | JR NC, 2F 217 | ADD HL,DE 218 | ADC A,0 219 | 2: EX AF,AF 220 | DEC A 221 | JR NZ,1B 222 | EX AF,AF 223 | RET 224 | 225 | ; Divide (16 bit) 226 | ; Returns HL=HL/BC 227 | ; 228 | DIV16: PUSH HL 229 | XOR A 230 | LD H,A 231 | LD L,A 232 | EXX 233 | LD B,16 234 | POP HL 235 | 1: ADC HL,HL 236 | EXX 237 | ADC HL,HL 238 | RLA 239 | SBC HL,BC 240 | JR NC,2F 241 | ADD HL,BC 242 | 2: CCF 243 | EXX 244 | DJNZ 1B 245 | ADC HL,HL 246 | RET 247 | 248 | ; Divide (24 bit) 249 | ; Returns result in AHL 250 | ; 251 | DIVIDEND: DS 3 252 | DIVISOR: DS 3 253 | 254 | DIV24: LD BC,(DIVISOR) 255 | LD A,(DIVISOR+2) 256 | LD D,A 257 | XOR A 258 | LD H,A 259 | LD L,A 260 | EXX 261 | LD B,24 262 | LD HL,(DIVIDEND) 263 | LD A,(DIVIDEND+2) 264 | LD E,A 265 | XOR A 266 | 1: ADC HL,HL 267 | RL E 268 | EXX 269 | ADC HL,HL 270 | RLA 271 | SBC HL,BC 272 | SBC D 273 | JR NC,2F 274 | ADD HL,BC 275 | ADC D 276 | 2: CCF 277 | EXX 278 | DJNZ 1B 279 | ADC HL,HL 280 | RL E 281 | LD A,E 282 | RET 283 | 284 | ; Square Root (16 bit) 285 | ; HL=number to find square root of 286 | ; Returns result in A 287 | ; 288 | SQR16: LD DE,1 289 | XOR A 290 | DEC A 291 | 1: SBC HL,DE 292 | INC DE 293 | INC DE 294 | INC A 295 | JR NC,1B 296 | RET 297 | 298 | ; Square Root (32 bit) 299 | ; BCDE=number to find square root of 300 | ; Returns result in DE 301 | ; 302 | SQR32: LD A,B 303 | PUSH DE 304 | POP IX 305 | LD D,0 306 | LD E,D 307 | LD H,D 308 | LD L,D 309 | LD B,16 310 | 1: SUB 0x40 311 | SBC HL,DE 312 | JR NC,2F 313 | ADD A,0x40 314 | ADC HL,DE 315 | 2: CCF 316 | RL E 317 | RL D 318 | ADD IX,IX 319 | RL C 320 | RLA 321 | ADC HL,HL 322 | DJNZ 1B 323 | RET 324 | 325 | ; 16 bit random number routine I found on the web 326 | ; Returns a pseudo-random number in the HL register 327 | ; 328 | RND16_SEED: EQU 12345 329 | RND16: LD DE,RND16_SEED 330 | LD A,D 331 | LD H,E 332 | LD L,253 333 | OR A 334 | SBC HL,DE 335 | SBC A,0 336 | SBC HL,DE 337 | LD D,0 338 | SBC A,D 339 | LD E,A 340 | SBC HL,DE 341 | JR NC,1F 342 | INC HL 343 | 1: LD (RND16+1),HL 344 | RET -------------------------------------------------------------------------------- /lib/output.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum Standard Output Routines 3 | ; Author: Dean Belfield 4 | ; Created: 29/07/2011 5 | ; Last Updated: 08/02/2020 6 | ; 7 | ; Requires: 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 02/07/2012: Added Pixel_Address_Down and Pixel_Address_Up routines 12 | ; 04/07/2012: Moved Clear_Screen to Screen_Buffer 13 | ; 08/02/2010: Added Print_BC 14 | ; Moved Clear_Screen into here (originally in screen_buffer.z80) 15 | ; All output routines refactored to use HL for screen address 16 | ; Added Fill_Attr routine 17 | ; 18 | 19 | ; Simple clear-screen routine 20 | ; Uses LDIR to block clear memory 21 | ; A: Colour to clear attribute block of memory with 22 | ; 23 | Clear_Screen: LD HL,16384 ; Start address of screen bitmap 24 | LD DE,16385 ; Address + 1 25 | LD BC,6144 ; Length of bitmap memory to clear 26 | LD (HL),0 ; Set the first byte to 0 27 | LDIR ; Copy this byte to the second, and so on 28 | LD BC,767 ; Length of attribute memory, less one to clear 29 | LD (HL),A ; Set the first byte to A 30 | LDIR ; Copy this byte to the second, and so on 31 | RET 32 | 33 | ; Fill a box of the screen with a solid colour 34 | ; A: The colour 35 | ; HL: Address in the attribute map 36 | ; C: Width 37 | ; B: Height 38 | ; 39 | Fill_Attr: LD DE,32 40 | 1: PUSH HL 41 | PUSH BC 42 | 2: LD (HL), A 43 | INC L 44 | DEC C 45 | JR NZ, 2B 46 | POP BC 47 | POP HL 48 | ADD HL,DE 49 | DJNZ 1B 50 | RET 51 | 52 | ; Print String Data 53 | ; First two bytes of string contain X and Y char position, then the string 54 | ; Individual strings are terminated with 0xFE 55 | ; End of data is terminated with 0xFF 56 | ; IX: Address of string 57 | ; 58 | Print_String: LD L,(IX+0) ; Fetch the X coordinate 59 | INC IX ; Increase HL to the next memory location 60 | LD H,(IX+0) ; Fetch the Y coordinate 61 | INC IX ; Increase HL to the next memory location 62 | CALL Get_Char_Address ; Calculate the screen address (in DE) 63 | Print_String_0: LD A,(IX) ; Fetch the character to print 64 | INC IX ; Increase HL to the next character 65 | CP 0xFE ; Compare with 0xFE 66 | JR Z,Print_String ; If it is equal to 0xFE then loop back to print next string 67 | RET NC ; If it is greater or equal to (carry bit set) then 68 | CALL Print_Char ; Print the character 69 | INC L ; Go to the next screen address 70 | JR Print_String_0 ; Loop back to print next character 71 | RET 72 | 73 | ; Get screen address 74 | ; H = Y character position 75 | ; L = X character position 76 | ; Returns address in HL 77 | ; 78 | Get_Char_Address: LD A,H 79 | AND %00000111 80 | RRA 81 | RRA 82 | RRA 83 | RRA 84 | OR L 85 | LD L,A 86 | LD A,H 87 | AND %00011000 88 | OR %01000000 89 | LD H,A 90 | RET ; Returns screen address in HL 91 | 92 | ; Move HL down one character line 93 | ; 94 | Char_Address_Down: LD A, L 95 | ADD A, 32 96 | LD L, A 97 | RET NC 98 | LD A, H 99 | ADD A, 8 100 | LD H, A 101 | RET 102 | 103 | ; Get screen address 104 | ; B = Y pixel position 105 | ; C = X pixel position 106 | ; Returns address in HL and pixel position within character in A 107 | ; 108 | Get_Pixel_Address: LD A,B ; Calculate Y2,Y1,Y0 109 | AND %00000111 ; Mask out unwanted bits 110 | OR %01000000 ; Set base address of screen 111 | LD H,A ; Store in H 112 | LD A,B ; Calculate Y7,Y6 113 | RRA ; Shift to position 114 | RRA 115 | RRA 116 | AND %00011000 ; Mask out unwanted bits 117 | OR H ; OR with Y2,Y1,Y0 118 | LD H,A ; Store in H 119 | LD A,B ; Calculate Y5,Y4,Y3 120 | RLA ; Shift to position 121 | RLA 122 | AND %11100000 ; Mask out unwanted bits 123 | LD L,A ; Store in L 124 | LD A,C ; Calculate X4,X3,X2,X1,X0 125 | RRA ; Shift into position 126 | RRA 127 | RRA 128 | AND %00011111 ; Mask out unwanted bits 129 | OR L ; OR with Y5,Y4,Y3 130 | LD L,A ; Store in L 131 | LD A,C 132 | AND 7 133 | RET 134 | 135 | ; Move HL down one pixel line 136 | ; 137 | Pixel_Address_Down: INC H ; Go down onto the next pixel line 138 | LD A,H ; Check if we have gone onto next character boundary 139 | AND 7 140 | RET NZ ; No, so skip the next bit 141 | LD A,L ; Go onto the next character line 142 | ADD A,32 143 | LD L,A 144 | RET C ; Check if we have gone onto next third of screen 145 | LD A,H ; Yes, so go onto next third 146 | SUB 8 147 | LD H,A 148 | RET 149 | 150 | ; Move HL up one pixel line 151 | ; 152 | Pixel_Address_Up: DEC H ; Go up onto the next pixel line 153 | LD A,H ; Check if we have gone onto the next character boundary 154 | AND 7 155 | CP 7 156 | RET NZ 157 | LD A,L 158 | SUB 32 159 | LD L,A 160 | RET C 161 | LD A,H 162 | ADD A,8 163 | LD H,A 164 | RET 165 | 166 | ; Print a single BCD value 167 | ; A: Character to print 168 | ; HL: Screen address to print character at 169 | ; 170 | Print_BCD_8 LD A, (IX) : INC IX: CALL Print_BCD 171 | Print_BCD_6 LD A, (IX) : INC IX: CALL Print_BCD 172 | Print_BCD_4 LD A, (IX) : INC IX: CALL Print_BCD 173 | Print_BCD_2 LD A, (IX) : INC IX 174 | Print_BCD: PUSH AF ; Store the value 175 | AND 0xF0 ; Get the top nibble 176 | RRA ; Shift into bottom nibble 177 | RRA 178 | RRA 179 | RRA 180 | ADD A, '0' ; Add to ASCII '0' 181 | CALL Print_Char ; Print the character 182 | INC L ; Move right one space 183 | POP AF 184 | AND 0x0F ; Get the bottom nibble 185 | ADD A, '0' ; Add to ASCII '0' 186 | CALL Print_Char ; Print 187 | INC L ; Move right one space 188 | RET 189 | 190 | ; Print a single character out to an X/Y position 191 | ; A: Character to print 192 | ; C: X Coordinate 193 | ; B: Y Coordinate 194 | ; DE: Address of character set 195 | ; 196 | Print_Char_At: PUSH AF 197 | CALL Get_Char_Address 198 | POP AF ; Fall through to Print_Char 199 | ; 200 | ; Print a single character out to a screen address 201 | ; A: Character to print 202 | ; HL: Screen address to print character at 203 | ; DE: Address of character set (if entering at Print_Char_UDG) 204 | ; No SM code here - needs to be re-enterent if called on interrupt 205 | ; 206 | Print_Char: LD DE, 0x3C00 ; Address of character set in ROM 207 | PUSH HL 208 | LD B, 0 ; Get index into character set 209 | LD C, A 210 | DUP 3 211 | SLA C 212 | RL B 213 | EDUP 214 | EX DE, HL 215 | ADD HL, BC 216 | EX DE, HL 217 | CALL Print_UDG8 218 | POP HL 219 | RET 220 | 221 | ; Print a UDG (Single Height) 222 | ; DE - Character data 223 | ; HL - Screen address 224 | ; 225 | Print_UDG8: LD B,8 ; Loop counter 226 | 2: LD A,(DE) ; Get the byte from the ROM into A 227 | LD (HL),A ; Stick A onto the screen 228 | INC DE ; Goto next byte of character 229 | INC H ; Goto next line on screen 230 | DJNZ 2B ; Loop around whilst it is Not Zero (NZ) 231 | RET 232 | -------------------------------------------------------------------------------- /lib/score.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: Score 3 | ; Author: Dean Belfield 4 | ; Created: 06/02/2020 5 | ; Last Updated: 06/02/2020 6 | ; 7 | ; Requires: 8 | ; 9 | ; Modinfo: 10 | ; 11 | 12 | ; Add to a score 13 | ; DE: Score to add (in BCD) 14 | ; IX: Score address 15 | ; 16 | ADD_SCORE: LD A, E 17 | ADD A, (IX+3) 18 | DAA 19 | LD (IX+3), A 20 | LD A, D 21 | ADC A, (IX+2) 22 | DAA 23 | LD (IX+2), A 24 | LD A, 0 25 | ADC A, (IX+1) 26 | DAA 27 | LD (IX+1), A 28 | LD A, 0 29 | ADC A, (IX+0) 30 | DAA 31 | LD (IX+0), A 32 | RET -------------------------------------------------------------------------------- /lib/screen_buffer.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum Screen Buffer Routines 3 | ; Author: Dean Belfield 4 | ; Created: 20/08/2011 5 | ; Last Updated: 11/04/2020 6 | ; 7 | ; Requires: 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 04/07/2012: Added Clear_Screen and Clear_Screen_Fast routines 12 | ; 08/02/2020: Moved Clear_Screen to output.z80 13 | ; 10/04/2020: Added Get_Pixel_Address_B 14 | ; Fixed Copy_Screen to copy entire screen width, not just half a screen 15 | ; 11/04/2020: Get_Pixel_Address_B now only compiled if SCREEN_BUFFER is declared 16 | ; 17 | 18 | ; Get screen address (based upon a 32x192 byte buffer where each pixel row is 32 bytes below the previous) 19 | ; Functionally equivalent to Get_Pixel_Address in output.z80 that deals with the Spectrum screen memory 20 | ; B = Y pixel position 21 | ; C = X pixel position 22 | ; Returns address in HL and pixel position within character in A 23 | ; 24 | IFDEF SCREEN_BUFFER 25 | Get_Pixel_Address_B: LD L,B ; Calculate Y2,Y1,Y0 26 | LD H,0 27 | DUP 5 28 | ADD HL,HL 29 | EDUP 30 | LD A,high SCREEN_BUFFER 31 | ADD A,H 32 | LD H,A 33 | LD A,C ; Calculate X4,X3,X2,X1,X0 34 | RRA ; Shift into position 35 | RRA 36 | RRA 37 | AND %00011111 ; Mask out unwanted bits 38 | OR L ; OR with Y5,Y4,Y3 39 | LD L,A ; Store in L 40 | LD A,C 41 | AND 7 42 | RET 43 | ENDIF 44 | 45 | ; Fast clear-screen routine 46 | ; Uses the stack to block clear memory 47 | ; HL: Address of screen buffer 48 | ; 49 | Clear_Screen_Fast: LD (Clear_Screen_Fast_End+1),SP ; Store the stack (self modding code) 50 | LD DE,0x1800 51 | ADD HL,DE ; Move to end of buffer 52 | LD SP,HL ; Set stack to pointer 53 | LD DE,0 ; We are clearing, so set DE to 0 54 | LD B,128 ; We loop 128 times - 24 words * 128 = 6144 bytes 55 | 1: DUP 24 56 | PUSH DE 57 | EDUP 58 | DJNZ 1B 59 | Clear_Screen_Fast_End: LD SP,0x0000 ; Restore the stack 60 | RET 61 | 62 | ; A voodoo screen buffer routine that uses the stack to quickly shift bytes around 63 | ; HL: Location of offscreen buffer 64 | ; 65 | Copy_Screen: LD (Copy_Screen_End+1),SP ; This is some self-modifying code; stores the stack pointer in an LD SP,nn instruction at the end 66 | EXX ; Switch to alternate registers 67 | LD HL,0x4000 ; HL' = screen pointer 68 | 1: LD (Copy_Screen_HL1+1), HL ; Store the screen position for later 69 | EXX ; Switch to normal registers 70 | 71 | LD SP,HL ; HL = Buffer address 72 | POP AF ; Fetch the data 73 | POP BC 74 | POP DE 75 | POP IX 76 | EXX ; Switch to alternate registers 77 | EX AF,AF' 78 | LD DE,16 79 | ADD HL,DE ; Add Offset for screen 80 | POP AF 81 | POP BC 82 | POP DE 83 | POP IY 84 | LD (Copy_Screen_SP1+1),SP ; Save the current buffer address for later 85 | LD SP,HL ; The screen address 86 | PUSH IY ; Push the data 87 | PUSH DE 88 | PUSH BC 89 | PUSH AF 90 | EX AF,AF' ; Switch to normal registers 91 | EXX 92 | PUSH IX 93 | PUSH DE 94 | PUSH BC 95 | PUSH AF 96 | Copy_Screen_SP1: LD HL,0 ; HL = Buffer 97 | 98 | LD SP,HL ; HL = Buffer address 99 | POP AF ; Fetch the data 100 | POP BC 101 | POP DE 102 | POP IX 103 | EXX ; Switch to alternate registers 104 | EX AF,AF' 105 | LD DE,16 106 | ADD HL,DE ; Add Offset for screen 107 | POP AF 108 | POP BC 109 | POP DE 110 | POP IY 111 | LD (Copy_Screen_SP2+1),SP ; Save the current buffer address for later 112 | LD SP,HL ; The screen address 113 | PUSH IY ; Push the data 114 | PUSH DE 115 | PUSH BC 116 | PUSH AF 117 | EX AF,AF' ; Switch to normal registers 118 | EXX 119 | PUSH IX 120 | PUSH DE 121 | PUSH BC 122 | PUSH AF 123 | Copy_Screen_SP2: LD HL,0 ; HL = Buffer 124 | 125 | EXX ; Switch to alternate registers 126 | Copy_Screen_HL1: LD HL,0 ; HL' = Screen 127 | INC H ; Drop down 1 pixel row in screen memory 128 | LD A,H ; Check whether we've gone past a character boundary 129 | AND 0x07 130 | JR NZ,1B 131 | LD A,H ; Go to the next character line 132 | SUB 8 133 | LD H,A 134 | ld A,L 135 | ADD A,32 136 | LD L,A 137 | JR NC,1B ; Check for next third 138 | LD A,H ; Go to next third 139 | ADD A,8 140 | LD H,A 141 | CP 0x58 ; Check for end of screen memory 142 | JR NZ,1B ; Loop back if not reached 143 | 144 | Copy_Screen_End: LD SP,0 ; Restore the SP 145 | EXX ; Switch to normal registers 146 | RET 147 | 148 | -------------------------------------------------------------------------------- /lib/scroll.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum Stack Based Scroller 3 | ; Author: Dean Belfield 4 | ; Created: 30/01/2020 5 | ; Last Updated: 23/02/2020 6 | ; 7 | ; Requires: scroll_attr 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 30/01/2020 Initial proof-of-concept - missing mapping code 12 | ; 03/02/2020 Tilesets for each row are now pulled from the map data 13 | ; 05/02/2020 Tileset now has BC as blank tile - so 0x00 is now blank in map data rather than 0x20 14 | ; Fixed order of pops in Scroll_Tile_Part so that map tile graphics correctly map to map data 15 | ; Added Scroll_BlockAtXY and Scroll_Move 16 | ; 23/02/2020: Minor tweaks to add scrolling colours (attributes) 17 | ; 18 | 19 | include "../lib/scroll_attr.z80" ; Extra code for scrolling the attributes 20 | 21 | ; Macros 22 | ; 23 | SCROLL_TILE_ROW: MACRO callbackAddress 24 | LD HL, 0 ; This will be the address of the tileset graphics 25 | LD IX, callbackAddress ; The address of the self-modified push code 26 | CALL Scroll_Tile_Full 27 | ENDM 28 | 29 | ; Move the scroll up or down a number of pixels 30 | ; A: Number of pixels to move the scroll 31 | ; 32 | Scroll_Move: LD HL, (Vertical_Scroll_Offset) 33 | LD_BC_A 34 | ADD HL, BC 35 | BIT 7, H 36 | JR Z, 1F ; If result positive, not reached top 37 | LD HL, 0 ; If we've reached the top, then zero the counter 38 | 1: LD (Vertical_Scroll_Offset), HL 39 | LD BC, 16 * (Demo_Map_Length - 12) ; Bottom boundary check 40 | SUB_HL_BC 41 | RET C 42 | LD (Vertical_Scroll_Offset), BC 43 | RET 44 | 45 | ; Get the scroll block value at map position (X,Y) 46 | ; HL: Map Array 47 | ; C: X position 48 | ; B: Y position 49 | ; Returns: 50 | ; HL: Block address 51 | ; A: Block value 52 | ; 53 | Scroll_BlockAtXY: LD DE, (Vertical_Scroll_Offset) ; Get the vertical scroll offset (in pixels) 54 | LD A, B ; And the sprite Y coordinate 55 | ADD_DE_A ; Add them 56 | AND 0xF0 ; Zero the lower nibble 57 | LD E, A 58 | ADD HL, DE ; And add to the map table address 59 | SRL C ; Divide the X coordinate by 16 60 | SRL C 61 | SRL C 62 | SRL C 63 | LD A, 15 ; Map is stored right-to-left 64 | SUB C ; So do 15 - E 65 | LD E, A 66 | LD D, 0 67 | ADD HL, DE ; Add them 68 | LD A,(HL) 69 | RET 70 | 71 | ; Initialise 72 | ; HL = Map Array (12 bytes long) 73 | ; 74 | Initialise_Scroll: LD DE, (Vertical_Scroll_Offset) ; Work out the map offset 75 | LD A, E ; Mask out lower nibble 76 | AND 0xF0 ; to give us the index into 77 | LD E,A ; the 16 byte wide map table 78 | ADD HL, DE ; Add to map address 79 | ; 80 | ; Write out the self-modding code for the scroll routine 81 | ; HL - The address of the self-modified push code 82 | ; IX - The address to store the tilest address in 83 | ; 84 | LUA ALLPASS 85 | for i = 0, 12 do 86 | _pc(string.format("LD DE, Scroll_Write_Row_%02d", i)) 87 | _pc(string.format("LD IX, Scroll_Tile_Mod_%02d + 1", i)) 88 | _pc(string.format("LD IY, Scroll_Attr_Mod_%02d + 1", i)) 89 | if i < 12 then 90 | _pc("CALL Initialise_Scroll_0") 91 | end 92 | end 93 | ENDLUA 94 | ; 95 | ; Drop straight into Initialise_Scroll_0 for the last row 96 | ; 97 | ; Read a map row in and write out the self-modding code 98 | ; Parameters: 99 | ; DE - Map 100 | ; HL - Buffer 101 | ; IX - Address to store the tileset pixel address in 102 | ; IY - Address to store the tileset attribute address in 103 | ; The self-modding code is essentially a series of PUSH instructions 104 | ; where each PUSH instruction corresponds to the tile that we want to 105 | ; display in that column. There are only 5 possible graphics, with 106 | ; HL being reserved for a blank tile. The code is terminated with a JP 107 | ; instruction back to the scroll routine 108 | ; C5 - Push BC = 00 109 | ; D5 - Push DE = 10 110 | ; E5 - Push HL = 20 111 | ; F5 - Push AF = 30 112 | ; DD E5 - Push IX = 40 (note IX and IY require two bytes written out) 113 | ; FD E5 - Push IY = 50 114 | ; C3 LL HH - JP nn - where nn is the address of Scroll_Rentry_Point 115 | ; Need a buffer size of 12 x 2 + 3 116 | ; 117 | Initialise_Scroll_0: LD C, (HL) ; Get the tileset address 118 | INC HL 119 | LD B, (HL) 120 | INC HL 121 | LD (IX + 0), C ; Store the address of the tileset pixel data 122 | LD (IX + 1), B 123 | LD A, 12 * 16 ; Get to the tileset attribute data (underneath the tileset) 124 | ADD_BC_A 125 | LD (IY + 0), C ; Store the address of the tileset attribute data 126 | LD (IY + 1), B 127 | INC HL ; Skip the spare two bytes 128 | INC HL 129 | LD B, 12 ; Numbe of tile columns to write out 130 | LD C, 0x40 ; Use this to check for IX and IY 131 | EX DE, HL 132 | Initialise_Scroll_1: LD A,(DE) ; Get first map tile 133 | AND 0xF0 ; Only interested in top nibble 134 | CP C ; Are we writing out AF, BC, DE or HL 135 | JR C, Initialise_Scroll_1B ; Yes, so jump to write out a single byte PUSH 136 | JR Z, Initialise_Scroll_IX ; If exactly 0x40 then jump to write out PUSH IX 137 | ; Write out IY (FD E5) ; So we must be writing out IY at this point 138 | LD (HL), 0xFD 139 | INC HL 140 | LD (HL), 0xE5 141 | JR Initialise_Scroll_Ret 142 | ; Write out IX (DD E5) 143 | ; 144 | Initialise_Scroll_IX: LD (HL), 0xDD 145 | INC HL 146 | LD (HL), 0xE5 147 | JR Initialise_Scroll_Ret 148 | ; 149 | ; Write out AF, BC, DE, HL (C5, D5, E5 and F5) 150 | ; 151 | Initialise_Scroll_1B: ADD A, 0xC5 ; Quickly add 0xC5 to the tile # to get the PUSH opcode 152 | LD (HL), A ; Write out the PUSH instruction here! 153 | ; 154 | ; Write out final JP instruction (C3 LL HH) 155 | ; 156 | Initialise_Scroll_Ret: INC HL ; Loop to next byte of memory to write out 157 | INC DE ; And the next tile address 158 | DJNZ Initialise_Scroll_1 ; Jump to next tile column 159 | LD (HL), 0xC3 ; Here we're writing out a JP instruction 160 | INC HL 161 | LD (HL),low Scroll_Ret_Vector ; These are self-modding with the correct value at 162 | INC HL ; top of function Initialise_Scroll with the 163 | LD (HL),high Scroll_Ret_Vector 164 | EX DE, HL 165 | RET 166 | 167 | ; Stack-based scroll routine 168 | ; Scrolls a 24x24 character block vertically - each tile is 16x16 pixels 169 | ; 170 | Scroll: LD DE, Scroll_Ret ; Set the scroll return vector up 171 | LD (Scroll_Ret_Vector+1), DE 172 | LD DE, 0x4018 ; Point to the screen 173 | Scroll_Tile_Mod_00: LD HL, 0 ; Point to tileset 174 | LD IX, Scroll_Write_Row_00 175 | LD A, (Vertical_Scroll_Offset) ; Get offset for first row into tileset 176 | AND 15 177 | LD C, A 178 | SLA A ; Multiply by 12 (6 possible tiles in the set 179 | ADD A, C 180 | SLA A ; each of them a word wide 181 | SLA A 182 | LD B, 0 ; Load into BC 183 | LD C, A 184 | ADD HL, BC ; And add to the tile offset 185 | LD A, (Vertical_Scroll_Offset) ; Get the # of lines to output for first tile 186 | AND 15 187 | LD B, A 188 | LD A, 16 189 | SUB B 190 | LD B, A ; Partial number of rows for first tile 191 | CALL Scroll_Tile_Part ; Draw the first partial row 192 | 193 | Scroll_Tile_Mod_01: SCROLL_TILE_ROW Scroll_Write_Row_01 ; Write out the 11 complete tile rows 194 | Scroll_Tile_Mod_02: SCROLL_TILE_ROW Scroll_Write_Row_02 ; Using a macro to condense the code 195 | Scroll_Tile_Mod_03: SCROLL_TILE_ROW Scroll_Write_Row_03 ; The Scroll_Mod_nn labels are used to 196 | Scroll_Tile_Mod_04: SCROLL_TILE_ROW Scroll_Write_Row_04 ; modify the first instruction of the 197 | Scroll_Tile_Mod_05: SCROLL_TILE_ROW Scroll_Write_Row_05 ; macro, LD HL, 0, with the tileset 198 | Scroll_Tile_Mod_06: SCROLL_TILE_ROW Scroll_Write_Row_06 ; address 199 | Scroll_Tile_Mod_07: SCROLL_TILE_ROW Scroll_Write_Row_07 200 | Scroll_Tile_Mod_08: SCROLL_TILE_ROW Scroll_Write_Row_08 201 | Scroll_Tile_Mod_09: SCROLL_TILE_ROW Scroll_Write_Row_09 202 | Scroll_Tile_Mod_10: SCROLL_TILE_ROW Scroll_Write_Row_10 203 | Scroll_Tile_Mod_11: SCROLL_TILE_ROW Scroll_Write_Row_11 204 | 205 | Scroll_Tile_Mod_12: LD HL, 0 ; And finally, draw the end part if required 206 | LD IX, Scroll_Write_Row_12 207 | LD A, (Vertical_Scroll_Offset) 208 | AND 15 209 | RET Z 210 | LD B, A 211 | JR Scroll_Tile_Part 212 | 213 | ; Write out a single row of tiles 214 | ; HL - Address of the tileset for this row of tiles 215 | ; DE - Screen Address 216 | ; IX - Address of the routine to push out a pixel row of the tiles 217 | ; 218 | Scroll_Tile_Full: LD B, 16 ; Set the tile height in B 219 | Scroll_Tile_Part: LD (Scroll_03 + 1), SP ; Save the stack pointer 220 | LD (Scroll_02 + 1), IX ; Save the draw line routine 221 | Scroll_01: LD SP, HL ; Point the stack at the tileset 222 | LD HL, 12 ; Go to the next line of the tileset 223 | ADD HL, SP 224 | EXX ; Switch to alternate registers 225 | POP BC,DE,HL,AF,IX,IY ; Pop the tileset into the AF, BC, DE', HL', IX and IY 226 | EXX ; Switch back to normal registers 227 | EX DE, HL ; Swap the screen address (in DE) into HL 228 | LD SP, HL ; And load into the stack pointer 229 | EXX ; Switch back to the alternate registers 230 | Scroll_02: JP 0 ; Write out a row of pixels 231 | Scroll_Ret: EXX ; Switch back to the normal registers 232 | EX DE, HL ; Swap DE and HL back again 233 | INC D ; Drop down to the next pixel line of the screen 234 | LD A, D 235 | AND 0x07 236 | JR NZ, Scroll_04 ; If we've gone over a character boundary, then 237 | LD A, E ; Drop down one character in screen memory 238 | ADD A, 32 239 | LD E, A 240 | JR C, Scroll_04 ; If we've gone over a screen third boundary 241 | LD A, D ; Drop down to the next third 242 | SUB 8 243 | LD D,A 244 | Scroll_04: DJNZ Scroll_01 ; Loop 245 | Scroll_03: LD SP, 0 ; Restore the stack pointer 246 | RET 247 | 248 | Scroll_Ret_Vector: JP Scroll_Ret ; The return address for the scroll 249 | 250 | Vertical_Scroll_Offset: DW 0 ; Vertical scroll offset 251 | 252 | ; Buffer for all the PUSH instructions for a single scroll - self modded code written 253 | ; by Initialise_Scroll is written in here 254 | ; These are called in sequence in the function Scroll 255 | ; 256 | Scroll_Write_Row_00: DEFS 27, 0 257 | Scroll_Write_Row_01: DEFS 27, 0 258 | Scroll_Write_Row_02: DEFS 27, 0 259 | Scroll_Write_Row_03: DEFS 27, 0 260 | Scroll_Write_Row_04: DEFS 27, 0 261 | Scroll_Write_Row_05: DEFS 27, 0 262 | Scroll_Write_Row_06: DEFS 27, 0 263 | Scroll_Write_Row_07: DEFS 27, 0 264 | Scroll_Write_Row_08: DEFS 27, 0 265 | Scroll_Write_Row_09: DEFS 27, 0 266 | Scroll_Write_Row_10: DEFS 27, 0 267 | Scroll_Write_Row_11: DEFS 27, 0 268 | Scroll_Write_Row_12: DEFS 27, 0 269 | 270 | -------------------------------------------------------------------------------- /lib/scroll_attr.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum Stack Based Scroller 3 | ; Author: Dean Belfield 4 | ; Created: 23/02/2020 5 | ; Last Updated: 23/02/2020 6 | ; 7 | ; Requires: scroll 8 | ; 9 | ; Modinfo: 10 | ; 11 | 12 | ; Macros 13 | ; 14 | SCROLL_ATTR_ROW: MACRO callbackAddress, mask 15 | LD HL, 0 ; This will be the address of the tileset colours 16 | LD IX, callbackAddress ; The address of the self-modified push code 17 | LD C, mask 18 | CALL Scroll_Attr_Full 19 | ENDM 20 | 21 | Scroll_Attr: LD DE, Scroll_Attr_Ret ; Set the scroll return address vector up 22 | LD (Scroll_Ret_Vector+1), DE 23 | LD DE, 0x5818 ; Point to the screen attributes 24 | 25 | Scroll_Attr_Mod_00: LD HL, 0 ; Point to tileset colours 26 | LD IX, Scroll_Write_Row_00 27 | LD A, (Vertical_Scroll_Offset) ; Get offset for first row into tileset 28 | AND 8 ; Get the colour offset (0 or 8) 29 | LD C, A 30 | SRL A ; Divide by 2 31 | ADD A,C ; Add to the original value 32 | LD B, 0 ; Load into BC 33 | LD C, A 34 | ADD HL, BC ; And add to the tile offset 35 | LD A, (Vertical_Scroll_Offset) ; Get the # of lines to output for first tile 36 | AND 8 37 | SRL A 38 | SRL A 39 | SRL A 40 | LD B, A 41 | LD A, 2 42 | SUB B 43 | LD B, A ; Partial number of rows for first tile 44 | CALL Scroll_Attr_Part ; Draw the first partial row 45 | 46 | Scroll_Attr_Mod_01: SCROLL_ATTR_ROW Scroll_Write_Row_01, 0 ; Write out the 11 complete tile rows 47 | Scroll_Attr_Mod_02: SCROLL_ATTR_ROW Scroll_Write_Row_02, 1 ; Using a macro to condense the code 48 | Scroll_Attr_Mod_03: SCROLL_ATTR_ROW Scroll_Write_Row_03, 2 ; The Scroll_Mod_nn labels are used to 49 | Scroll_Attr_Mod_04: SCROLL_ATTR_ROW Scroll_Write_Row_04, 3 ; modify the first instruction of the 50 | Scroll_Attr_Mod_05: SCROLL_ATTR_ROW Scroll_Write_Row_05, 0 ; macro, LD HL, 0, with the tileset 51 | Scroll_Attr_Mod_06: SCROLL_ATTR_ROW Scroll_Write_Row_06, 1 ; address 52 | Scroll_Attr_Mod_07: SCROLL_ATTR_ROW Scroll_Write_Row_07, 2 53 | Scroll_Attr_Mod_08: SCROLL_ATTR_ROW Scroll_Write_Row_08, 3 54 | Scroll_Attr_Mod_09: SCROLL_ATTR_ROW Scroll_Write_Row_09, 0 55 | Scroll_Attr_Mod_10: SCROLL_ATTR_ROW Scroll_Write_Row_10, 1 56 | Scroll_Attr_Mod_11: SCROLL_ATTR_ROW Scroll_Write_Row_11, 2 57 | 58 | Scroll_Attr_Mod_12: LD HL, 0 ; Point to tileset colours 59 | RET 60 | 61 | ; Write out a single colourset for a row of tiles 62 | ; HL - Address of the colourset for this row of tiles 63 | ; DE - Screen Address 64 | ; IX - Address of the routine to push out a pixel row of the tiles 65 | ; C - Mask (to only update some attribute rows at at time) 66 | ; 67 | Scroll_Attr_Full: LD B, 2 68 | LD A, (Vertical_Scroll_Offset) 69 | AND 3 70 | CP C 71 | JR Z, Scroll_Attr_Part 72 | LD A, 64 73 | ADD_DE_A 74 | RET 75 | Scroll_Attr_Part: LD (Scroll_Attr_SP + 1), SP 76 | LD (Scroll_Attr_02 + 1), IX 77 | Scroll_Attr_01: LD SP, HL ; Point HL at the colour data 78 | LD HL, 12 79 | ADD HL, SP 80 | EXX 81 | POP BC, DE, HL, AF, IX, IY 82 | EXX 83 | EX DE, HL ; Swap the screen address (in DE) into HL 84 | LD SP, HL ; And load into the SP 85 | EXX 86 | Scroll_Attr_02: JP 0 87 | Scroll_Attr_Ret: EXX 88 | EX DE, HL 89 | LD A, E 90 | ADD A, 32 91 | LD E, A 92 | JR NC, 1F 93 | INC D 94 | 1: DJNZ Scroll_Attr_01 95 | Scroll_Attr_SP: LD SP, 0 96 | RET -------------------------------------------------------------------------------- /lib/sound.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum 48K Sound Routines 3 | ; Author: Dean Belfield 4 | ; Created: 11/08/2011 5 | ; Last Updated: 11/08/2011 6 | ; 7 | ; Requires: 8 | ; 9 | ; Modinfo: 10 | ; 11 | 12 | ; Call this every time you want to initialise a sound effect 13 | ; A = Variable 1 14 | ; B = Variable 2 15 | ; C = Duration of overall sound effect 16 | ; D = Duration of each step of the sound effect 17 | ; 18 | SoundFX_A_Init: LD (SoundFX_A_V2+1),A 19 | LD A,B 20 | LD (SoundFX_A_V3+1),A 21 | LD A,C 22 | LD (SoundFX_A_Main+1),A 23 | LD A,D 24 | LD (SoundFX_A_V1+1),A 25 | XOR A 26 | LD (SoundFX_A_V4),A 27 | RET 28 | 29 | ; Call this during your main loop 30 | ; It will play one step of the sound effect each pass 31 | ; until the complete sound effect has finished 32 | ; 33 | SoundFX_A_Main: LD A,0 34 | DEC A 35 | RET Z 36 | LD (SoundFX_A_Main+1),A 37 | SoundFX_A_V1: LD B,0 38 | LD HL,SoundFX_A_V4 39 | SoundFX_A_L1: LD C,B 40 | LD A,%00001000 41 | OUT (254),A 42 | LD A,(HL) 43 | SoundFX_A_V2: XOR 0 44 | LD B,A 45 | DJNZ $ 46 | XOR A 47 | OUT (254),A 48 | LD A,(HL) 49 | SoundFX_A_V3: XOR 0 50 | LD B,A 51 | DJNZ $ 52 | DEC (HL) 53 | LD B,C 54 | DJNZ SoundFX_A_L1 55 | RET 56 | 57 | SoundFX_A_V4: DEFB 0 58 | 59 | -------------------------------------------------------------------------------- /lib/sprite.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum 48K Sprite Routines 3 | ; Author: Dean Belfield 4 | ; Created: 20/08/2011 5 | ; Last Updated: 02/07/2012 6 | ; 7 | ; Requires: output.asm 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; Subroutines Render_Sprites and Clear_Sprites now call Pixel_Address_Down for sake of readability 12 | ; 13 | 14 | ; This routine goes through the sprite logic table and runs the logic routine for each sprite 15 | ; 16 | Handle_Sprites: LD IX,Sprite_Data ; The sprite data block 17 | LD B,Sprite_Max ; The number of sprites to handle 18 | Handle_Sprites_1: ld A,(IX+Sprite_Logic+1) ; Get the high address of the handler routine 19 | AND A ; If it is zero 20 | JR Z,Handle_Sprites_3 ; Then don't process the sprite 21 | LD HL,Handle_Sprites_2 ; Set to the return address 22 | PUSH BC ; Push the loop counter 23 | PUSH IX ; Push the index register 24 | PUSH HL ; Push the return address (to simulate a call) 25 | LD H,A ; Set H to the previously fetched high address of handler routine 26 | LD L,(IX+Sprite_Logic) ; Fetch the low address of the handler routine 27 | LD A,(IX+Sprite_X) ; Store the current X and Y coordinates (for erase routine) 28 | LD (IX+Sprite_X_Old),A 29 | LD A,(IX+Sprite_Y) 30 | LD (IX+Sprite_Y_Old),A 31 | JP (HL) ; Jump to the handler. Return address is stacked, so RET from that routine 32 | Handle_Sprites_2: POP IX ; This is the return address, so pop the index register 33 | POP BC ; Pop the loop counter 34 | Handle_Sprites_3: LD DE,Sprite_Data_Block_Size ; Go to next sprite data block 35 | ADD IX,DE 36 | DJNZ Handle_Sprites_1 ; Loop until all sprites have been processed 37 | RET 38 | 39 | ; This routine renders the sprites 40 | ; It's a bit of a work-in-progress but clears each sprite in turn before drawing it in the new position 41 | ; HL = Address of sprite definition table 42 | ; 43 | Render_Sprites: LD (Render_Sprites_SM1+1),HL ; Store sprite definition table for later... 44 | LD IX,Sprite_Data 45 | LD B,Sprite_Max 46 | Render_Sprites_1: LD A,(IX+Sprite_Logic+1) 47 | AND A 48 | JR Z,Render_Sprites_2 49 | PUSH BC 50 | LD B,(IX+Sprite_Y_Old) 51 | LD C,(IX+Sprite_X_Old) 52 | CALL Clear_Sprite 53 | LD B,(IX+Sprite_Y) 54 | LD C,(IX+Sprite_X) 55 | Render_Sprites_SM1: LD DE,0 56 | CALL Render_Sprite 57 | POP BC 58 | Render_Sprites_2: LD DE,Sprite_Data_Block_Size 59 | ADD IX,DE 60 | DJNZ Render_Sprites_1 61 | RET 62 | 63 | ; This routine draws a single sprite; again, work in progress. No off-screen clipping or masking yet 64 | ; B = Y pixel position 65 | ; C = X pixel position 66 | ; DE = Address of sprite table (8 words; one word per pre-shifted sprite definition) 67 | ; 68 | Render_Sprite: CALL Get_Pixel_Address ; This routine is in output.asm 69 | PUSH HL ; Store screen address temporarily 70 | LD H,0 ; Multiply pixel shift by 2 71 | LD L,A 72 | ADD HL,HL ; I think this is quicker than shifting L and H 73 | ADD HL,DE ; Add base address of sprite table 74 | LD E,(HL) ; Get sprite definition address 75 | INC HL 76 | LD D,(HL) 77 | POP HL ; Get screen address back 78 | LD B,16 ; Height of sprite, in pixels 79 | Render_Sprite_1: LD A,(DE) ; Fetch sprite definition 80 | OR (HL) ; OR with contents of screen 81 | LD (HL),A ; Write back to screen 82 | INC DE ; Next byte of sprite definition 83 | INC L ; Next byte of screen memory 84 | LD A,(DE) ; Fetch and write again... 85 | OR (HL) 86 | LD (HL),A 87 | INC DE 88 | INC L 89 | LD A,(DE) ; And again... 90 | OR (HL) 91 | LD (HL),A 92 | INC DE 93 | DEC L ; Go back to original screen address 94 | DEC L 95 | CALL Pixel_Address_Down 96 | Render_Sprite_2: DJNZ Render_Sprite_1 97 | RET 98 | 99 | ; Clear a single sprite 100 | ; B = Y pixel position 101 | ; C = X pixel position 102 | ; 103 | Clear_Sprite: CALL Get_Pixel_Address ; This routine is in output.asm 104 | LD B,16 ; Height of sprite, in pixels 105 | Clear_Sprite_1 XOR A ; Clear A 106 | LD (HL),A ; Write 3 0's to line 107 | INC L 108 | LD (HL),A 109 | INC L 110 | LD (HL),A 111 | DEC L ; Go back to original screen address 112 | DEC L 113 | CALL Pixel_Address_Down 114 | Clear_Sprite_2: DJNZ Clear_Sprite_1 115 | RET 116 | 117 | Sprite_Image: EQU 0x00 118 | Sprite_X: EQU 0x01 119 | Sprite_Y: EQU 0x02 120 | Sprite_w: EQU 0x03 121 | Sprite_H: EQU 0x04 122 | Sprite_Logic: EQU 0x05 123 | Sprite_Flags: EQU 0x07 124 | Sprite_X_Old: EQU 0x08 125 | Sprite_Y_Old: EQU 0x09 126 | 127 | Sprite_Data_Block_Size: EQU 0x0A 128 | Sprite_Max: EQU 0x0D 129 | 130 | Sprite_Data: DEFS (Sprite_Max * Sprite_Data_Block_Size), 0 131 | -------------------------------------------------------------------------------- /lib/sprite_masked.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum 48K Sprite Routines 3 | ; Author: Dean Belfield 4 | ; Created: 29/01/2020 5 | ; Last Updated: 09/02/2020 6 | ; 7 | ; Requires: output.asm 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 03/02/2020: Sprite width and height now stored in sprite table 12 | ; 09/02/2020: Added Get_Next_Sprite 13 | 14 | INITIALISE_SPRITE: MACRO p_X, p_Y, p_IMAGE, p_LOGIC 15 | LD (IY+Sprite_X), p_X 16 | LD (IY+Sprite_Y), p_Y 17 | LD (IY+Sprite_Logic+0), low p_LOGIC 18 | LD (IY+Sprite_Logic+1), high p_LOGIC 19 | LD (IY+Sprite_Image+0), low p_IMAGE 20 | LD (IY+Sprite_Image+1), high p_IMAGE 21 | ENDM 22 | 23 | ; This routine clears the sprite logic table 24 | ; 25 | Clear_Sprite_Slots: LD IX, Sprite_Data 26 | LD DE, Sprite_Data_Block_Size 27 | LD B, Sprite_Max 28 | 1: LD (IX+Sprite_Logic+1), 0 29 | ADD IX, DE 30 | DJNZ 1B 31 | RET 32 | 33 | ; This routine goes through the sprite logic table and finds the next available 34 | ; slot 35 | ; Returns 36 | ; A: Z if slot found, NZ if no more slote available 37 | ; IY: Address of sprite slot (usually called from logic, which uses IX) 38 | ; 39 | Get_Sprite_Slot: LD IY, Sprite_Data 40 | LD DE, Sprite_Data_Block_Size 41 | LD B, Sprite_Max 42 | 1: LD A, (IY+Sprite_Logic+1) 43 | AND A 44 | RET Z 45 | ADD IY, DE 46 | DJNZ 1B 47 | RET 48 | 49 | ; Scroll adjust sprites 50 | ; Moves sprites in opposite direction 51 | ; A - Scroll offset (as passed to Scroll_Move) 52 | ; 53 | Scroll_Adjust_Sprites: LD IY, Sprite_Data 54 | LD DE, Sprite_Data_Block_Size 55 | LD B, Sprite_Max 56 | NEG 57 | LD C, A 58 | 1: LD A, (IY+Sprite_Logic+1) 59 | AND A 60 | JR Z, 2F 61 | LD A, (IY+Sprite_Y) 62 | ADD A, C 63 | LD (IY+Sprite_Y), A 64 | 2: ADD IY, DE 65 | DJNZ 1B 66 | RET 67 | 68 | ; This routine goes through the sprite logic table and runs the logic routine for each sprite 69 | ; 70 | Handle_Sprites: LD (Handle_Sprites_SP + 1), SP ; Clear th sort table 71 | LD SP,Sprite_Sort_Table_End ; Set it to the end of the self modifying code area 72 | LD B,Sprite_Sort_Table_Size ; Number of buckets to clear 73 | LD DE,0 ; We're zeroing the memory 74 | 1: PUSH DE ; Push 4 words into the area 75 | DJNZ 1B 76 | Handle_Sprites_SP: LD SP,0 ; Restore the stack pointer 77 | LD IX,Sprite_Data ; The sprite data block 78 | LD B,Sprite_Max ; The number of sprites to handle 79 | 2: LD A,(IX+Sprite_Logic+1) ; Get the high address of the handler routine 80 | AND A ; If it is zero 81 | JR Z,3F ; Then don't process the sprite 82 | PUSH BC ; Push the loop counter 83 | PUSH IX ; Push the index register 84 | LD HL,Handle_Sprites_RA ; Set to the return address 85 | PUSH HL ; Push the return address (to simulate a call) 86 | LD H,A ; Set H to the previously fetched high address of handler routine 87 | LD L,(IX+Sprite_Logic) ; Fetch the low address of the handler routine 88 | JP (HL) ; Jump to the handler. Return address is stacked, so RET from that routine 89 | Handle_Sprites_RA: POP IX ; Pop the index register 90 | LD A, (IX+Sprite_Y) ; Get the Y coordinate 91 | CP 191 ; Check for off screen (temporary value) 92 | CALL C, Sort_Sprite ; Sort the sprite 93 | POP BC ; Pop the loop counter 94 | 3: LD DE,Sprite_Data_Block_Size ; Go to next sprite data block 95 | ADD IX,DE 96 | DJNZ 2B ; Loop until all sprites have been processed 97 | RET 98 | 99 | ; Create the sorted table of sprites 100 | ; A: Y address of sprite 101 | ; 102 | Sort_Sprite: LD HL,Sprite_Sort_Table + 1 ; Address of the sort table 103 | AND 0xF0 ; Get the top 16 bits 104 | SRL A ; Divide by 8 105 | SRL A 106 | SRL A 107 | LD C,A ; Stick in BC 108 | LD B,0 109 | ADD HL,BC ; Point it to the correct bucket 110 | Sort_Sprite_1: LD A,(HL) ; See if bucket clear 111 | AND A ; Check for A=0 112 | JR Z,Sort_Sprite_2 ; If zero, then bucket empty - jump to insert code 113 | INC HL 114 | INC HL 115 | JR Sort_Sprite_1 116 | Sort_Sprite_2: LD A,IXH 117 | LD (HL),A ; Store the sprite data address in the bucket 118 | DEC HL 119 | LD A,IXL 120 | LD (HL),A 121 | RET 122 | 123 | ; Render the sprites 124 | ; 125 | Render_Sprites: LD IY,Sprite_Sort_Table 126 | LD B,Sprite_Sort_Table_Size 127 | Render_Sprites_0: PUSH BC 128 | LD A,(IY+1) 129 | AND A 130 | JR Z,Render_Sprites_1 131 | LD IXH,A 132 | LD A,(IY+0) 133 | LD IXL,A 134 | LD E,(IX+Sprite_Image) 135 | LD D,(IX+Sprite_Image+1) 136 | LD C,(IX+Sprite_X) 137 | LD B,(IX+Sprite_Y) 138 | CALL Render_Sprite 139 | Render_Sprites_1: POP BC 140 | INC IY 141 | INC IY 142 | DJNZ Render_Sprites_0 143 | RET 144 | 145 | ; This routine draws a single sprite; again, work in progress. No off-screen clipping or masking yet 146 | ; B = Y pixel postion 147 | ; C = X pixel position 148 | ; DE = Address of sprite table - 20 bytes 149 | ; - width (1 byte) : width of sprite in characters - only supports 2 at the moment 150 | ; - height (1 byte) : sprite height in pixels 151 | ; - definitions (8 words) : one word per pre-shifted sprite definition 152 | ; 153 | Render_Sprite: LD A,(DE) ; The sprite width 154 | INC DE 155 | CP 3 156 | JP Z, Render_Sprite_24px 157 | CP 2 158 | JP Z, Render_Sprite_16px 159 | CP 1 160 | RET NZ 161 | 162 | Render_Sprite_8px: LD A,(DE) ; Get the sprite height 163 | INC DE 164 | LD (Render_Sprite_8px_H + 1), A ; Store H for later 165 | ; 166 | ; Y clipping 167 | ; 168 | ADD A, B ; Add Y to it 169 | CP 192 ; Compare with screen H 170 | JR C, 0F ; If entirely on screen, then output 171 | LD A, B 172 | NEG 173 | ADD A, 192 174 | LD (Render_Sprite_8px_H + 1), A ; Store the clipped height 175 | 176 | 0: CALL Get_Pixel_Address ; HL = Screen Address, A = Pixel in Row 177 | EX DE,HL ; HL = Sprite, DE = Screen 178 | SLA A ; Multiply pixel shift by 2 179 | LD B,0 180 | LD C,A 181 | ADD HL,BC ; Add base address of sprite table 182 | LD A,(HL) ; Get sprite definition address 183 | INC HL 184 | LD H,(HL) 185 | LD L,A ; HL = Sprite, DE = Screen 186 | LD (Render_Sprite_8px_SP+ 1), SP ; Preserve the stack pointer 187 | LD SP, HL ; Store in SP 188 | EX DE,HL ; HL = Screen, SP = Sprite 189 | Render_Sprite_8px_H: LD B, 16 ; Height of sprite, in pixels 190 | 1: POP DE ; Fetch first word of sprite (E = mask, D = sprite) 191 | LD A,(HL) ; Fetch screen data 192 | AND E ; AND with mask 193 | OR D ; OR with data 194 | LD (HL),A ; Store back in screen 195 | INC L ; To next screen location 196 | POP DE ; And a third time... 197 | LD A,(HL) 198 | AND E 199 | OR D 200 | LD (HL), A 201 | DEC L 202 | INC H ; Drop down to the next pixel line of the screen 203 | LD A, H 204 | AND 0x07 205 | JR NZ, 2F 206 | LD A, L 207 | ADD A, 32 208 | LD L, A 209 | JR C, 2F 210 | LD A, H 211 | SUB 8 212 | LD H,A 213 | 2: DJNZ 1B 214 | Render_Sprite_8px_SP: LD SP,0 ; Restore the stack pointer 215 | RET 216 | 217 | Render_Sprite_16px: LD A,(DE) ; Get the sprite height 218 | INC DE 219 | LD (Render_Sprite_16px_H + 1), A ; Store H for later 220 | ; 221 | ; Y clipping 222 | ; 223 | ADD A, B ; Add Y to it 224 | CP 192 ; Compare with screen H 225 | JR C, 0F ; If entirely on screen, then output 226 | LD A, B 227 | NEG 228 | ADD A, 192 229 | LD (Render_Sprite_16px_H + 1), A ; Store the clipped height 230 | 231 | 0: CALL Get_Pixel_Address ; HL = Screen Address, A = Pixel in Row 232 | EX DE,HL ; HL = Sprite, DE = Screen 233 | SLA A ; Multiply pixel shift by 2 234 | LD B,0 235 | LD C,A 236 | ADD HL,BC ; Add base address of sprite table 237 | LD A,(HL) ; Get sprite definition address 238 | INC HL 239 | LD H,(HL) 240 | LD L,A ; HL = Sprite, DE = Screen 241 | LD (Render_Sprite_16px_SP+ 1), SP ; Preserve the stack pointer 242 | LD SP, HL ; Store in SP 243 | EX DE,HL ; HL = Screen, SP = Sprite 244 | Render_Sprite_16px_H: LD B, 16 ; Height of sprite, in pixels 245 | 1: DUP 2 246 | POP DE ; Fetch first word of sprite (E = mask, D = sprite) 247 | LD A,(HL) ; Fetch screen data 248 | AND E ; AND with mask 249 | OR D ; OR with data 250 | LD (HL),A ; Store back in screen 251 | INC L ; To next screen location 252 | EDUP 253 | POP DE ; And a third time... 254 | LD A,(HL) 255 | AND E 256 | OR D 257 | LD (HL), A 258 | DEC L ; Go back to original screen address 259 | DEC L 260 | INC H ; Drop down to the next pixel line of the screen 261 | LD A, H 262 | AND 0x07 263 | JR NZ, 2F 264 | LD A, L 265 | ADD A, 32 266 | LD L, A 267 | JR C, 2F 268 | LD A, H 269 | SUB 8 270 | LD H,A 271 | 2: DJNZ 1B 272 | Render_Sprite_16px_SP: LD SP,0 ; Restore the stack pointer 273 | RET 274 | 275 | Render_Sprite_24px: LD A,(DE) ; Get the sprite height 276 | INC DE 277 | LD (Render_Sprite_24px_H + 1), A ; Store H for later 278 | ; 279 | ; Y clipping 280 | ; 281 | ADD A, B ; Add Y to it 282 | CP 192 ; Compare with screen H 283 | JR C, 0F ; If entirely on screen, then output 284 | LD A, B 285 | NEG 286 | ADD A, 192 287 | LD (Render_Sprite_24px_H + 1), A ; Store the clipped height 288 | 289 | 0: CALL Get_Pixel_Address ; HL = Screen Address, A = Pixel in Row 290 | EX DE,HL ; HL = Sprite, DE = Screen 291 | SLA A ; Multiply pixel shift by 2 292 | LD B,0 293 | LD C,A 294 | ADD HL,BC ; Add base address of sprite table 295 | LD A,(HL) ; Get sprite definition address 296 | INC HL 297 | LD H,(HL) 298 | LD L,A ; HL = Sprite, DE = Screen 299 | LD (Render_Sprite_24px_SP+ 1), SP ; Preserve the stack pointer 300 | LD SP, HL ; Store in SP 301 | EX DE,HL ; HL = Screen, SP = Sprite 302 | Render_Sprite_24px_H: LD B, 16 ; Height of sprite, in pixels 303 | 1: DUP 3 304 | POP DE ; Fetch first word of sprite (E = mask, D = sprite) 305 | LD A,(HL) ; Fetch screen data 306 | AND E ; AND with mask 307 | OR D ; OR with data 308 | LD (HL),A ; Store back in screen 309 | INC L ; To next screen location 310 | EDUP 311 | POP DE ; And a third time... 312 | LD A,(HL) 313 | AND E 314 | OR D 315 | LD (HL), A 316 | DEC L ; Go back to original screen address 317 | DEC L 318 | DEC L 319 | INC H ; Drop down to the next pixel line of the screen 320 | LD A, H 321 | AND 0x07 322 | JR NZ, 2F 323 | LD A, L 324 | ADD A, 32 325 | LD L, A 326 | JR C, 2F 327 | LD A, H 328 | SUB 8 329 | LD H,A 330 | 2: DJNZ 1B 331 | Render_Sprite_24px_SP: LD SP,0 ; Restore the stack pointer 332 | RET 333 | 334 | Sprite_Image: EQU 0x00 335 | Sprite_X: EQU 0x02 336 | Sprite_Y: EQU 0x03 337 | Sprite_Logic: EQU 0x04 338 | Sprite_Flags: EQU 0x06 339 | Sprite_Data_1: EQU 0x07 340 | Sprite_Data_2: EQU 0x08 341 | Sprite_Data_3: EQU 0x09 342 | Sprite_Data_4: EQU 0x0A 343 | Sprite_Data_5: EQU 0x0B 344 | 345 | Sprite_Data_Block_Size: EQU 0x0C 346 | Sprite_Max: EQU 0x10 347 | Sprite_Data_Len: EQU Sprite_Max * Sprite_Data_Block_Size 348 | Sprite_Sort_Table_Size: EQU Sprite_Max * 2 349 | Sprite_Sort_Table_Len: EQU Sprite_Sort_Table_Size * 2 350 | 351 | Sprite_Data: DEFS Sprite_Data_Len, 0 352 | Sprite_Sort_Table: DEFS Sprite_Sort_Table_Len, 0 353 | Sprite_Sort_Table_End: EQU $ 354 | -------------------------------------------------------------------------------- /lib/vector.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum Vector Output Routines 3 | ; Author: Dean Belfield 4 | ; Created: 30/06/2012 5 | ; Last Updated: 30/05/2020 6 | ; 7 | ; Requires: output 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 03/07/2012: Simplified line draw; now always draws the line down. Relabled to improve clarity 12 | ; Fixed bug in point tables; one entry had 7 bits 13 | ; 04/07/2012: Added Draw_Horz_Line_Solid & Draw_Horz_Line_Texture; special fast case used for drawing polygons 14 | ; 05/07/2012: Added Draw_Circle routine; needs some optimisation, more a proof of concept at the moment 15 | ; 01/04/2020: Moved Draw_Horz_Line_Solid & Draw_Horz_Line_Texture to vector_filled.z80 16 | ; 21/05/2020: Fixed bug in Draw_Line where final pixel not plotted 17 | ; 30/05/2020: Fixed bug in implementation of Bresenham for Draw_Line and Erase_Line 18 | ; 19 | 20 | ; 21 | ; Most of these routines will precalculate the screen address in HL and return a bit position in A (0-7). 22 | ; The bit position is used as an index into the table Plot_Point; this contains a pixel set in the correct 23 | ; position for each bit, so 0=>%10000000, 1=>%01000000 and so on. 24 | ; 25 | ; The line routine, for example, will only do this slow calculation once and will work relative to that position 26 | ; for the rest of the draw. So, to move the pixel right if the data is stored in register D you would RRC D. 27 | ; At this point, if the pixel is %00000010 it would then be %00000001. If the pixel is then rotated again, it would 28 | ; be %10000000 and the carry bit would be set. This is picked up in the code and an INC or DEC L is used to move 29 | ; HL to the next adjacent character position. 30 | ; 31 | ; The function Pixel_Address_Down takes HL and move down or up one pixel line, taking into account the 32 | ; Spectrum's strange screen layout. Again, this is quicker than calculating the address from scratch each 33 | ; time as most of the time it's just doing an INC H (or DEC H). 34 | ; 35 | ; For the sake of clarity I've used CALLs within loops; I wouldn't normally do this in speed critical code but felt 36 | ; that I'd lose clarity if I didn't. Feel free to inline any code that is called. 37 | ; 38 | ; Finally, this code uses a lot of self-modifying code; the code is in RAM so it is possible to use the code to 39 | ; modify itself. This is used in the line routine to adjust the line drawing loop to draw in all four quadrants. 40 | ; 41 | 42 | ; Plot routine 43 | ; B = Y pixel position 44 | ; C = X pixel position 45 | ; 46 | Plot: CALL Get_Pixel_Address ; Get screen address in HL, pixel position (0 to 7) in A 47 | LD BC,Plot_Point ; Address of point lookup table 48 | ADD A,C ; Add pixel position to get entry in table 49 | LD C,A 50 | LD A,(BC) ; Get pixel data from table 51 | OR (HL) ; OR with screen data 52 | LD (HL),A ; Write back to screen 53 | RET 54 | 55 | 56 | ; Unplot routine 57 | ; B = Y pixel position 58 | ; C = X pixel position 59 | ; 60 | Unplot: CALL Get_Pixel_Address ; Same as Plot... 61 | LD BC,Unplot_Point 62 | ADD A,C 63 | LD C,A 64 | LD A,(BC) 65 | AND (HL) ; AND with screen data 66 | LD (HL),A 67 | RET 68 | 69 | ; Draw Circle (Beta - uses Plot to draw the circle outline as a proof of concept) 70 | ; B = Y pixel position of circle centre 71 | ; C = X pixel position of circle centre 72 | ; A = Radius of circle 73 | ; 74 | Draw_Circle: AND A ; Zero radius 75 | JR Z,Plot ; Just plot the point 76 | LD (Draw_Circle_M1 + 1),BC ; Store circle origin 77 | 78 | LD IXH,A ; IXH = Y 79 | LD IXL,0 ; IXL = X 80 | ; 81 | ; Calculate BC (D2) = 3-(R*2) 82 | ; 83 | LD H,0 ; HL = R 84 | LD L,A 85 | ADD HL,HL ; HL = R*2 86 | EX DE,HL ; DE = R*2 87 | LD HL,3 88 | AND A 89 | SBC HL,DE ; HL = 3-(R*2) 90 | LD B,H 91 | LD C,L 92 | ; 93 | ; Calculate HL (Delta) = 1-R 94 | ; 95 | LD HL,1 96 | LD D,0 97 | LD E,IXL 98 | AND A 99 | SBC HL,DE ; HL = 1 - CR 100 | ; 101 | ; SET DE (D1) = 1 102 | ; 103 | LD DE,1 104 | 105 | Draw_Circle_Loop: LD A,IXH ; Get Y in A 106 | CP IXL ; Compare with X 107 | RET C ; Return if X>Y 108 | ; 109 | ; The routine only calculates an eighth of the circle, so use symnmetry to draw 110 | ; 111 | EXX 112 | Draw_Circle_M1: LD DE,0 ; Get the circle origin 113 | 114 | LD A,E 115 | ADD A,IXL 116 | LD C,A 117 | LD A,D 118 | ADD A,IXH 119 | LD B,A 120 | CALL Plot ; Plot CX+X,CY+Y 121 | LD A,E 122 | SUB IXL 123 | LD C,A 124 | LD A,D 125 | ADD A,IXH 126 | LD B,A 127 | CALL Plot ; Plot CX-X,CY+Y 128 | LD A,E 129 | ADD A,IXL 130 | LD C,A 131 | LD A,D 132 | SUB IXH 133 | LD B,A 134 | CALL Plot ; Plot CX+X,CY-Y 135 | LD A,E 136 | SUB IXL 137 | LD C,A 138 | LD A,D 139 | SUB IXH 140 | LD B,A 141 | CALL Plot ; Plot CY+X,CX-Y 142 | LD A,D 143 | ADD A,IXL 144 | LD B,A 145 | LD A,E 146 | ADD A,IXH 147 | LD C,A 148 | CALL Plot ; Plot CY+X,CX+Y 149 | LD A,D 150 | SUB IXL 151 | LD B,A 152 | LD A,E 153 | ADD A,IXH 154 | LD C,A 155 | CALL Plot ; Plot CY-X,CX+Y 156 | LD A,D 157 | ADD A,IXL 158 | LD B,A 159 | LD A,E 160 | SUB IXH 161 | LD C,A 162 | CALL Plot ; Plot CY+X,CX-Y 163 | LD A,D 164 | SUB IXL 165 | LD B,A 166 | LD A,E 167 | SUB IXH 168 | LD C,A 169 | CALL Plot ; Plot CX+X,CY-Y 170 | EXX 171 | ; 172 | ; Do the incremental circle thing here 173 | ; 174 | BIT 7,H ; Check for Hl<=0 175 | JR Z,Draw_Circle_1 176 | ADD HL,DE ; Delta=Delta+D1 177 | JR Draw_Circle_2 ; 178 | Draw_Circle_1: ADD HL,BC ; Delta=Delta+D2 179 | INC BC 180 | INC BC ; D2=D2+2 181 | DEC IXH ; Y=Y-1 182 | Draw_Circle_2: INC BC ; D2=D2+2 183 | INC BC 184 | INC DE ; D1=D1+2 185 | INC DE 186 | INC IXL ; X=X+1 187 | JR Draw_Circle_Loop 188 | 189 | 190 | ; Draw Triangle 191 | ; IY = Pointer to 3 bytes worth of coordinate data 192 | ; 193 | Draw_Triangle: LD C,(IY+0) 194 | LD B,(IY+1) 195 | LD E,(IY+2) 196 | LD D,(IY+3) 197 | CALL Draw_Line 198 | LD C,(IY+2) 199 | LD B,(IY+3) 200 | LD E,(IY+4) 201 | LD D,(IY+5) 202 | CALL Draw_Line 203 | LD C,(IY+4) 204 | LD B,(IY+5) 205 | LD E,(IY+0) 206 | LD D,(IY+1) 207 | JP Draw_Line 208 | 209 | ; Erase Triangle 210 | ; IY = Pointer to 3 bytes worth of coordinate data 211 | ; 212 | Erase_Triangle: LD C,(IY+0) 213 | LD B,(IY+1) 214 | LD E,(IY+2) 215 | LD D,(IY+3) 216 | CALL Erase_Line 217 | LD C,(IY+2) 218 | LD B,(IY+3) 219 | LD E,(IY+4) 220 | LD D,(IY+5) 221 | CALL Erase_Line 222 | LD C,(IY+4) 223 | LD B,(IY+5) 224 | LD E,(IY+0) 225 | LD D,(IY+1) 226 | JP Erase_Line 227 | 228 | ; Draw Line routine 229 | ; B = Y pixel position 1 230 | ; C = X pixel position 1 231 | ; D = Y pixel position 2 232 | ; E = X pixel position 2 233 | ; 234 | Draw_Line: LD A,D ; Check whether we are going to be drawing up 235 | CP B 236 | JR NC,Draw_Line_1 237 | 238 | PUSH BC ; If we are, then this neat trick swaps BC and DE 239 | PUSH DE ; using the stack, forcing the line to be always 240 | POP BC ; drawn downwards 241 | POP DE 242 | 243 | Draw_Line_1: CALL Get_Pixel_Address ; Get screen address in HL, pixel position (0-7) in A 244 | ; 245 | ; At this point we have 246 | ; A = Pixel position (0-7) 247 | ; HL = Screen address of the start point 248 | ; BC = Start coordinate (B=Y1, C=X1) 249 | ; DE = End coordinates (D=Y2, E=X2) 250 | ; 251 | LD IX,Plot_Point ; Point to the Plot_Point table 252 | ADD A,IXL ; Add the pixel position to get entry in table 253 | LD IXL,A 254 | 255 | LD A,D ; Calculate the line height in B (Y2-Y1) 256 | SUB B 257 | LD B,A 258 | 259 | LD A,E ; Calculate the line width in C (X2-X1) 260 | SUB C 261 | JR C,Draw_Line_X1 ; If carry set (negative result) then we are drawing from right to left 262 | ; 263 | ; This bit of code mods the main loop for drawing left to right 264 | ; 265 | LD C,A ; Store the line width 266 | LD A,0x2C ; Code for INC L 267 | LD (Draw_Line_Q1_M3),A ; Mod the code 268 | LD (Draw_Line_Q2_M3),A 269 | LD A,0x0A ; Code for RRC D (CB 0A) 270 | JR Draw_Line_X2 ; Skip the next bit 271 | ; 272 | ; This bit of code mods the main loop for drawing right to left 273 | ; 274 | Draw_Line_X1: NEG ; The width of line is negative, so make it positive again 275 | LD C,A ; Store the line width 276 | LD A,0x2D ; Code for DEC L 277 | LD (Draw_Line_Q1_M3),A 278 | LD (Draw_Line_Q2_M3),A 279 | LD A,0x02 ; Code for RLC D (CB 02) 280 | ; 281 | ; We've got the basic information at this point 282 | ; 283 | Draw_Line_X2: LD (Draw_Line_Q1_M2 + 1),A ; A contains the code for RLC D or RRC D, so make the mods 284 | LD (Draw_Line_Q2_M2 + 1),A 285 | LD D,(IX+0) ; Get the pixel data from the Point_Plot table 286 | LD A,B ; Check if B and C are 0 287 | OR C 288 | JR Z,Draw_Line_P ; There is no line, so just plot a single point 289 | ; 290 | ; At this point 291 | ; HL = Screen address of the start point 292 | ; B = Line height (YL) 293 | ; C = Line width (XL) 294 | ; D = Pixel data 295 | ; 296 | Draw_Line_Q: LD A,B ; Work out which diagonal we are on 297 | CP C 298 | JR NC,Draw_Line_Q2 299 | ; 300 | ; This bit of code draws the line where B=C (more vertical than horizontal, or diagonal) 328 | ; 329 | Draw_Line_Q2: LD (Draw_Line_Q2_M1 + 1),A ; Self-mod the code to store YL in loop 330 | LD E,B ; E = YL 331 | SRL E ; E = YL / 2 (error) 332 | Draw_Line_Q2_L: LD A,(HL) ; Plot the pixel 333 | OR D 334 | LD (HL),A 335 | LD A,E ; Add the line width to the error 336 | SUB C ; 337 | JR NC,Draw_Line_Q2_S ; Skip the next bit if we don't get a carry 338 | Draw_Line_Q2_M1: ADD A,0 ; Add the line height to the error (E = E + XL) - previously self-modded 339 | Draw_Line_Q2_M2: RRC D ; Rotates the pixel right with carry 340 | JR NC,Draw_Line_Q2_S 341 | Draw_Line_Q2_M3: INC L ; If we get a carry then move to adjacent screen address; more self modifying code 342 | Draw_Line_Q2_S: LD E,A ; Store the error value back in 343 | CALL Pixel_Address_Down ; And also move down 344 | DJNZ Draw_Line_Q2_L 345 | JR Draw_Line_P ; Plot the final point 346 | 347 | ; Erase Line routine 348 | ; B = Y pixel position 1 349 | ; C = X pixel position 1 350 | ; D = Y pixel position 2 351 | ; E = X pixel position 2 352 | ; 353 | Erase_Line: LD A,D ; Check whether we are going to be drawing up 354 | CP B 355 | JR NC,Erase_Line_1 356 | 357 | PUSH BC ; If we are, then this neat trick swaps BC and DE 358 | PUSH DE ; using the stack, forcing the line to be always 359 | POP BC ; drawn downwards 360 | POP DE 361 | 362 | Erase_Line_1: CALL Get_Pixel_Address ; Get screen address in HL, pixel position (0-7) in A 363 | ; 364 | ; At this point we have 365 | ; A = Pixel position (0-7) 366 | ; HL = Screen address of the start point 367 | ; BC = Start coordinate (B=Y1, C=X1) 368 | ; DE = End coordinates (D=Y2, E=X2) 369 | ; 370 | LD IX,Unplot_Point ; Point to the Unplot_Point table 371 | ADD A,IXL ; Add the pixel position to get entry in table 372 | LD IXL,A 373 | 374 | LD A,D ; Calculate the line height in B (Y2-Y1) 375 | SUB B 376 | LD B,A 377 | 378 | LD A,E ; Calculate the line width in C (X2-X1) 379 | SUB C 380 | JR C,Erase_Line_X1 ; If carry set (negative result) then we are drawing from right to left 381 | ; 382 | ; This bit of code mods the main loop for drawing left to right 383 | ; 384 | LD C,A ; Store the line width 385 | LD A,0x2C ; Code for INC L 386 | LD (Erase_Line_Q1_M3),A ; Mod the code 387 | LD (Erase_Line_Q2_M3),A 388 | LD A,0x0A ; Code for RRC D (CB 0A) 389 | JR Erase_Line_X2 ; Skip the next bit 390 | ; 391 | ; This bit of code mods the main loop for drawing right to left 392 | ; 393 | Erase_Line_X1: NEG ; The width of line is negative, so make it positive again 394 | LD C,A ; Store the line width 395 | LD A,0x2D ; Code for DEC L 396 | LD (Erase_Line_Q1_M3),A 397 | LD (Erase_Line_Q2_M3),A 398 | LD A,0x02 ; Code for RLC D (CB 02) 399 | ; 400 | ; We've got the basic information at this point 401 | ; 402 | Erase_Line_X2: LD (Erase_Line_Q1_M2 + 1),A ; A contains the code for RLC D or RRC D, so make the mods 403 | LD (Erase_Line_Q2_M2 + 1),A 404 | LD D,(IX+0) ; Get the pixel data from the Unplot_Point table 405 | LD A,B ; Check if B and C are 0 406 | OR C 407 | JR NZ,Erase_Line_Q ; There is a line to draw, so skip to the next bit 408 | LD A,(HL) ; Here we've got a single point line, so plot and return 409 | AND D 410 | LD (HL),A 411 | RET 412 | ; 413 | ; At this point 414 | ; HL = Screen address of the start point 415 | ; B = Line height 416 | ; C = Line width 417 | ; D = Pixel data 418 | ; 419 | Erase_Line_Q: LD A,B ; Work out which diagonal we are on 420 | CP C 421 | JR NC,Erase_Line_Q2 422 | ; 423 | ; This bit of code draws the line where B=C (more vertical than horizontal, or diagonal) 451 | ; 452 | Erase_Line_Q2: LD (Erase_Line_Q2_M1 + 1),A 453 | LD E,B ; Calculate the error value 454 | SRL E 455 | Erase_Line_Q2_L: LD A,(HL) ; Unplot the pixel 456 | AND D 457 | LD (HL),A 458 | LD A,E ; Get the error value 459 | SUB C ; Add the line length to it (X2-X1) 460 | JR NC,Erase_Line_Q2_S ; Skip the next bit if we don't get a carry 461 | Erase_Line_Q2_M1: ADD A,0 ; Add the line height (previously stored; self modifying code) 462 | Erase_Line_Q2_M2: RRC D ; Rotates the pixel right with carry 463 | JR C,Erase_Line_Q2_S ; Note the change here from the Draw_Line routine 464 | Erase_Line_Q2_M3: INC L ; If we get no carry then move to adjacent screen address; more self modifying code 465 | Erase_Line_Q2_S: LD E,A ; Store the error value back in 466 | CALL Pixel_Address_Down ; And also move down 467 | DJNZ Erase_Line_Q2_L 468 | JR Erase_Line_P ; Plot the final pixel 469 | 470 | ; Note that the functions above only work if each of these tables are in a byte boundary 471 | ; 472 | Plot_Point: DB %10000000,%01000000,%00100000,%00010000,%00001000,%00000100,%00000010,%00000001 473 | Unplot_Point: DB %01111111,%10111111,%11011111,%11101111,%11110111,%11111011,%11111101,%11111110 474 | -------------------------------------------------------------------------------- /lib/vector_filled.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: ZX Spectrum Vector Output Routines 3 | ; Author: Dean Belfield 4 | ; Created: 01/04/2020 5 | ; Last Updated: 20/05/2020 6 | ; 7 | ; Requires: output, macro 8 | ; 9 | ; Modinfo: 10 | ; 11 | ; 05/04/2020: Improved code to determine which table to draw into 12 | ; Added Draw_Quad_Filled 13 | ; Added Draw_Circle_Filled 14 | ; 08/04/2020: Fixed bug in Draw_Circle_Filled to draw the bottom-most scan line 15 | ; 10/04/2020: Checks for SCREEN_BUFFER definition and writes to that if defined 16 | ; 11/04/2020: Optimised Draw_Horz_Line_Texture and Draw_Vector_Table 17 | ; 13/04/2020: Fixed bug in Draw_Horz_Line_Texture; now masks pixel data on LHS and RHS 18 | ; Fixed bug in Draw_Horz_Line_Texture; Plot_Line_LHS and Plot_Line_RHS can now fall over a page boundary 19 | ; 20/05/2020: ixed bug in implementation of Bresenham for Draw_Line_Table 20 | 21 | ; EQUS 22 | ; 23 | Vector_Table_X1 EQU 0xFD00 ; These tables needs to be on a page boundary and 24 | Vector_Table_X2 EQU Vector_Table_X1+0x100 ; next to each other 25 | 26 | ; Macros 27 | ; 28 | 29 | ; Shortcut to draw a line between two points 30 | ; 31 | DRAW_LINE_TABLE: MACRO PX1,PY1,PX2,PY2 32 | LD C,(IY+PX1) 33 | LD B,(IY+PY1) 34 | LD E,(IY+PX2) 35 | LD D,(IY+PY2) 36 | CALL Draw_Line_Table 37 | ENDM 38 | 39 | ; Shortcut to plot a circle quadrant 40 | ; 41 | PLOT_CIRCLE_TABLE: MACRO TABLE, OPX, OPY 42 | LD H,high TABLE 43 | LD A,B ; Get the Y origin 44 | OPY IXH ; Add the Y coordinate 45 | LD L,A ; Store in L 46 | LD A,C ; Get the X coordinate 47 | OPX IXL ; Add the X origin 48 | LD (HL), A ; Store in the table 49 | LD A,B ; Repeat the second quadrant 50 | OPY IXL 51 | LD L,A 52 | LD A,C 53 | OPX IXH 54 | LD (HL),A 55 | ENDM 56 | 57 | ; Get the absolute distance between two points 58 | ; 59 | ABS_DELTA: MACRO P1,P2,REG 60 | LD A,P2 61 | SUB P1 62 | JR NC, .S1 63 | NEG 64 | .S1 LD REG, A 65 | ENDM 66 | 67 | ; Draw a filled circle 68 | ; IY: Pointer to 2 coordinates and 1 radius 69 | ; HL: 8-Byte texture address 70 | ; 71 | 72 | ; Draw a filled circle 73 | ; HL: 8-Byte texture address 74 | ; B = Y pixel position of circle centre 75 | ; C = X pixel position of circle centre 76 | ; A = Radius of circle 77 | ; 78 | Draw_Circle_Filled: LD (Draw_Vector_Table_1+1), HL 79 | PUSH AF 80 | CALL Draw_Circle_Table 81 | POP AF ; A = radius 82 | EXX ; BC' = YX origin 83 | LD C,A ; C = radius 84 | LD A, B ; Get the origin 85 | SUB C ; Subtract the radius 86 | JR NC, 1F ; Skip next bit if OK 87 | XOR A ; Set to zero if off top of screen 88 | 1: LD L,A ; Store in L 89 | LD A,B ; Get the origin 90 | ADD C ; Add the radius 91 | CP 192 ; Check bottom screen boundary 92 | JR C,2F ; If off bottom then 93 | LD A,191 ; Crop to 191 94 | 2 SUB L ; Subtract the top 95 | INC A ; Because height = bottom - top + 1 96 | LD B,A ; Store in B 97 | JP Draw_Vector_Table ; Draw the table 98 | 99 | ; Draw a filled polygon 100 | ; IY: Pointer to 8 bytes worth of coordinate data 101 | ; HL: 8-Byte texture address 102 | ; 103 | Draw_Quad_Filled: LD (Draw_Vector_Table_1+1), HL ; Store the texture address (8 bytes) 104 | DRAW_LINE_TABLE 0,1,2,3 105 | DRAW_LINE_TABLE 2,3,4,5 106 | DRAW_LINE_TABLE 4,5,6,7 107 | DRAW_LINE_TABLE 6,7,0,1 108 | LD A,(IY+1) ; Get the min Y 109 | MIN (IY+3) 110 | MIN (IY+5) 111 | MIN (IY+7) 112 | LD L,A ; Store in L 113 | LD A,(IY+1) ; Get the max Y 114 | MAX (IY+3) 115 | MAX (IY+5) 116 | MAX (IY+7) 117 | SUB L ; Subtract from L (the min) 118 | LD B,A ; Get the height 119 | JP NZ, Draw_Vector_Table ; Only draw if not zero 120 | RET 121 | 122 | ; Draw a filled triangle 123 | ; IY: Pointer to 6 bytes worth of coordinate data 124 | ; HL: 8-Byte texture address 125 | ; 126 | Draw_Triangle_Filled: LD (Draw_Vector_Table_1+1),HL ; Store the texture address (8 bytes) 127 | DRAW_LINE_TABLE 0,1,2,3 ; Side 1 128 | DRAW_LINE_TABLE 2,3,4,5 ; Side 2 129 | DRAW_LINE_TABLE 4,5,0,1 ; Side 3 130 | LD A,(IY+1) ; Get the min Y 131 | MIN (IY+3) 132 | MIN (IY+5) 133 | LD L,A ; Store in L 134 | LD A,(IY+1) ; Get the max Y 135 | MAX (IY+3) 136 | MAX (IY+5) 137 | SUB L ; Subtract from L (the min) 138 | LD B,A ; Get the height 139 | RET Z ; Don't draw if zero 140 | 141 | ; Draw a line into the vector table 142 | ; B = Y pixel position 1 143 | ; C = X pixel position 1 144 | ; D = Y pixel position 2 145 | ; E = X pixel position 2 146 | ; 147 | Draw_Line_Table: LD H, high Vector_Table_X1 ; Default to drawing in this table 148 | LD A,D ; Check whether we are going to be drawing up 149 | CP B 150 | JR NC, 3F 151 | INC H ; If we're drawing up, then draw in second table 152 | PUSH BC ; And use this neat trick to swaps BC and DE 153 | PUSH DE ; using the stack, forcing the line to be always 154 | POP BC ; drawn downwards 155 | POP DE 156 | 157 | 3: LD L, B ; Y address -> index of table 158 | LD A, C ; X address 159 | PUSH AF ; Stack the X address 160 | LD A, D ; Calculate the line height in B 161 | SUB B 162 | LD B, A 163 | LD A, E ; Calculate the line width 164 | SUB C 165 | JR C, 4F 166 | ; 167 | ; This bit of code mods the main loop for drawing left to right 168 | ; 169 | LD C, A ; Store the line width 170 | LD A,0x14 ; Opcode for INC D 171 | JR 5F 172 | ; 173 | ; This bit of code mods the main loop for drawing right to left 174 | ; 175 | 4: NEG 176 | LD C,A 177 | LD A,0x15 ; Opcode for DEC D 178 | ; 179 | ; We've got the basic information at this point 180 | ; 181 | 5: LD (Draw_Line_Table_Q1_M2), A ; Code for INC D or DEC D 182 | LD (Draw_Line_Table_Q2_M2), A 183 | POP AF ; Pop the X address 184 | LD D, A ; And store in the D register 185 | LD A, B ; Check if B and C are 0 186 | OR C 187 | JR NZ, Draw_Line_Table_Q ; There is a line to draw, so skip to the next bit 188 | LD (HL), D ; Otherwise just plot the point into the table 189 | RET 190 | ; 191 | ; At this point 192 | ; HL = Table address 193 | ; B = Line height 194 | ; C = Line width 195 | ; D = X Position 196 | ; 197 | Draw_Line_Table_Q: LD A,B ; Work out which diagonal we are on 198 | CP C 199 | JR NC,Draw_Line_Table_Q2 200 | ; 201 | ; This bit of code draws the line where B=C (more vertical than horizontal, or diagonal) 223 | ; 224 | Draw_Line_Table_Q2: LD (Draw_Line_Table_Q2_M1+1), A ; Self-mod the code to store the line width 225 | LD E,B ; Calculate the error value 226 | SRL E 227 | 1: LD (HL),D ; Store the X position 228 | LD A,E ; Get the error value 229 | SUB C ; Add the line length to it (X2-X1) 230 | JR NC,2F ; Skip the next bit if we don't get a carry 231 | Draw_Line_Table_Q2_M1: ADD A,0 ; Add the line height (self modifying code) 232 | Draw_Line_Table_Q2_M2: INC D ; Increment or decrement the X coordinate (self-modding code) 233 | 2: LD E,A ; Store the error value back in 234 | INC L ; And also move down 235 | DJNZ 1B 236 | LD (HL),D 237 | RET 238 | 239 | ; Draw a circle in the table 240 | ; B = Y pixel position of circle centre 241 | ; C = X pixel position of circle centre 242 | ; A = Radius of circle 243 | ; 244 | Draw_Circle_Table: AND A 245 | RET Z 246 | 247 | PUSH BC ; Get BC in BC' 248 | EXX 249 | POP BC 250 | 251 | LD IXH,A ; IXH = Y 252 | LD IXL,0 ; IXL = X 253 | ; 254 | ; Calculate BC (D2) = 3-(R*2) 255 | ; 256 | LD H,0 ; HL = R 257 | LD L,A 258 | ADD HL,HL ; HL = R*2 259 | EX DE,HL ; DE = R*2 260 | LD HL,3 261 | AND A 262 | SBC HL,DE ; HL = 3-(R*2) 263 | LD B,H 264 | LD C,L 265 | ; 266 | ; Calculate HL (Delta) = 1-R 267 | ; 268 | LD HL,1 269 | LD D,0 270 | LD E,IXL 271 | AND A 272 | SBC HL,DE ; HL = 1 - CR 273 | ; 274 | ; SET DE (D1) = 1 275 | ; 276 | LD DE,1 277 | ; 278 | ; The circle loop 279 | ; First plot all the octants 280 | ; B' = Y origin 281 | ; C' = X origin 282 | ; 283 | 0: EXX ; Plot the circle quadrants 284 | PLOT_CIRCLE_TABLE Vector_Table_X1, ADD, ADD 285 | PLOT_CIRCLE_TABLE Vector_Table_X2, SUB, ADD 286 | PLOT_CIRCLE_TABLE Vector_Table_X1, ADD, SUB 287 | PLOT_CIRCLE_TABLE Vector_Table_X2, SUB, SUB 288 | EXX 289 | ; 290 | ; Now calculate the next point 291 | ; 292 | LD A,IXH ; Get Y in A 293 | CP IXL ; Compare with X 294 | RET C ; Return if X>Y 295 | BIT 7,H ; Check for Hl<=0 296 | JR Z,1F 297 | ADD HL,DE ; Delta=Delta+D1 298 | JR 2F 299 | 1: ADD HL,BC ; Delta=Delta+D2 300 | INC BC 301 | INC BC ; D2=D2+2 302 | DEC IXH ; Y=Y-1 303 | 2: INC BC ; D2=D2+2 304 | INC BC 305 | INC DE ; D1=D1+2 306 | INC DE 307 | INC IXL ; X=X+1 308 | JR 0B 309 | 310 | ; Draw the contents of the vector tables 311 | ; L: Start Y position 312 | ; B: Length 313 | ; 314 | Draw_Vector_Table: LD C,L ; Store the Y position in C 315 | IFDEF SCREEN_BUFFER ; If using a screen buffer... 316 | LD DE,SCREEN_BUFFER ; Calculate initial positio in buffer 317 | LD H,0 318 | DUP 5 319 | ADD HL,HL 320 | EDUP 321 | ADD HL,DE 322 | ELSE ; If drawing direct to screen... 323 | PUSH BC ; Get screen address 324 | LD B,L 325 | LD C,0 326 | CALL Get_Pixel_Address 327 | POP BC 328 | ENDIF 329 | 1: PUSH HL ; Save the screen buffer address 330 | LD H,high Vector_Table_X1 ; Get the MSB table in H - HL is now a pointer in that table 331 | LD L,C ; Get the LSB in from C 332 | LD D,(HL) ; Get X1 from the first table 333 | INC H ; Increment H to the second table (they're a page apart) 334 | LD E,(HL) ; Get X2 from the second table 335 | LD A,C ; Now calculate the index into the texture table 336 | AND 7 ; by getting the Y coordinate, ANDing with 7, and 337 | Draw_Vector_Table_1: LD HL,0 ; adding to the texture address, which has been self-modded into here 338 | ADD_HL_A ; Add it as an offst into that table 339 | LD A,(HL) ; And fetch the texture byte 340 | POP HL ; Pop screen position back off stack 341 | PUSH HL ; And stick it back on again 342 | PUSH BC 343 | CALL Draw_Horz_Line_Texture 344 | POP BC 345 | POP HL 346 | INC C 347 | IFDEF SCREEN_BUFFER ; If using scren buffer... 348 | LD DE,32 ; Just add 32 bytes to get to next line 349 | ADD HL,DE 350 | ELSE ; If writing direct to screen 351 | CALL Pixel_Address_Down ; Go to next line in screen memory 352 | ENDIF 353 | DJNZ 1B 354 | RET 355 | 356 | ; Draw Horizontal Line routine with texture 357 | ; HL = Screen address (first character row) 358 | ; D = X pixel position 1 359 | ; E = X pixel position 2 360 | ; A = Texture byte 361 | ; 362 | Draw_Horz_Line_Solid: LD A,255 363 | Draw_Horz_Line_Texture: PUSH HL ; Push screen address 364 | EX AF,AF ; Shove texture in AF' for the moment 365 | LD A,E ; Check if D > E 366 | CP D 367 | JR NC,0F 368 | LD E,D ; Swap D and E 369 | LD D,A 370 | 0: LD BC,Plot_Line_LHS ; Get pixel data table 371 | LD A,D ; Calculate index into table for X1 372 | AND 7 373 | ADD_BC_A 374 | LD A,D ; Calculate X1 byte position 375 | SRL A 376 | SRL A 377 | SRL A 378 | AND 31 379 | ADD A,L 380 | LD D,A ; D = X1 (byte) 381 | LD A,(BC) 382 | LD IXH,A ; IXH = X1 (data) 383 | LD BC,Plot_Line_RHS ; Get pixel data table 384 | LD A,E ; Calculate index into table for X1 385 | AND 7 386 | ADD_BC_A 387 | LD A,E ; Calculate X1 byte position 388 | SRL A 389 | SRL A 390 | SRL A 391 | AND 31 392 | ADD A,L 393 | LD E,A ; E = X2 (byte) and D = X1 (byte) 394 | LD A,(BC) 395 | LD IXL,A ; IXL = X2 (data) 396 | EX AF,AF ; Get the texture back 397 | LD C,A ; Stick it in C 398 | ; Here: 399 | ; H = High byte of screen buffer address 400 | ; E = X2 (low byte of screen buffer address), IXL = data 401 | ; D = X1 (low byte of screen buffer address), IXH = data 402 | ; C = Texture 403 | ; 404 | LD A,E ; Calculate line length in bytes 405 | SUB D 406 | LD B,A 407 | JR NZ, 1F ; If not zero, then skip to draw line 408 | LD L,D ; Special case when both endpoints in same byte 409 | LD A,IXH ; Get the LHS pixel data 410 | XOR IXL ; XOR with the RHS - this gives a mask 411 | LD D,A ; Store the mask in D 412 | CPL ; Get the pixel data by inverting the mask 413 | AND C ; AND it with the texture data 414 | LD E,A ; Store the pixel data in E 415 | LD A,(HL) ; Get the screen data 416 | AND D ; AND it with the mask 417 | OR E ; OR it with the pixel data 418 | LD (HL),A ; Write back to the screen 419 | POP HL ; POP the screen address back off the stack 420 | RET 421 | 422 | 1: LD L,D ; Draw the LHS byte 423 | LD A,IXH ; Get the pixel data 424 | AND C ; AND it with the texture data 425 | LD D,A ; Store in D 426 | LD A,IXH ; Get the pixel data again 427 | CPL ; Invert the bits to turn it into a mask 428 | AND (HL) ; AND the mask with the screen data 429 | OR D ; OR it with the pixel data 430 | LD (HL),A ; Write back to the screen 431 | INC L 432 | ; 433 | ; Draw the bulk of the line. This is an unrolled loop and works by skipping into the relevant 434 | ; bit of the unrolled routine 435 | ; 436 | LD A,31 ; Calculate how far to skip 437 | SUB B 438 | JR C,3F ; If negative, then skip 439 | SLA A 440 | LD (Draw_Horz_Line_Fast_M1+1),A ; Self-mod the JR instruction 441 | Draw_Horz_Line_Fast_M1: JR $+2 ; Jump into relevant bit of DUP'd code 442 | DUP 30 443 | LD (HL),C 444 | INC L 445 | EDUP 446 | 447 | 3: LD L,E ; Finally do the RHS byte 448 | LD A,IXL ; Get the pixel data 449 | AND C ; AND it with the texture data 450 | LD E,A ; Store in E 451 | LD A,IXL ; Get the pixel data 452 | CPL ; Invert the bits to turn it into a mask 453 | AND (HL) ; AND the mask with the screen data 454 | OR E ; OR it with the pixel data 455 | LD (HL),A ; Write back to the screen 456 | 457 | POP HL ; POP the screen address back off the stack 458 | RET 459 | 460 | ; End-points for the horizontal lines 461 | ; 462 | Plot_Line_LHS: DB %11111111,%01111111,%00111111,%00011111,%00001111,%00000111,%00000011,%00000001 463 | Plot_Line_RHS: DB %10000000,%11000000,%11100000,%11110000,%11111000,%11111100,%11111110,%11111111 464 | 465 | ; Some sample textures 466 | ; 467 | Vector_Texture_00: DB %11111111,%11111111,%11111111,%11111111,%11111111,%11111111,%11111111,%11111111 468 | Vector_Texture_01: DB %10101010,%01010101,%10101010,%01010101,%10101010,%01010101,%10101010,%01010101 469 | Vector_Texture_02: DB %10000001,%01000010,%00100100,%00011000,%00011000,%00100100,%01000010,%10000001 470 | Vector_Texture_03: DB %10001000,%01000100,%00100010,%00010001,%10001000,%01000100,%00100010,%00010001 --------------------------------------------------------------------------------