1;;==========================================================================;; 2;; Joe Zbiciak's 4-TRIS, A "Falling Tetrominoes" Game for Intellivision. ;; 3;; Copyright 2000, Joe Zbiciak, intvnut AT gmail.com. ;; 4;; http://spatula-city.org/~im14u2c/intv/ ;; 5;;==========================================================================;; 6 7;* ======================================================================== *; 8;* This program is free software; you can redistribute it and/or modify *; 9;* it under the terms of the GNU General Public License as published by *; 10;* the Free Software Foundation; either version 2 of the License, or *; 11;* (at your option) any later version. *; 12;* *; 13;* This program is distributed in the hope that it will be useful, *; 14;* but WITHOUT ANY WARRANTY; without even the implied warranty of *; 15;* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *; 16;* General Public License for more details. *; 17;* *; 18;* You should have received a copy of the GNU General Public License *; 19;* along with this program; if not, write to the Free Software *; 20;* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *; 21;* ======================================================================== *; 22;* Copyright (c) 2000, Joseph Zbiciak *; 23;* ======================================================================== *; 24 25 CFGVAR "name" = "4-Tris" 26 CFGVAR "author" = "Joe Zbiciak" 27 CFGVAR "year" = 2000 28 CFGVAR "music_by" = "Pyotr Ilyich Tchaikovsky" 29 CFGVAR "music_by" = "Bobby McFarrin" 30 CFGVAR "license" = "GPLv2+" 31 CFGVAR "publisher" = "SDK-1600" 32 33 ROMW 16 ; Just for the heck of it. 34 ORG $5000 ; Standard Mattel cartridge memory map 35 36;------------------------------------------------------------------------------ 37; Magic Constants 38;------------------------------------------------------------------------------ 39EDGEL EQU 15 ; Left edge column 40EDGER EQU 24 ; Right edge column 41EDGEB EQU 21 ; Bottom edge row 42MOVEINC EQU 5 ; Per-downward-move score increment 43DANGER EQU 6 ; Trigger point at which music goes 2x 44COPYR EQU $13A ; Character # for Copyright circle-C 45 46NXTPCX EQU 33 ; X coord where next piece is shown 47NXTPCY EQU 11 ; Y coord where next piece is shown 48 49LVLROW EQU 4 ; Row 'Level' banner is displayed on 50LVLCOL EQU 2 ; Column 'Level' banner is displayed on 51LVLLOC EQU $200 + 20*(LVLROW - 1) + LVLCOL 52 53LINROW EQU 7 ; Row 'Lines' banner is displayed on 54LINCOL EQU 2 ; Column 'Lines' banner is displayed on 55LINLOC EQU $200 + 20*(LINROW - 1) + LINCOL 56 57NXTROW EQU (NXTPCY - 3)/2 ; Row 'Next' banner is displayed on 58NXTCOL EQU (NXTPCX - 3)/2 ; Column 'Next' banner is displayed on 59NXTLOC EQU $200 + 20*(NXTROW - 1) + NXTCOL 60 61;------------------------------------------------------------------------------ 62; Magic memory locations 63;------------------------------------------------------------------------------ 64VBLANK EQU $20 ; Vertical-blank Handshake 65COLSTK EQU $21 ; Color-stack/FGBG switch 66CS0 EQU $28 ; Color Stack 0 67CS1 EQU $29 ; Color Stack 1 68CS2 EQU $2A ; Color Stack 2 69CS3 EQU $2B ; Color Stack 3 70CB EQU $2C ; Color for border 71ISRVEC EQU $100 ; ISR jump vector 72 73; $130 .. $136 ; Save area used by DRAWPIECE/TESTPIECE 74 75TMPPC STRUCT $137 ; Temporary storage 76@@C EQU $ + 0 ; (R0) Color of current piece UNUSED 77@@X EQU $ + 1 ; (R1) Pivot X coordinate for current piece 78@@Y EQU $ + 2 ; (R2) Pivot Y coordinate for current piece 79@@N EQU $ + 3 ; (R3) Piece number for current piece. 80 ENDS 81 82CURPC STRUCT $13B ; Current active piece 83@@C EQU $ + 0 ; (R0) Color of current piece 84@@X EQU $ + 1 ; (R1) Pivot X coordinate for current piece 85@@Y EQU $ + 2 ; (R2) Pivot Y coordinate for current piece 86@@N EQU $ + 3 ; (R3) Piece number for current piece. 87 ENDS 88 89PXQ_L0 EQU $140 ; Putpixel Queue length -- not committed 90PXQ_L1 EQU $141 ; Putpixel Queue length -- committed 91PXQ_XY EQU $142 ;..$161 ; Putpixel Queue XY data, interleaved. 92 93VOLSV EQU $170 ;..$172 ; Volume save-area during pause 94PAUSED EQU $173 ; Flag saying we paused. 95 96OVRFLO EQU $1A0 ; Number of overflows observed 97WASDOWN EQU $1A1 ; Flag: This movement was 'down' 98WASHAND EQU $1A2 ; Flag: This movement was 'hand' 99SLEVEL EQU $1A8 ; Current game level number 100LEVEL EQU $1A9 ; Current game level number 101SFXPRIO EQU $1AA ; Priority of currently playing sound 102SCRPOS EQU $1AB ; Position onscreen to display the score 103SCRDIG EQU $1AC ; 10 - Number of digits to display in score 104SHOWNXT EQU $1AD ; Show next piece 105NXTPC EQU $1AE ; Next Piece 106REPEAT EQU $1AF ; Flag: Set to keypress we're repeating 107LHAND EQU $1B0 ; Last hand-controller input 108LHKPD EQU $1B1 ; Last input was pad if non-zero 109MUTE EQU $1B2 ; Last input was pad if non-zero 110CSAVE EQU $1B3 ; Global 'c' save area 111TSKDQ EQU $1C0 ;..$1DF ; $1C0..$1DF: Task data queue 112XSAVE EQU $1E0 ; Global 'x' save area 113YSAVE EQU $1E1 ; Global 'y' save area 114 ;$1E2 .. $1E6, reserved 115DIDNXT EQU $1EB ; Next Piece was displayed some time this piece 116HEIGHT EQU $1EC ; Height of blocks in well. 117TSKACT EQU $1ED ; Number of highest numbered task + 1 118TSKQHD EQU $1EE ; Head pointer for task queue (0..15) 119TSKQTL EQU $1EF ; Tail pointer for task queue (0..15) 120PSG0 EQU $1F0 ;..$1FD ; PSG base address 121CTRL0 EQU $1FE ; Right hand controller 122CTRL1 EQU $1FF ; Left hand controller 123 124MAXTSK EQU 4 ; Right now allow 4 active tasks 125TSKQ EQU $320 ;..$32F ; $320..$32F: Task queue 126TSKTBL EQU $330 ;..$33F ; $330..$33F: Task table (four tasks) 127SNDSTRM EQU $340 ;..$347 ; $340..$347: Sound stream table 128PREVBL EQU $348 ; Pre-VBLANK routine address 129HANDFN EQU $34A ; Keypad dispatch function table. 130SCRCOL EQU $34B ; Color that score is displayed in 131WTIMER EQU $34C ; Countdown timer for WAIT 132REGSV EQU $34D ;..$351 ; $34D..$351: Register save area for SLEEP 133PSCORL EQU $352 ; Player's score (lo 16 bits) 134PSCORH EQU $353 ; Player's score (hi 16 bits) 135DSCORL EQU $354 ; Displayed score (lo 16 bits) 136DSCORH EQU $355 ; Displayed score (hi 16 bits) 137LINES EQU $356 ; Number of lines cleared so far 138MSCORE EQU $358 ; Total of per-move scores 139TMP EQU $35D 140RANDLO EQU $35E ; Low word of random number generator 141RANDHI EQU $35F ; High word of random number generator 142 143SPEED EQU TSKTBL + 3 ; Piece movement speed (IN TASK TABLE!) 144 145GROM EQU $3000 146GRAM EQU $3800 147 148 149;------------------------------------------------------------------------------ 150ROMHDR: BIDECLE ZERO ; MOB picture base (points to NULL list) 151 BIDECLE ZERO ; Process table (points to NULL list) 152 BIDECLE START ; Program start address 153 BIDECLE ZERO ; Bkgnd picture base (points to NULL list) 154 BIDECLE ONES ; GRAM pictures (points to NULL list) 155 BIDECLE T1TLE ; Cartridge title/date 156 DECLE $03C0 ; No ECS title, run code after title, 157 ; ... no clicks 158ZERO: DECLE $0000 ; Screen border control 159 DECLE $0000 ; 0 = color stack, 1 = f/b mode 160ONES: DECLE 1, 1, 1, 1, 1 ; Initial color stack / border color. 161;------------------------------------------------------------------------------ 162 163;;==========================================================================;; 164;; SNDTBL ;; 165;; Contains pointers and parameters for all sound effects and music. ;; 166;;==========================================================================;; 167SNDTBL PROC 168@@bip DECLE BIP, $2744 ; Played when player moves a piece 169@@byump DECLE BYUMP, $2744 ; Played when the player places a piece 170@@bomb DECLE BOMB, $27CC ; Played when clearing a line 171@@bomb4 DECLE BOMB4, $3FFF ; Played when clearing FOUR lines 172@@boom2 DECLE BOOM2, $3FFF ; Played when well overflows 173@@ding3 DECLE DING3, $35EE ; Played to signal changing level 174 175@@title DECLE NUTMARCH, $3FFF ; Title screen music 176@@game DECLE CHINDNCE, $3FFF ; In-game music 177@@over DECLE BEHAPPY, $3FFF ; Game-over music 178 179@@silence 180 DECLE SILENCE, $3FFF ; Silence! 181 ENDP 182 183FXBIP EQU SNDTBL.bip - SNDTBL 184FXBOMB EQU SNDTBL.bomb - SNDTBL 185FXBOMB4 EQU SNDTBL.bomb4 - SNDTBL 186FXBOOM2 EQU SNDTBL.boom2 - SNDTBL 187FXDING3 EQU SNDTBL.ding3 - SNDTBL 188FXBYUMP EQU SNDTBL.byump - SNDTBL 189 190M_TITLE EQU SNDTBL.title - SNDTBL 191M_GAME EQU SNDTBL.game - SNDTBL 192M_OVER EQU SNDTBL.over - SNDTBL 193 194FX_OFF EQU SNDTBL.silence - SNDTBL 195 196;;==========================================================================;; 197;; TITLE / START ;; 198;; ;; 199;; This contains the title string and the startup code. We pre-empt the ;; 200;; EXEC's initialization sequence by setting the "Special Copyright" bit ;; 201;; in location $500C. This causes the code at 'START' to run before the ;; 202;; built-in title screen is completely displayed. ;; 203;; ;; 204;; The Startup code does very little. Mainly, it sets the Interrupt ;; 205;; Service Routine vector to point to our _real_ initialization routine, ;; 206;; INIT. This is done because we can only get to GRAM and STIC registers ;; 207;; during the vertical retrace, and vertical retrace is signaled by an ;; 208;; interrupt. (Actually, we can have access to GRAM/STIC for longer ;; 209;; if we don't hit the STIC 'handshake' at location $20, but then the ;; 210;; display blanks. During INIT, the display does blank briefly.) ;; 211;;==========================================================================;; 212TITLE: BYTE 100, "*-TRIS", 0 ; Title: 4-TRIS, Copyright 2000 213 ; Intercept/preempt EXEC initialization and just do our own. 214 ; We call no EXEC routines in this game. 215START: 216 MVI $1FF, R2 217 AND $1FE, R2 218 219 CLRR R4 ; Prepare to zero all system RAM, PSG0,& STIC. 220 MVII #$20, R1 ; $00...$1F. (The STIC) 221 JSRD R5, FILLZERO 222 223 ADDI #8, R4 ; $28...$32. (The rest of the STIC) 224 MVII #11, R1 225 CALL FILLZERO 226 227 MVII #$F0, R4 ; $F0...$35D. We spare the rand seed values 228 MVII #$26D, R1 ; in $35E..$35F to add some randomness. 229 CALL FILLZERO 230 231 COMR R2 232 MVO R2, LHAND ; Set up initial hand-controller state 233 234 MVII #INIT, R0 ; Our initialization routine 235 MVII #ISRVEC, R4 ; ISR vector 236 MVO@ R0, R4 ; Write low half 237 SWAP R0 ; 238 MVO@ R0, R4 ; Write high half 239 MVI RANDLO, R0 ; Get whatever garbage data is in memory in the 240 MVI RANDHI, R1 ; random number generator fields 241 242 MOVR PC, R5 ; Loop starting at the next instruction. 243@@spin: 244 INCR R0 ; Seed random number generator while 245 DECR R1 ; we wait for our first interrupt. 246 MVO R0, RANDLO 247 MVO R1, RANDHI 248 EIS 249 ; This falls through to STUB which causes 250 ; our loop (saves a couple DECLEs.) 251 252;;==========================================================================;; 253;; STUB ;; 254;; Null routine used in dispatchers where no behavior is defined/desired. ;; 255;;==========================================================================;; 256STUB: JR R5 ; Stub routine 257 258 259;;==========================================================================;; 260;; INIT ;; 261;; Initializes the ISR, etc. Gets everything ready to run. ;; 262;; This is called via the ISR dispatcher, so it's safe to bang GRAM from ;; 263;; here, too. ;; 264;; ;; 265;; -- Zero out memory to get started ;; 266;; -- Set up variables that need to be set up here and there ;; 267;; -- Set up GRAM image ;; 268;; -- Drop into the main game state-machine. ;; 269;;==========================================================================;; 270INIT: PROC 271 DIS 272 MVII #$2F0, R6 ; Reset the stack pointer 273 274 MVI COLSTK, R1 ; Force display to color-stack mode 275 276 ; Make sure random number generator is non-zero. 277 MOVR PC, R0 ; The PC is guaranteed to be non-zero. 278 XOR RANDLO, R0 279 BEQ @@randok ; If the XOR result is zero, we're ok, 280 MVO R0, RANDLO ; otherwise make RANDLO non-zero. 281@@randok: 282 283 ; Stub out all of the task hooks 284 MVII #STUB, R0 ; Prepare to stub out some hooks. 285 MVO R0, PREVBL ; Initial pre-VBL task is NULL 286 287 MVII #MAINISR, R0 ; Point ISR vector to our ISR 288 MVO R0, ISRVEC ; store low half of ISR vector 289 SWAP R0 ; 290 MVO R0, ISRVEC+1 ; store high half of ISR vector 291 292 ; Do the face? 293 MVII #$D9, R0 294 CMP $1FE, R0 295 BEQ DOFACE 296 CMP $1FF, R0 297 BEQ DOFACE 298 299 ; Default the GRAM image to be same as GROM. 300 MVII #GROM, R5 ; Point R5 at GROM 301 MVII #GRAM, R4 ; Point R4 at GRAM 302 MVII #$200, R0 303@@gromcopy: 304 MVI@ R5, R1 305 MVO@ R1, R4 306 DECR R0 307 BNEQ @@gromcopy 308 309 ; Copy our GRAM font into GRAM overtop of default. 310 CALL LOADFONT 311 DECLE FONT 312 313 ; Ok, everything's ready to roll now. 314 EIS 315 316 ;;==================================================================;; 317 ;; Game phases: ;; 318 ;; -- Title screen ;; 319 ;; -- (optional) Sound Test ;; 320 ;; -- In Game ;; 321 ;; -- Game over ;; 322 ;; ;; 323 ;; The INIT code cycles between these gae phases by first ;; 324 ;; calling the subroutine which sets up the tasks for a given ;; 325 ;; game phase, and then calling the main event loop which ;; 326 ;; runs the game. This assumes that all tasks are stopped when ;; 327 ;; the MAINLOOP exits. Calling SCHEDEXIT accomplishes a clean ;; 328 ;; exit from MAINLOOP by scheduling an exit while stopping all ;; 329 ;; other tasks. (Neat, eh?) ;; 330 ;;==================================================================;; 331@@loop: 332 CALL TITLESCREEN ; Set up 'Title Screen' 333 CALL MAINLOOP ; Do title screen. 334 MVI SLEVEL, R0 ; If starting level == 0, do 'Sound Test' 335 TSTR R0 336 BEQ @@soundtest 337 338 CALL INGAME ; Set up 'In Game' 339 CALL MAINLOOP ; Do the Game. 340 341 CALL GAMEOVER ; Set up the 'Game Over' sequence. 342 CALL MAINLOOP ; Let it run. 343 344 B @@loop ; Start over at the title screen. 345 346@@soundtest: 347 CALL SOUNDTEST ; Sound-test requested. Set that up. 348 CALL MAINLOOP ; Run the sound-test. 349 B @@loop ; Go back to title screen when done. 350 ENDP 351 352;;==========================================================================;; 353;; LOADFONT -- Load a compressed FONT into GRAM. ;; 354;; ;; 355;; Note: This should be called only when the STIC is already in CPU- ;; 356;; controlled mode, such as from an interrupt handler. Loading a large ;; 357;; font may cause the screen to blank for one or more frames. ;; 358;; ;; 359;; Font data is broken up into spans of characters that are copied ;; 360;; into GRAM. Each span is defined as follows. ;; 361;; ;; 362;; Span Header: 2 Decles ;; 363;; DECLE Skip Length (in bytes of GRAM memory) ;; 364;; DECLE Span Length (in bytes of GRAM memory) ;; 365;; Span Data -- up to Span Length Decles. ;; 366;; ;; 367;; Span Data is run-length encoded using the upper two bits of the ;; 368;; decle to specify the run length. Valid run lengths are 0..3, ;; 369;; with 0 meaning "just copy this byte to the GRAM", and 3 meaning ;; 370;; "copy this byte to GRAM and make three more copies in the locations ;; 371;; afterwards". To see what I mean, look at the font data in ;; 372;; "font.asm". ;; 373;; ;; 374;; The run length encoding does not change the value used for ;; 375;; 'span length'. The span length is always given in terms of ;; 376;; # of GRAM locations, and not number of decles in the FONT data. ;; 377;; ;; 378;; The font is terminated with a span of length 0. ;; 379;; ;; 380;; INPUTS: ;; 381;; R5 -- Points to word (1 decle in 16-bit ROM) containing ptr to font ;; 382;; info. Code returns after this word. ;; 383;; ;; 384;; OUTPUTS: ;; 385;; R0, R1, R4, R5 trashed. ;; 386;; GRAM is updated according to the font specification. ;; 387;; ;; 388;;==========================================================================;; 389LOADFONT PROC 390 MVI@ R5, R0 391 PSHR R5 392 MOVR R0, R5 393 394 MVII #GRAM, R4 ; Point R4 at GRAM 395 396@@gramloop: 397 MVI@ R5, R0 ; Get skip & span len. (in GRAM bytes) 398 TSTR R0 ; Quit if skip/span == 0. 399 BEQ @@gramdone 400 401 MOVR R0, R1 402 ANDI #$7F8, R0 ; Extract span length. 403 XORR R0, R1 ; Clear away span bits from word 404 SWAP R1 ; Extrack skip value. 405 ADDR R1, R4 ; Skip our output pointer. 406 SLR R0, 1 ; Divide count by 2. 407 408@@charloop: 409 MVI@ R5, R1 ; Get two bytes 410 MVO@ R1, R4 ; Put the first byte 411 SWAP R1 ; Put the other byte into position 412 MVO@ R1, R4 ; Put the second byte 413 DECR R0 ; Sheesh, do I have to spell this out? 414 BNEQ @@charloop ; inner loop 415 B @@gramloop ; outer loop 416 417@@gramdone: 418 PULR PC 419 ENDP 420 421;;==========================================================================;; 422;; MAINISR ;; 423;; This is the main interrupt service routine. It has to perform the ;; 424;; following tasks: ;; 425;; ;; 426;; -- Run any miscellaneous "pre-VBLANK" task requested by main program. ;; 427;; -- Hit the vertical blank handshake to keep screen enabled. ;; 428;; -- Update any active sound streams. ;; 429;; -- Drain and pixels queued up in the Pixel Queue. ;; 430;; -- Count down task timers and schedule tasks when timers expire. ;; 431;; -- Count down the 'busy-wait' timer if it is set. ;; 432;; -- Detect a "Pause" request and pause the game if needed. ;; 433;; ;; 434;; This particular program currently does not sequence GRAM at all after ;; 435;; the initial bootup. Therefore, we can hit the VBLANK handshake almost ;; 436;; immediately after entering the ISR. We do support a pre-VBLANK ;; 437;; routine though for other purposes, such as setting color-stack regs. ;; 438;; ;; 439;; Our code does not rely on any EXEC routines at all, except the ISR ;; 440;; dispatch routine which saves and restores all the registers. (On a ;; 441;; real Intellivision, we have no choice. On an emulator, that routine ;; 442;; can be replaced with a separate non-EXEC routine which performs a ;; 443;; similar dispatch, if we want to distribute this program without an ;; 444;; EXEC ROM image but with an emulator.) ;; 445;; ;; 446;;==========================================================================;; 447MAINISR PROC 448 449 PSHR R5 ; Save return address 450 451 ;; Call out to user-defined pre-VBLANK routine. 452 ;; This routine should point to the stub if no routine is to 453 ;; be called. (simplifies the code w/out really slowing it down.) 454 MVII #@@rl0, R5 ; Point to @@rl0 455 MVI PREVBL, PC ; Call subroutine 456@@rl0: 457 ;; Hit the VBLANK handshake now 458 MVO R0, $20 ; Allow STIC to display this frame. 459 460 ;; Update sound streams. All sound streams update at 60Hz. 461 ;; Stream #0 can update at "60Hz * 2" if double-speed mode is on. 462 CALL SNDPRIO ; Make sure music & sfx play nice 463 464 ; Update music 465 CALL DOSNDSTREAM ; Update the sound stream 466 DECLE SNDSTRM ; Point to the music stream 467 468 ; Update sfx 469 CALL DOSNDSTREAM ; Update the sound stream 470 DECLE SNDSTRM+4 ; Point to the sfx stream 471 472 ; If well is too tall, play music fast. 473 MVII #DANGER, R0 474 CMP HEIGHT, R0 475 BLT @@slowmusic 476 MVII #SNDSTRM, R4 477 CALL DOSNDSTREAM ; Update the sound stream 478 DECLE SNDSTRM ; Point to the music stream 479@@slowmusic: 480 481 MVII #RANDHI,R1 482 MVI SNDSTRM+1, R2 483 XOR@ R1, R2 484 MVO@ R2, R1 ; Throw some bits into the random bucket 485 486 ;; Drain any pixels that were queued and committed. 487 CALL DRAINPXQ 488 489 ;; Count down task timers and schedule tasks. We have up to MAXTSK 490 ;; active tasks at one time. We decrement all counters by two. 491 ;; -- If the count is initially <= 0, the task is inactive and 492 ;; is skipped. 493 ;; -- If the count goes to zero, the task is triggered and its 494 ;; count is reinitialized. 495 ;; -- If the count goes negative, the task is triggered and its 496 ;; count is not reinitialized. 497 ;; This allows us to have one-shot and repeating tasks pretty 498 ;; easily. Repeating tasks clear bit 0 of their period count, 499 ;; and one-shot tasks set bit 0. 500 ;; 501 ;; When tasks are triggered, their procedure address is written 502 ;; to the task queue which is drained by the non-ISR main task. 503 ;; The task queue is a small circular buffer in 16-bit memory with 504 ;; head and tail pointers in 8-bit memory. The circular buffer is 505 ;; 16 entries large. I hope this is enough. Overflow can cause 506 ;; strange effects. 507 ;; 508 ;; Task control table entry layout: 509 ;; 510 ;; Word 0: Function Ptr 511 ;; Word 1: Instance data 512 ;; Word 2: Current down-count 513 ;; Word 3: Reinit count 514 515 MVI TSKACT, R0 ; Iterate over active tasks 516 TSTR R0 517 BEQ @@notasks 518 MVII #TSKTBL+2, R3 ; Point to the task control table 519 520@@taskloop: 521 MVI@ R3, R1 ; Get tasks's current tick count 522 TSTR R1 ; Is it <= 0? 523 BLE @@nexttask ; ... yes? Skip it. 524 525 MOVR R1, R2 526 ADD RANDLO, R2 527 MVO R2, RANDLO ; Throw some bits into the random bucket 528 529 SUBI #2, R1 ; Count it down. 530 BNEQ @@noreinit ; If it went to zero, reinit count 531 532 INCR R3 533 MVI@ R3, R1 ; Get new period for task 534 DECR R3 535 SUBR R2, R2 ; Z = 1, S = 0 (causes the BGT to not be taken) 536@@noreinit: 537 MVO@ R1, R3 ; Store new period. 538 BGT @@nexttask ; If this count didn't expire, ... 539 ; ... go to the next task 540@@q: 541 ; Schedule this task by adding it to the task queue 542 MOVR R3, R4 543 SUBI #2, R4 544 MVI@ R4, R2 ; Get task function pointer 545 MVI TSKQHD, R1 ; Get task queue head 546 INCR R1 ; Move to next slot 547 ANDI #$F, R1 ; Stay within 16-entry circ buffer 548 CMP TSKQTL, R1 549 BEQ @@overflow ; Drop this task if queue overflows. (!) 550 551 MOVR R1, R5 552 ADDR R1, R5 ; R5 is for task data queue 553 ADDI #TSKDQ, R5 ; Point to task data queue 554 555 MVO R1, TSKQHD ; Store updated task queue head 556 557 ADDI #TSKQ, R1 ; Point to actual task queue 558 MVO@ R2, R1 ; Store the function pointer 559 MVI@ R4, R2 ; Get task instance data 560 MVO@ R2, R5 ; Store low half of instance data 561 SWAP R2, 1 562 MVO@ R2, R5 ; Store high half of instance data 563 564@@nexttask: 565 ADDI #4, R3 ; Point to next task struct 566 DECR R0 567 BNEQ @@taskloop ; Loop until done with all tasks 568 B @@notasks 569 570@@overflow: 571 MVI OVRFLO, R0 572 INCR R0 573 MVO R0, OVRFLO 574 575@@notasks: 576 577@@wtimer: 578 ;; Count down the wait-timer, if there is one 579 MVI WTIMER, R5 580 DECR R5 581 BMI @@expired 582 MVO R5, WTIMER 583@@expired: 584 585 ;; Check to see if the user has requested a pause. 586 MVII #$5A, R0 ; Keypad 1 + Keypad 9 587 CMP $1FE, R0 ; Pause on first controller? 588 BEQ @@pausing 589 CMP $1FF, R0 ; Pause on second controller? 590 BEQ @@pausing 591 592 PULR PC ; Return 593 594@@pausing: 595 DIS ; Disable interrupts while paused. 596 ; Grab PSG volume settings aside and force volume to zero. 597 MVII #VOLSV, R2 598 MVII #$1FB, R3 599 MOVR R2, R5 600 MOVR R3, R4 601 602 MVO R4, PAUSED ; Set 'we paused' flag 603 MVI@ R4, R0 ; Channel a 604 MVO@ R0, R5 605 MVI@ R4, R0 ; Channel b 606 MVO@ R0, R5 607 MVI@ R4, R0 ; Channel c 608 MVO@ R0, R5 609 610 CLRR R1 611 MOVR R3, R4 612 MVO@ R1, R4 613 MVO@ R1, R4 614 MVO@ R1, R4 615 616 CLRC 617 CALL @@waithand ; Wait for both controllers to be released. 618 SETC 619 CALL @@waithand ; Wait for either controller to be pressed 620 CLRC 621 CALL @@waithand ; Wait for both controllers to be released. 622 623 MOVR R3, R5 624 MOVR R2, R4 625 MVI@ R4, R0 ; Channel a 626 MVO@ R0, R5 627 MVI@ R4, R0 ; Channel b 628 MVO@ R0, R5 629 MVI@ R4, R0 ; Channel c 630 MVO@ R0, R5 631 EIS 632 PULR PC 633 634@@waitpress: 635 BNEQ @@waitdone 636@@waithand: 637 MVII #100, R1 ; Debounce factor 638@@waitloop: 639 MVII #$1FE, R4 640 SDBD 641 MVI@ R4, R0 642 COMR R0 643 BC @@waitpress 644 BNEQ @@waithand 645@@waitdone: 646 DECR R1 647 BNEQ @@waitloop 648 JR R5 649 650 ENDP 651 652;;==========================================================================;; 653;; MAINLOOP ;; 654;; The main loop runs asynchronously from the MAINISR, running tasks as ;; 655;; they get scheduled. There is also a background task that runs when ;; 656;; nothing else is in the queue. ;; 657;; ;; 658;; -- Grab task from task queue, if any ;; 659;; -- run that task ;; 660;; -- look for next task ;; 661;; -- run background task once if no tasks available ;; 662;; ;; 663;; The background task scans hand controllers. I do this because ;; 664;; hand-controller scanning could be fairly expensive. Also, scanning ;; 665;; hand controllers may cause new tasks to get scheduled (eg. the triggers ;; 666;; for various hand controller events) and I don't want to overrun the ;; 667;; task queue. ;; 668;; ;; 669;; The background task also updates the random number generator state. I ;; 670;; do this to make the random numbers a bit more random since the game ;; 671;; loading varies according to what's going on. ;; 672;;==========================================================================;; 673MAINLOOP PROC 674 675 PSHR R5 ; Save return address, since any 676 ; task is allowed to exit the main 677 ; game loop, say, when switching 678 ; between game phases. 679 ; 680 ; There are three game phases: 681 ; -- Title Screen 682 ; -- In Game 683 ; -- Game Over. 684 685 MVII #EDGEB, R1 ; Set the well height so that music 686 MVO R1, HEIGHT ; plays correctly 687 MVO R1, MUTE ; Also, un-mute music. 688 689@@loop: 690 MOVR PC, R5 ; Set our return address to @@loop 691 DECR R5 ; This is 2 decles vs. 4 for SDBD/MVII 692 693 ; Run next scheduled task from queue 694 DIS ; Shut off interrupts 695 MVI TSKQTL, R1 ; Get the tail of the queue 696 CMP TSKQHD, R1 ; Are there tasks in the queue? 697 BEQ @@bktsk ; No: Do background task 698 699 INCR R1 ; Pop task from queue by 700 ANDI #$F, R1 ; ... moving the queue tail 701 MVO R1, TSKQTL ; Store new task-queue tail 702 MOVR R1, R4 703 ADDR R4, R4 704 ADDI #TSKDQ, R4 705 SDBD 706 MVI@ R4, R2 ; Load instance data for task 707 ADDI #TSKQ, R1 ; Point to task queue 708 MVI@ R1, R0 ; Get function pointer from queue 709 710 ; Dispatch to user's task. Make sure interrupts are enabled again 711 EIS ; Done with critical section 712 JR R0 ; Jump to subroutine 713 ; Note that this implicitly loops back to @@oloop here 714 715@@bktsk: 716 EIS ; Done with critical section 717 CALL NEXTRAND ; Update random number state 718 CALL SCANHAND ; Scan the hand controllers. 719 720EC_LOC EQU $CF00 721EC_MAG EQU $69 722EC_POLL EQU $CF01 723 724 MVI EC_LOC, R0 725 CMPI #EC_MAG,R0 726 BNEQ @@loop 727 CALL EC_POLL 728 729 B @@loop ; Run around the inner loop 730 731 ENDP 732 733;;==========================================================================;; 734;; QTASK ;; 735;; Add a task to the task queue. ;; 736;; NOTE: CALL THIS WITH INTERRUPTS OFF!!!! (eg. use JSRD). Interrupts ;; 737;; will be enabled before return. ;; 738;; ;; 739;; INPUTS: ;; 740;; R0 -- Task function pointer ;; 741;; R1 -- Task instance data ;; 742;; R5 -- Return address. ;; 743;; ;; 744;; OUTPUTS: ;; 745;; R0 -- intact. ;; 746;; R1 -- swapped ;; 747;; R2, R4 -- trashed. ;; 748;;==========================================================================;; 749QTASK PROC 750 MVI TSKQHD, R2 ; Get head of task queue 751 INCR R2 ; Move to next queue slot 752 ANDI #$F, R2 ; Stay in circular buffer 753 CMP TSKQTL, R2 ; Are we overflowing the queue? 754 BEQ @@overflow ; Yes ... don't overflow it! 755 MVO R2, TSKQHD ; Store new task queue head 756 757 MOVR R2, R4 ; Generate instance data pointer 758 ADDR R2, R4 ; (remember mult by two for data ptr) 759 ADDI #TSKQ, R2 ; Point into task queue 760 MVO@ R0, R2 ; Store function pointer to task q 761; moved addi for STIC interruptibility fix 762 ADDI #TSKDQ, R4 ; Point into task data queue 763 MVO@ R1, R4 ; Store instance data to data q 764 SWAP R1 ; .... cont'd 765 MVO@ R1, R4 ; .... cont'd 766 EIS ; Re-enable ints after crit section 767 JR R5 ; Return. 768 769@@overflow: 770 MVI OVRFLO, R2 771 INCR R2 772 MVO R2, OVRFLO 773 774 EIS ; Re-enable ints after crit section 775 JR R5 ; Return. 776 777 ENDP 778 779;;==========================================================================;; 780;; SCHEDEXIT ;; 781;; Flushes the task queue and schedules the exit task. ;; 782;; ;; 783;; INPUTS: ;; 784;; R5 -- Return address. ;; 785;;==========================================================================;; 786SCHEDEXIT: PROC 787 PSHR R5 ; Push return address. 788 JSRD R5,STOPALLTASKS ; Flush the pending task queue 789 MVII #@@exit, R0 ; Schedule the exit task ... 790 PULR R5 791 B QTASK ; ... and chain the return. 792@@exit: PULR PC 793 ENDP 794 795;;==========================================================================;; 796;; STOPALLTASKS ;; 797;; Stops all tasks by setting the task count to zero and by ;; 798;; flushing the task queue (sets head/tail to 0). Returns old task count. ;; 799;; NOTE: CALL THIS WITH INTERRUPTS OFF!!!! (eg. use JSRD) Interrupts ;; 800;; are NOT re-enabled by this function. ;; 801;; ;; 802;; INPUTS: ;; 803;; R5 -- Return address ;; 804;; ;; 805;; OUTPUTS: ;; 806;; R0 -- Previous TSKACT ;; 807;; R1 -- Cleared ;; 808;; All tasks stopped. ;; 809;;==========================================================================;; 810STOPALLTASKS PROC 811 MVI TSKACT, R0 812 CLRR R1 813 MVO R1, TSKACT 814 MVO R1, TSKQHD 815 MVO R1, TSKQTL 816 DECR R1 817 MVO R1, TSKTBL+2 ; period == -1 for task 0 818 MVO R1, TSKTBL+6 ; period == -1 for task 1 819 MVO R1, TSKTBL+10 ; period == -1 for task 2 820 NOP ; (interruptible, for STIC) 821 MVO R1, TSKTBL+14 ; period == -1 for task 3 822 JR R5 823 824 ENDP 825 826;;==========================================================================;; 827;; STARTTASK ;; 828;; Puts a task into a task slot for general timer-based scheduling ;; 829;; ;; 830;; INPUTS: ;; 831;; R5 -- Invocation record: ;; 832;; DECLE -- Task number ;; 833;; DECLE -- Task function pointer ;; 834;; DECLE -- Task initial period * 2. If bit 0==1, this is one-shot task. ;; 835;; DECLE -- Task period re-init. Useful if first delay!=recurring delay. ;; 836;; R2 -- Task instance data (optional) ;; 837;; ;; 838;; OUTPUTS: ;; 839;; Task record is set up. (User still needs to update TSKACT to make ;; 840;; the task records active, if necessary.) ;; 841;; ;; 842;; Registers R0, R4 are trashed. ;; 843;;==========================================================================;; 844STARTTASK PROC 845 MVI@ R5, R0 846 SLL R0, 2 ; R0 = R0 * 4 (four words/entry) 847 MVII #TSKTBL,R4 ; R4 = &TSKTBL[0] 848 ADDR R0, R4 ; R4 = &TSKTBL[n] 849 DIS ; Entering critical section. 850 MVI@ R5, R0 ; Get Function pointer 851 MVO@ R0, R4 ; ... and write it 852 MVO@ R2, R4 ; Write task instance data 853 MVI@ R5, R0 ; Get task period 854 MVO@ R0, R4 ; ... and write it 855 MVI@ R5, R0 ; Get task period reinit 856 MVO@ R0, R4 ; ... and write it 857 EIS 858 JR R5 859 ENDP 860 861;;==========================================================================;; 862;; STOPTASK ;; 863;; Stops a given task by setting its period to -1. Task can be ;; 864;; retriggered/restarted by calling RETRIGGERTASK. ;; 865;; ;; 866;; INPUTS: ;; 867;; R3 -- Task number. ;; 868;; ;; 869;; OUTPUTS: ;; 870;; Task is disabled. ;; 871;; R0 == -1, R3 points to task delay count. ;; 872;;==========================================================================;; 873STOPTASK PROC 874 SLL R3, 2 ; R3 = R3 * 4 (four words/entry) 875 ADDI #TSKTBL+2, R3 ; R3 = &TSKTBL[n].delay 876 CLRR R0 877 DECR R0 ; R0 == -1. 878 MVO@ R0, R3 ; TSKTBL[n].delay = -1 879 JR R5 880 ENDP 881 882;;==========================================================================;; 883;; RETRIGGERTASK ;; 884;; Restarts a task by copying its delay re-init field to its delay field. ;; 885;; Can be used on one-shot tasks or tasks which have been stopped with ;; 886;; 'StopTask'. ;; 887;; ;; 888;; INPUTS: ;; 889;; R3 -- Task number. ;; 890;; ;; 891;; OUTPUTS: ;; 892;; R0 -- Task delay ;; 893;; R3 -- points to task delay count ;; 894;;==========================================================================;; 895 896RETRIGGERTASK: PROC 897 SLL R3, 2 ; R3 = R3 * 4 (four words/entry) 898 ADDI #TSKTBL+3, R3 ; R3 = &TSKTBL[n].reload 899 DIS 900 MVI@ R3, R0 ; Get reload value 901@@chain: 902 DECR R3 ; Offset 2 is delay count 903 MVO@ R0, R3 904 EIS 905 JR R5 906 907 ENDP 908 909;;==========================================================================;; 910;; WAIT ;; 911;; Busy-waits for the number of ticks specified in R0. ;; 912;; ;; 913;; INPUTS: ;; 914;; R0 -- Number of ticks to wait ;; 915;; R5 -- Return address ;; 916;; ;; 917;; OUTPUTS: ;; 918;; R0 -- cleared ;; 919;;==========================================================================;; 920WAIT PROC 921 MVI@ R5, R0 922 MVO R0, WTIMER 923 CLRR R0 924@@loop: 925 CMP WTIMER, R0 926 BNEQ @@loop 927 JR R5 928 ENDP 929 930 931;;==========================================================================;; 932;; SLEEP ;; 933;; Causes a one-shot process to sleep by scheduling a second one-shot ;; 934;; process to reawake it. This is VERY DIFFICULT TO USE, and can be ;; 935;; used by ONE PROCESS ONLY at a time. ;; 936;; ;; 937;; SPAWN ;; 938;; Same as sleep, only the sleeping task isn't a one-shot. It keeps ;; 939;; retriggering automatically at the designated interval, or can be ;; 940;; retriggered manually if the period is odd. ;; 941;; ;; 942;; INPUTS: ;; 943;; R5 -- Invocation record, in following format: ;; 944;; Period/sleep time 1 Decle (Must be ODD for SLEEP) ;; 945;; Process # to use 1 Decle ;; 946;; Top of Stack -- Return address from _this_ process back to scheduler. ;; 947;; Task will wake at address after invocation record. ;; 948;; ;; 949;; OUTPUTS: ;; 950;; R0, R1, R2, R3, R4 restored ;; 951;; R5 points to your return address ;; 952;; Top of stack has return address for scheduler ;; 953;;==========================================================================;; 954SLEEP: PROC 955SPAWN: 956 MVO R4, REGSV+4 ; Save R4 957 MVII #REGSV, R4 958 MVO@ R0, R4 ; Save R0 959 MVO@ R1, R4 ; Save R1 960 MVO@ R2, R4 ; Save R2 961 MVO@ R3, R4 ; Save R3 962 963 MVI@ R5, R2 ; Get sleep time 964 MVI@ R5, R3 ; Get PID 965 966 SLL R3, 2 ; R3 = R3 * 4 (four words/entry) 967 MVII #TSKTBL,R4 ; R4 = &TSKTBL[0] 968 ADDR R3, R4 ; R4 = &TSKTBL[n] 969 MVII #@@wake,R0 970 MVO@ R0, R4 ; Address to wake up at 971 MVO@ R5, R4 ; Instance data (R2 restore) 972 MVO@ R2, R4 973 MVO@ R2, R4 974 975 PULR PC 976 977@@wake: 978 PSHR R5 ; Remember return address 979 MOVR R2, R5 980 MVII #REGSV, R4 ; Point to register save area 981 MVI@ R4, R0 ; Restore R0 982 MVI@ R4, R1 ; Restore R1 983 MVI@ R4, R2 ; Restore R2 984 MVI@ R4, R3 ; Restore R3 985 MVI@ R4, R4 ; Restore R4 986 987 JR R5 ; Go there 988 989 ENDP 990 991;;==========================================================================;; 992;; DOFACE ;; 993;;==========================================================================;; 994DOFACE PROC 995 DIS 996 MVII #$2F0, SP 997 998 MVII #@@loadface, R0 999 MVO R0, PREVBL 1000 SWAP R0 1001 MVO R0, PREVBL+1 1002 1003 CLRR R0 1004 MVII #$F0, R1 1005 MVII #$200, R4 1006 CALL FILLMEM 1007 1008 MVII #$200 + 8 + 20, R4 1009 MVII #FACE, R5 1010 MVII #9, R0 1011 MVO R0, CB 1012 MVO R0, CS0 1013@@f_oloop: 1014 MVII #6, R2 1015@@f_iloop: 1016 MVI@ R5, R1 1017 MVO@ R1, R4 1018 DECR R2 1019 BNEQ @@f_iloop 1020 ADDI #14, R4 1021 DECR R0 1022 BNEQ @@f_oloop 1023 1024 CALL DRAWSTRING3 1025 DECLE 0, $200 + 20*10 1026 ;01234567890123456789 1027 BYTE $4A, $6F, $65, $20, $5A, $62, $69, $63, $69, $61, $6B 1028 BYTE $20, $73, $61, $79, $73, $20, $48, $49, $21, $0A, $00 1029 1030 EIS 1031 DECR PC 1032 1033@@loadface: 1034 MVII #@@shunt, R0 1035 MVO R0, PREVBL 1036 SWAP R0 1037 MVO R0, PREVBL+1 1038 1039 MVII #9, R0 1040 MVO R0, CS0 1041 MVO R0, CB 1042 1043 CALL LOADFONT 1044 DECLE FACEFONT 1045@@shunt: 1046 MVO R0, $20 1047 PULR PC 1048 ENDP 1049 1050;;==========================================================================;; 1051;; Colored squares pixel-manipulation routines. ;; 1052;; J. Zbiciak, July 1999 ;; 1053;;==========================================================================;; 1054 1055; Data tables for pixel routines 1056MSKTBL: 1057 DECLE $0007, $0038, $01C0, $2600 1058 1059CLRTBL: 1060 DECLE $0000, $0249, $0492, $06DB 1061 DECLE $2124, $236D, $25B6, $27FF 1062 1063 1064;;==========================================================================;; 1065;; PIXCALC -- Pixel Calculation: Convert x,y into addr,mask ;; 1066;; ;; 1067;; INPUTS: ;; 1068;; R1 -- X coordinate ;; 1069;; R2 -- Y coordinate ;; 1070;; R5 -- Return address ;; 1071;; ;; 1072;; OUTPUTS: ;; 1073;; R1 -- Pixel mask ;; 1074;; R2 -- Address in display memory. ;; 1075;; R3, R4 -- Clobbered. ;; 1076;;==========================================================================;; 1077PIXCALC PROC 1078 SLL R2, 1 ; y' = y << 1 1079 MOVR R2, R4 ; mskidx = y << 1 1080 ANDI #2, R4 ; mskidx = (y << 1) & 2 1081 SUBR R4, R2 ; y' = (y & ~1) << 1 1082 MOVR R2, R3 ; save y' 1083 SLL R2, 2 ; y'' = (y & ~1) << 3 1084 ADDR R3, R2 ; y''' = y' + y'' = (y >> 1) * 20 1085 SARC R1, 1 ; c = x & 1; x' = x >> 1 1086 ADCR R4 ; mskidx = ((y << 1) & 2) + (x & 1) 1087 ADDR R1, R2 ; ofs = x' + y''' = (x >> 1) + (y >> 1) * 20 1088 ADDI #$200, R2 ; ptr = ofs + $200 = &BACKTAB[ofs] ==> R2 1089 ADDI #MSKTBL,R4 ; mskptr = MSKTBL + mskidx' = &MSKTBL[mskidx] 1090 MVI@ R4, R1 ; mask = *mskptr ==> R1 1091 JR R5 ; return 1092 1093 ENDP 1094 1095;;==========================================================================;; 1096;; PUTPIXEL -- Put pixel at x, y coordinates with color 'c' (0-7) ;; 1097;; ;; 1098;; INPUTS: ;; 1099;; R0 -- Color ;; 1100;; R1 -- X coordinate ;; 1101;; R2 -- Y coordinate ;; 1102;; R5 -- Return address ;; 1103;; ;; 1104;; OUTPUTS: ;; 1105;; R1 -- Word stored at R2 ;; 1106;; R2 -- Display memory address ;; 1107;; R0, R3, R4, R5 -- Clobbered. ;; 1108;;==========================================================================;; 1109 1110PUTPIXEL PROC 1111 PSHR R5 ; Save return address 1112 MVII #PUTPIXELR+1,R5 ; Return to actual pixel draw below 1113 B PIXCALC ; Convert x,y into mask,addr 1114 1115;;==========================================================================;; 1116;; PUTPIXELR -- Put pixel 'raw': accepts address, mask, and color ;; 1117;; (Alternate entry point for PUTPIXEL.) ;; 1118;; ;; 1119;; INPUTS: ;; 1120;; R0 -- Color ;; 1121;; R1 -- Pixel mask ;; 1122;; R2 -- Display memory address ;; 1123;; ;; 1124;; OUTPUTS: ;; 1125;; R1 -- Word stored at R2 ;; 1126;; R2 -- Display memory address ;; 1127;; R0,R5 -- Clobbered. ;; 1128;;==========================================================================;; 1129PUTPIXELR: 1130 PSHR R5 ; Save return address 1131 MOVR R0, R5 1132 ADDI #CLRTBL,R5 ; clrptr = CLRTBL + c' = &CLRTBL[c] 1133 MVI@ R5, R0 ; color = CLRTBL[c] 1134 ANDR R1, R0 ; color = color & mask (select desired pix) 1135 COMR R1 ; invert mask 1136 AND@ R2, R1 ; pix = BACKTAB & ~mask (clear pixel) 1137 ADDR R0, R1 ; pix' = pix + color (merge pixels) 1138 MVO@ R1, R2 ; BACKTAB[ofs] = pix' (write pixels) 1139 PULR PC ; return. 1140 1141 ENDP 1142 1143;;==========================================================================;; 1144;; PIXELCLIP ;; 1145;; Returns with C==1 if pixel is onscreen ;; 1146;; ;; 1147;; INPUTS: ;; 1148;; R1 -- X coordinate ;; 1149;; R2 -- Y coordinate ;; 1150;; R5 -- Return address ;; 1151;; ;; 1152;; OUTPUTS: ;; 1153;; Carry == 1 if onscreen, 0 if offscreen. ;; 1154;;==========================================================================;; 1155 1156PIXELCLIP PROC 1157 TSTR R1 ; Off left? 1158 BMI @@offscr ; Yes: Set carry and exit 1159 TSTR R2 ; Off top? 1160 BMI @@offscr ; Yes: Set carry and exit 1161 CMPI #39, R1 ; Off right? 1162 BGT @@offscr ; Yes: Set carry and exit 1163 CMPI #23, R2 ; Off bottom? 1164 BGT @@offscr ; Yes: Set carry and exit 1165 SETC ; No to all: Clear carry and exit 1166 JR R5 1167@@offscr 1168 CLRC 1169 JR R5 1170 ENDP 1171 1172;;==========================================================================;; 1173;; GETPIXELS -- Calls GETPIXEL, but saves/restores X, Y ;; 1174;; ;; 1175;; INPUTS: ;; 1176;; R1 -- X coordinate ;; 1177;; R2 -- Y coordinate ;; 1178;; R5 -- Return addr. ;; 1179;; ;; 1180;; OUTPUTS ;; 1181;; R0 -- Color ;; 1182;; R1 -- X coordinate ;; 1183;; R2 -- Y coordinate ;; 1184;; R3, R4, R5 -- Clobbered. ;; 1185;;==========================================================================;; 1186GETPIXELS PROC 1187 PSHR R5 1188@@chain: 1189 MVO R1, XSAVE 1190 MVO R2, YSAVE 1191 CALL GETPIXEL 1192 MVI XSAVE, R1 1193 MVI YSAVE, R2 1194 PULR PC 1195 ENDP 1196 1197 1198;;==========================================================================;; 1199;; PUTPIXELS -- Calls PUTPIXEL, but saves/restores X, Y ;; 1200;; ;; 1201;; INPUTS: ;; 1202;; R0 -- Color ;; 1203;; R1 -- X coordinate ;; 1204;; R2 -- Y coordinate ;; 1205;; R5 -- Return addr. ;; 1206;; ;; 1207;; OUTPUTS ;; 1208;; R0 -- Color ;; 1209;; R1 -- X coordinate ;; 1210;; R2 -- Y coordinate ;; 1211;; R3, R4, R5 -- Clobbered. ;; 1212;;==========================================================================;; 1213PUTPIXELS PROC 1214 PSHR R5 1215@@chain: 1216 MVO R1, XSAVE 1217 MVO R2, YSAVE 1218 MVO R0, CSAVE 1219 CALL PUTPIXEL 1220 MVI CSAVE, R0 1221 MVI XSAVE, R1 1222 MVI YSAVE, R2 1223 PULR PC 1224 ENDP 1225 1226;;==========================================================================;; 1227;; GETPIXELSC ;; 1228;; Calls GETPIXELS or returns R0 unchanged if pixel is off screen ;; 1229;; ;; 1230;; INPUTS: ;; 1231;; R0 -- Color to return if off-screen ;; 1232;; R1 -- X coordinate ;; 1233;; R2 -- Y coordinate ;; 1234;; R5 -- Return addr. ;; 1235;; ;; 1236;; OUTPUTS ;; 1237;; R0 -- Color ;; 1238;; R1 -- X coordinate ;; 1239;; R2 -- Y coordinate ;; 1240;; R3, R4, R5 -- Clobbered. ;; 1241;;==========================================================================;; 1242GETPIXELSC PROC 1243 PSHR R5 ; Save return address 1244 CALL PIXELCLIP ; See if pixel is onscreen 1245 ADCR PC ; If it is, then skip the return 1246 PULR PC ; Otherwise return. 1247 B GETPIXELS.chain ; If onscreen chain over to GETPIXELS 1248 ENDP 1249 1250 1251;;==========================================================================;; 1252;; GETPIXEL -- Get pixel at x, y address ;; 1253;; ;; 1254;; INPUTS: ;; 1255;; R1 -- X coordinate ;; 1256;; R2 -- Y coordinate ;; 1257;; ;; 1258;; OUTPUTS: ;; 1259;; R0 -- Color ;; 1260;; R2 -- Display memory address ;; 1261;; R1, R3, R4, R5 -- Clobbered. ;; 1262;;==========================================================================;; 1263GETPIXEL PROC 1264 PSHR R5 ; Save return address 1265 MVII #GETPIXELR+1,R5 ; Return to actual pixel read below 1266 B PIXCALC ; Convert x,y into mask,addr 1267 1268;;==========================================================================;; 1269;; GETPIXELR -- Get pixel 'raw': accepts address, mask ;; 1270;; (Alternate entry point for getpixel) ;; 1271;; INPUTS: ;; 1272;; R1 -- Pixel mask ;; 1273;; R2 -- Display memory address ;; 1274;; ;; 1275;; OUTPUTS: ;; 1276;; R0 -- Color ;; 1277;; R2 -- Display memory address ;; 1278;; R1, R5 -- Clobbered. ;; 1279;;==========================================================================;; 1280GETPIXELR: 1281 PSHR R5 ; Save return address 1282 MVI@ R2, R0 ; pix = BACKTAB[ofs] 1283 ANDR R1, R0 ; pix' = pix & mask 1284 MOVR R0, R1 ; 1285 SWAP R0, 1 ; 1286 ANDI #$20, R0 ; 1287 ADDR R1, R0 ; merge oddball bit from lower right pixel 1288 SLL R1, 2 ; 1289 SWAP R1, 1 ; 1290 ADDR R1, R0 ; Fold upper, lower pairs of pixels 1291 MOVR R0, R1 ; 1292 SLR R0, 1 ; 1293 SLR R0, 2 ; 1294 ADDR R1, R0 ; Fold left, right pixels 1295 ANDI #7, R0 ; Select remaining pixel 1296 PULR PC ; return 1297 1298 ENDP 1299 1300 1301;;==========================================================================;; 1302;; PUTPIXELSCQ ;; 1303;; ;; 1304;; Queues a pixel for display if it's onscreen. This routine does not ;; 1305;; draw the pixel directly, but rather puts it in a queue of pixels that ;; 1306;; are waiting to be drawn. This queue is emptied during the interrupt ;; 1307;; service routine after the queue is "committed" for display with a call ;; 1308;; to COMMITPXQ. ;; 1309;; ;; 1310;; The pixel queue serves as a mechanism for reducing/eliminating the ;; 1311;; flicker associated with redrawing a game piece. Without the queue, ;; 1312;; piece movements could result in a significant amount of flicker. ;; 1313;; To further reduce flicker, the queue insert routine attempts to ;; 1314;; optimize multiple 'PUTPIXELs' to the same location, with the last ;; 1315;; PUTPIXEL taking precedence. ;; 1316;; ;; 1317;; Because this queue is intended for drawing game pieces only, it is ;; 1318;; not very large. No checking is performed for queue overflow, either. ;; 1319;; Also, because double-buffering is not performed, this routine will ;; 1320;; block if there is a committed pixel queue waiting to be drawn. ;; 1321;; ;; 1322;; INPUTS: ;; 1323;; R0 -- Color ;; 1324;; R1 -- X coordinate ;; 1325;; R2 -- Y coordinate ;; 1326;; ;; 1327;; OUTPUTS ;; 1328;; R0 -- Color ;; 1329;; R1 -- X coordinate ;; 1330;; R2 -- Y coordinate ;; 1331;; R3, R4, R5 -- Clobbered. ;; 1332;;==========================================================================;; 1333PUTPIXELSCQ PROC 1334 PSHR R5 ; Save return address 1335 CALL PIXELCLIP ; See if pixel is onscreen 1336 ADCR PC ; If it is, then skip the return 1337 PULR PC ; Otherwise return. 1338 1339 ; Merge Y coordinate and color up-front, since we store 1340 ; them merged in the queue. 1341 MOVR R0, R3 ; Work on a copy of our color 1342 SWAP R3 1343 SLR R3, 2 1344 SLR R3, 1 ; R3 = color << 5 1345 XORR R3, R2 ; Put color in bits 5..7 of R2 1346 1347 DIS ; Critical section: Queue management. 1348 ; First, check to see if any pixels are queued at all. If there 1349 ; aren't any, just jump straight to the queue-insert. 1350 MVI PXQ_L0, R3 1351 TSTR R3 1352 BEQ @@insert_eis 1353 1354 ; Next, check to see if there are any committed pixels waiting 1355 ; to be drawn in the pixel queue. Wait until the queue is drained. 1356 CLRR R3 1357 CMP PXQ_L1, R3 ; If there are no committed pixels, we 1358 EIS ; Interrupts can be enabled now. 1359 BEQ @@check ; still need to check for redundant pixels. 1360@@drain: 1361 CMP PXQ_L1, R3 ; Otherwise, let the queue drain and jump 1362 BNEQ @@drain ; straight to the insert since it's empty 1363 B @@insert 1364 1365@@check: 1366 ; If we reach here, we have a non-empty pixel queue, and no 1367 ; committed pixels. Step through the queue to see if we have 1368 ; any redundant pixels. 1369 MVII #PXQ_XY - 1, R4 ; Point at start of Queue minus 1 1370 MVI PXQ_L0, R3 ; Get queue length 1371 1372@@notx: INCR R4 ; Where we branch when X didn't match. 1373@@noty: DECR R3 ; Decrement loop count 1374 BMI @@insert ; Get out of here when it expires. 1375@@rloop: 1376 CMP@ R4, R1 ; Compare X coords 1377 BNEQ @@notx ; X miscompared. 1378 1379 MOVR R2, R5 1380 XOR@ R4, R5 1381 ANDI #$1F, R5 ; Cmp Y coords (lower 5 bits of queue entry) 1382 BNEQ @@noty ; Y miscompared 1383 1384 ; If we get here, the coordinates match, so just update the color 1385 ; and exit the queue insertion process entirely. 1386 DECR R4 1387 B @@styc ; Store Y coordinate and color, and leave 1388 1389@@insert_eis: 1390 EIS 1391@@insert: 1392 ; If we get here, then this is not a duplicate pixel, so we need 1393 ; to extend the queue. Warning: No bounds checking is done here. 1394 MVI PXQ_L0, R3 1395 MOVR R3, R4 ; Copy the old queue length to R4 1396 ADDR R3, R4 ; Multiply it by 2 1397 INCR R3 1398 MVO R3, PXQ_L0 1399 ADDI #PXQ_XY, R4 ; Point R4 at pixel queue 1400 1401@@stx: MVO@ R1, R4 ; Store the X coordinate 1402@@styc: MVO@ R2, R4 ; Store the Y coordinate and color 1403 1404 ANDI #$1F, R2 ; Strip the color from the Y + color. 1405 1406 PULR PC ; Return. 1407 1408 ENDP 1409 1410;;==========================================================================;; 1411;; DRAINPXQ ;; 1412;; Drains the pixel queue (intended to be called from an ISR). ;; 1413;; ;; 1414;; INPUTS: ;; 1415;; R5 -- Return address ;; 1416;; Pixels in the committed pixel queue ;; 1417;; ;; 1418;; OUTPUTS: ;; 1419;; R0..R5 clobbered. ;; 1420;;==========================================================================;; 1421DRAINPXQ PROC 1422 PSHR R5 1423 DIS ; Critical section: Grab committed pixels. 1424 MVI PXQ_L1, R3 1425 TSTR R3 1426 BEQ @@leave 1427 CLRR R1 1428 MVO R1, PXQ_L0 ; Clear count of queued pixels. 1429 MVO R1, PXQ_L1 ; Clear count of comitted pixels. 1430 EIS 1431 MVII #PXQ_XY, R4 1432@@pixloop: 1433 MVI@ R4, R1 ; Get X coordinate 1434 MVI@ R4, R2 ; Get Y coordinate and color 1435 MOVR R2, R0 ; Separate Y coordinate and color 1436 SLL R0, 2 1437 SLL R0, 1 1438 SWAP R0 ; Color is now in 3 lsbs. 1439 ANDI #$7, R0 ; Mask away undesired bits in color 1440 ANDI #$1F, R2 ; Mask away undesired bits in Y coord. 1441 PSHR R3 1442 PSHR R4 1443 CALL PUTPIXEL ; Display the pixel 1444 PULR R4 1445 PULR R3 1446 DECR R3 ; Decrement our loop count 1447 BNEQ @@pixloop ; Keep looping until we've displayed them all 1448@@leave: 1449 EIS 1450 PULR PC 1451 ENDP 1452 1453;;==========================================================================;; 1454;; COMMITPXQ ;; 1455;; Commits queued pixels in the pixel queue to be displayed. ;; 1456;; ;; 1457;; INPUTS: ;; 1458;; R5 -- Return address ;; 1459;; Pixels in the pixel queue. ;; 1460;; ;; 1461;; OUTPUTS: ;; 1462;; Pixels are committed for display. ;; 1463;;==========================================================================;; 1464COMMITPXQ PROC 1465 PSHR R5 ; Save return address on stack. 1466 DIS ; Disable interrupts (critical section) 1467 MVI PXQ_L1, R5 ; See if there are already committed pixels 1468 TSTR R5 ; ... waiting to be drawn. (oops!) 1469 BNEQ @@leave ; Yes.. then leave. 1470 MVI PXQ_L0, R5 ; No? Get our count of non-committed pixels. 1471 MVO R5, PXQ_L1 ; Store it to our count of committed pixels. 1472@@leave: 1473 EIS ; Done with critical section. 1474 PULR PC ; Return to caller 1475 ENDP 1476 1477;;==========================================================================;; 1478;; PIECE ;; 1479;; The Piece Shape Table ;; 1480;; ;; 1481;; Shape encoding for the game pieces ;; 1482;; ;; 1483;; The game pieces are encoded as a series of moves from the "center ;; 1484;; point". Blocks are placed _after_ each move. All pieces are ;; 1485;; composed of four moves except piece 6 (the T) which requires 5 ;; 1486;; moves, and piece 5 which uses an extra move so that it is centered ;; 1487;; in the "next piece" box. ;; 1488;; ;; 1489;; Each move is encoded in two bits. The LS bit specifies the axis ;; 1490;; (up/down or left/right), and the MS bit specifies the sign of ;; 1491;; the direction on that axis (1==+1 and 0==-1). Moves are processed ;; 1492;; from the LS bit pair up to the MS bit pair. This encoding allows ;; 1493;; each piece to be described in exactly one decle. ;; 1494;; ;; 1495;; NOTE: The fifth move may NOT be "up". The rotation code ;; 1496;; distiguishes between four-move and five-move pieces by whether the ;; 1497;; upper two bits are zero. If they're both zero, it's a four-move ;; 1498;; piece, otherwise the two bits specify the fifth move. (Recall that ;; 1499;; 00 corresponds to "up".) ;; 1500;; ;; 1501;; Right now, rotations are performed in a brute-force manner: We ;; 1502;; just encode all of the rotations in our data set here. It's ;; 1503;; seems to be alot cheaper than writing the code to rotate pieces, ;; 1504;; although I can't be sure unless I do them both. At the very least, ;; 1505;; the brute-force approach is alot easier. ;; 1506;; ;; 1507;; Notation: pXrY is "Piece #X, Rotation #Y". X == 0..6, Y == 0..3. ;; 1508;; ;; 1509;; up#, dn#, lf# and rt# indicate a movement in a direction ;; 1510;; on a given move number. ;; 1511;; ;; 1512;; Pieces are shown in the comments in their initial ;; 1513;; orientation. The block marked with %% is the "pivot" ;; 1514;; block. ;; 1515;;==========================================================================;; 1516PIECE PROC 1517 1518; handy constants for defining the moves. 1519@@up0 EQU 0000000000b 1520@@dn0 EQU 0000000010b 1521@@lf0 EQU 0000000001b 1522@@rt0 EQU 0000000011b 1523 1524@@up1 EQU 0000000000b 1525@@dn1 EQU 0000001000b 1526@@lf1 EQU 0000000100b 1527@@rt1 EQU 0000001100b 1528 1529@@up2 EQU 0000000000b 1530@@dn2 EQU 0000100000b 1531@@lf2 EQU 0000010000b 1532@@rt2 EQU 0000110000b 1533 1534@@up3 EQU 0000000000b 1535@@dn3 EQU 0010000000b 1536@@lf3 EQU 0001000000b 1537@@rt3 EQU 0011000000b 1538 1539;;up4 not allowed 1540@@dn4 EQU 1000000000b 1541@@lf4 EQU 0100000000b 1542@@rt4 EQU 1100000000b 1543 1544; Piece 0: The Long Bar 1545; 1546; ### 1547; ### 1548; %%% 1549; %%% 1550; ### 1551; ### 1552; ### 1553; ### 1554; 1555@@p0r0 BYTE @@up0 + @@dn1 + @@dn2 + @@dn3 1556@@p0r1 BYTE @@lf0 + @@rt1 + @@rt2 + @@rt3 1557@@p0r2 BYTE @@up0 + @@dn1 + @@dn2 + @@dn3 1558@@p0r3 BYTE @@lf0 + @@rt1 + @@rt2 + @@rt3 1559 1560; Piece 1: The Square Block 1561; 1562; ###%%% 1563; ###%%% 1564; ###### 1565; ###### 1566; 1567@@p1r0 BYTE @@dn0 + @@lf1 + @@up2 + @@rt3 1568@@p1r1 BYTE @@dn0 + @@lf1 + @@up2 + @@rt3 1569@@p1r2 BYTE @@dn0 + @@lf1 + @@up2 + @@rt3 1570@@p1r3 BYTE @@dn0 + @@lf1 + @@up2 + @@rt3 1571 1572; Piece 2: The L shape 1573; 1574; ###%%%### 1575; ###%%%### 1576; ### 1577; ### 1578; 1579@@p2r0 BYTE @@rt0 + @@lf1 + @@lf2 + @@dn3 1580@@p2r1 BYTE @@dn0 + @@up1 + @@up2 + @@lf3 1581@@p2r2 BYTE @@lf0 + @@rt1 + @@rt2 + @@up3 1582@@p2r3 BYTE @@up0 + @@dn1 + @@dn2 + @@rt3 1583 1584; Piece 3: The Reverse-L shape 1585; 1586; ###%%%### 1587; ###%%%### 1588; ### 1589; ### 1590; 1591@@p3r0 BYTE @@lf0 + @@rt1 + @@rt2 + @@dn3 1592@@p3r1 BYTE @@up0 + @@dn1 + @@dn2 + @@lf3 1593@@p3r2 BYTE @@rt0 + @@lf1 + @@lf2 + @@up3 1594@@p3r3 BYTE @@dn0 + @@up1 + @@up2 + @@rt3 1595 1596; Piece 4: The S shape 1597; 1598; %%%### 1599; %%%### 1600; ###### 1601; ###### 1602; 1603@@p4r0 BYTE @@rt0 + @@lf1 + @@dn2 + @@lf3 1604@@p4r1 BYTE @@up0 + @@dn1 + @@rt2 + @@dn3 1605@@p4r2 BYTE @@rt0 + @@lf1 + @@dn2 + @@lf3 1606@@p4r3 BYTE @@up0 + @@dn1 + @@rt2 + @@dn3 1607 1608; Piece 5: The Z shape 1609; 1610; ###SSS Note: SSS brick is the actual starting brick. 1611; ###SSS 1612; %%%### 1613; %%%### 1614; 1615@@p5r0 DECLE @@dn0 + @@rt1 + @@lf2 + @@up3 + @@lf4 1616@@p5r1 DECLE @@dn0 + @@up1 + @@dn2 + @@lf3 + @@dn4 1617@@p5r2 DECLE @@dn0 + @@rt1 + @@lf2 + @@up3 + @@lf4 1618@@p5r3 DECLE @@dn0 + @@up1 + @@dn2 + @@lf3 + @@dn4 1619 1620; Piece 6: The T shape 1621; 1622; ###%%%### 1623; ###%%%### 1624; ### 1625; ### 1626; 1627@@p6r0 DECLE @@lf0 + @@rt1 + @@dn2 + @@up3 + @@rt4 1628@@p6r1 DECLE @@up0 + @@dn1 + @@lf2 + @@rt3 + @@dn4 1629@@p6r2 DECLE @@lf0 + @@rt1 + @@up2 + @@dn3 + @@rt4 1630@@p6r3 DECLE @@up0 + @@dn1 + @@rt2 + @@lf3 + @@dn4 1631 1632 ENDP 1633 1634 1635;;==========================================================================;; 1636;; PUTPIECEREC ;; 1637;; Writes a piece record to memory. ;; 1638;; ;; 1639;; PUTPIECEREC.NC ;; 1640;; Writes a piece record to memory, except color. ;; 1641;; ;; 1642;; INPUTS: ;; 1643;; R0 -- piece color ;; 1644;; R1 -- X coord of pivot ;; 1645;; R2 -- Y coord of pivot ;; 1646;; R3 -- piece number/rotation ;; 1647;; R4 -- Address of piece information record (+1 if PUTPIECEREC.NC) ;; 1648;; R5 -- Return address ;; 1649;; ;; 1650;; OUTPUTS: ;; 1651;; R0..R3, R5 -- not modified ;; 1652;; R4 -- points just past end of of piece information record ;; 1653;;==========================================================================;; 1654PUTPIECEREC PROC 1655 MVO@ R0, R4 ; Put piece color 1656@@NC: 1657 MVO@ R1, R4 ; Put piece pivot X coordinate 1658 MVO@ R2, R4 ; Put piece pivot Y coordinate 1659 MVO@ R3, R4 ; Put piece number/rotation 1660 1661 JR R5 ; Return 1662 ENDP 1663 1664;;==========================================================================;; 1665;; GETPIECEREC ;; 1666;; Reads a piece record from memory. ;; 1667;; ;; 1668;; GETPIECEREC.NC ;; 1669;; Reads a piece record from memory, except color. ;; 1670;; ;; 1671;; INPUTS: ;; 1672;; R4 -- Address of piece information record (+1 if GETPIECEREC.NC) ;; 1673;; R5 -- Return address ;; 1674;; ;; 1675;; OUTPUTS: ;; 1676;; R0 -- piece color (unless GETPIECEREC.NC) ;; 1677;; R1 -- X coord of pivot ;; 1678;; R2 -- Y coord of pivot ;; 1679;; R3 -- piece number/rotation ;; 1680;;==========================================================================;; 1681GETPIECEREC PROC 1682 MVI@ R4, R0 ; Get piece color 1683@@NC: 1684 MVI@ R4, R1 ; Get piece pivot X coordinate 1685 MVI@ R4, R2 ; Get piece pivot Y coordinate 1686 1687 ; Sign-extend R1 and R2 since they're in 8-bit RAM. 1688 MVII #$7F, R3 ; Generate constant $FF80 1689 COMR R3 ; R3 is "magic sign-extend constant" 1690 1691 ADDR R3, R1 ; Propogate sign-bit info to upper-half 1692 XORR R3, R1 ; R1 is fully sign-extended here. 1693 1694 ADDR R3, R2 ; Propogate sign-bit info to upper-half 1695 XORR R3, R2 ; R2 is fully sign-extended here. 1696 1697 MVI@ R4, R3 ; Get piece number/rotation 1698 1699 JR R5 ; Return 1700 ENDP 1701 1702;;==========================================================================;; 1703;; DRAWPIECE ;; 1704;; Draws a piece on the screen in the desired color (0..7). ;; 1705;; ;; 1706;; TESTPIECE ;; 1707;; Determines if a piece can be drawn in a given location by tracing its ;; 1708;; path and doing GETPIXELSC calls. ;; 1709;; ;; 1710;; INPUTS: ;; 1711;; R0 -- If DRAWPIECE, color of piece to draw (0..7) ;; 1712;; R1 -- X coordinate of pivot ;; 1713;; R2 -- Y coordinate of pivot ;; 1714;; R3 -- Piece number ;; 1715;; R5 -- Return address ;; 1716;; ;; 1717;; OUTPUTS: ;; 1718;; R0 -- If TESTPIECE, Zero == OK, non-zero == not OK; else, trashed. ;; 1719;; R4 -- Minimum Y coordinate ;; 1720;; R5 trashed. ;; 1721;; ;; 1722;; $130..$135 used for temporary storage. ;; 1723;;==========================================================================;; 1724DRAWPIECE PROC 1725@@colsv EQU $130 ; Piece color save area 1726@@pvxsv EQU $131 ; Pivot X coordinate save area 1727@@pvysv EQU $132 ; Pivot Y coordinate save area 1728@@pnrsv EQU $133 ; Piece number/rotation save area 1729@@pcwsv EQU $134 ; Loop count save area 1730@@cntsv EQU $135 ; Loop count save area 1731@@miny EQU $136 1732 1733 ADDR R0, R0 ; Shift color left by 1 1734 INCR R0 ; Set the LSB on the color 1735 INCR PC ; Skip the CLRR R0 1736TESTPIECE: 1737 CLRR R0 ; Start with color = 0, LSB = 0 1738 1739 PSHR R5 ; Save return address 1740 MVII #@@colsv, R4 ; Point R4 at save area 1741 CALL PUTPIECEREC 1742 1743 MVO R2, @@miny ; Start with Min Y == pivot point 1744 1745 ; Convert piece number to control word. 1746 ADDI #PIECE, R3 ; Point into PIECE array entry for piece 1747 MVI@ R3, R3 ; Get the piece control word 1748 1749 MVII #3, R4 ; At least four moves in piece 1750 1751@@loop: 1752 MOVR R3, R0 ; Copy Piece Control Word 1753 ANDI #2, R0 ; Get sign for move (set == +1, clear == -1) 1754 DECR R0 ; R0 is either +1/-1 according to move 1755 SARC R3, 2 ; Shift move away from PCW. Carry==axis 1756 BC @@xaxis ; Carry Clear == Y-Axis, Carry Set == X-Axis 1757 ADDR R0, R2 ; Add move to Y axis 1758 ; See if this Y coordinate is our new min-Y coord 1759 CMP @@miny, R2 1760 BGE @@cont 1761 MVO R2, @@miny 1762 1763 INCR PC ; Skip add-to-X (next instruction) 1764@@xaxis: 1765 ADDR R0, R1 ; Add move to X axis 1766@@cont: 1767 1768 ; Note, at most 8 bits remain in PCW here, so we save in 8-bit loc. 1769 MVO R3, @@pcwsv ; Save remaining Piece Control Word (max 8 bit) 1770 MVO R4, @@cntsv ; Save our loop trip count 1771 MVI @@colsv, R0 ; Get saved color 1772 SARC R0, 1 ; If bit-0 set, this is DRAW, else TEST 1773 BC @@draw ; 1774@@test: 1775 ; Note R0 should be 0 here. This treats off-screen pixels as black, 1776 ; which is what we want. 1777 CMPI #EDGEL, R1 ; Are we off left? 1778 BLT @@notok ; Yes: Bad! 1779 CMPI #EDGER, R1 ; Are we off left? 1780 BGT @@notok ; Yes: Bad! 1781 CALL GETPIXELSC ; Get pixel, with clipping. 1782 CMPI #7, R0 ; Ignore color 7, since we use that 1783 BEQ @@pixelok ; for the active piece. 1784 TSTR R0 ; Otherwise, make sure it's black. 1785 BEQ @@pixelok ; 1786 B @@notok ; Didn't pass the tests? It's not ok then. 1787@@draw: 1788 CALL PUTPIXELSCQ ; Put the pixel on the screen, with clipping. 1789@@pixelok: 1790 1791 MVI @@pcwsv, R3 ; Get our saved piece number 1792 MVI @@cntsv, R4 ; Get our loop trip count. 1793 DECR R4 1794 BPL @@loop ; Loop as long as R4 > 0 1795 1796 CLRR R4 1797 TSTR R3 ; See if this is a 5-move piece. 1798 BNEQ @@loop ; Loop one last time if it is. 1799 1800 MVI @@colsv, R0 ; Restore color register (if TESTPIECE is ok, 1801 SLR R0, 1 ; ...OR if this is DRAWPIECE). 1802 INCR PC ; (skip the MOVR.) 1803 1804@@notok: ; Else, skip the restore if TESTPIECE and 1805 MOVR PC, R0 ; ...pixel is not ok, so force R0 non-zero 1806 1807 MVII #@@pvxsv, R4 ; Point to save area + 1 1808 CALL GETPIECEREC.NC 1809 MVI @@miny, R4 ; Get minimum Y value into R4 1810 1811 PULR PC ; Return to the caller 1812 ENDP 1813 1814 1815;;==========================================================================;; 1816;; GETPIECECOLOR ;; 1817;; Gets Piece Color, given the piece number/rotation. ;; 1818;; ;; 1819;; INPUTS: ;; 1820;; R3 -- Piece number/rot. Bits 0..1 are rotation, Bits 2..4 are piece # ;; 1821;; R5 -- Return address ;; 1822;; ;; 1823;; OUTPUTS: ;; 1824;; R0 -- Piece color ;; 1825;;==========================================================================;; 1826GETPIECECOLOR PROC 1827 PSHR R3 ; Save R3 so that it's not clobbered 1828 SLR R3, 2 ; Generate index into color table 1829 ADDI #@@color,R3 ; R3 = PCCOLOR[piece] 1830 MVI@ R3, R0 ; Get the piece color into R0 1831 PULR R3 ; Restore R3 1832 JR R5 ; Return. 1833 1834@@color BYTE $2, $1, $3, $4, $5, $6, $1 1835 ENDP 1836 1837;;==========================================================================;; 1838;; PICKPIECE ;; 1839;; Grabs the 'next piece' and makes it the current piece. Then picks a ;; 1840;; new 'next piece'. ;; 1841;;==========================================================================;; 1842PICKPIECE PROC 1843 PSHR R5 1844@@chain: 1845@@randloop: 1846 MVII #4, R2 ; Generate some new random bits 1847 1848 CALL NEXTRANDX 1849 ANDI #$1C, R0 ; Mask to [0..7] * 4 1850 BEQ @@randloop ; If it comes up 0, try again 1851 1852 SUBI #4, R0 ; Make into piece #0..#6 1853 1854 CALL NEXTCLEAR ; Clear previous 'next-piece' 1855 MVI NXTPC, R3 ; Get previous 'next-piece'. 1856 MVO R0, NXTPC ; Save new 'next-piece'. 1857 1858 MVI SHOWNXT, R1 ; Are we showing next piece? 1859 MVO R1, DIDNXT ; Initialize our "showed next piece" flag 1860 TSTR R1 1861 BEQ @@noshow 1862 1863 PSHR R3 ; Save R3 -- new current piece number 1864 CALL NEXTSHOW ; Show next piece 1865 PULR R3 ; Restore R3 1866 1867@@noshow: 1868 CALL GETPIECECOLOR ; Get the current piece's color. 1869 MVO R0, CS1 ; Force color-stack to piece's color 1870 MVO R0, CS3 ; Force color-stack to piece's color 1871 1872 MVII #EDGEL+5, R1 ; Start out in middle of well... 1873 MVII #3, R2 ; ...just a little off top of screen 1874 COMR R2 ; (Y = -3) 1875 MVII #CURPC.C, R4 ; Write to "current piece" record. 1876 CALL PUTPIECEREC ; Make this the current piece. 1877 1878 PULR PC ; Return. 1879 ENDP 1880 1881;;==========================================================================;; 1882;; CLEARWELL ;; 1883;; Clears the well in a dramatic fashion ;; 1884;;==========================================================================;; 1885CLEARWELL PROC 1886@@bot EQU $1E2 1887 PSHR R5 1888 1889 CLRR R0 1890 MVO R0, CURPC.C ; make sure current piece number is cleared. 1891 1892@@wellloop: 1893 MVII #EDGEB, R3 1894 CMP HEIGHT, R3 ; R3 has actual well height. 1895 BLE @@return ; Do nothing if well is empty. 1896 1897@@randloop: 1898 MVII #5, R2 1899 CALL NEXTRANDX ; Get a random number (advance by 5 bits) 1900 ANDI #31, R0 1901 ADD HEIGHT, R0 1902 CMPR R3, R0 ; Make sure it's less than the well height. 1903 BGE @@randloop 1904 1905 MOVR R0, R2 1906 MVII #EDGEL+1, R1 1907 CALL GETPIXELS 1908 CMPI #7, R0 1909 BEQ @@nukeit 1910@@fillit: 1911 1912 CALL NUKELINE 1913 B @@wellloop 1914 1915@@nukeit: 1916 MVO R2, @@bot 1917 1918 CALL WAIT ; Wait a few ticks 1919 DECLE 2 ; 1920 1921 MVII #@@wellloop, R5 1922 PSHR R5 1923 B DOSCORE.nukeit ; Nuke the line, collapsing the well. 1924 ; This will return to @@wellloop 1925 1926@@return: 1927 PULR PC ; Return. 1928 ENDP 1929 1930;;==========================================================================;; 1931;; SCOREDROP ;; 1932;; Loops over a four line window and marks rows for deletion. ;; 1933;; Returns line-clearing score in R0 and leaves lines to be cleared set ;; 1934;; to color #7. ;; 1935;; ;; 1936;; NUKELINE ;; 1937;; Force-clears a line as if it were full. ;; 1938;; ;; 1939;; INPUTS: ;; 1940;; R2 == Row number to start at. ;; 1941;; ;; 1942;; OUTPUTS: ;; 1943;; R0 = Number of lines cleared. ;; 1944;; R2 = original value plus 4 ;; 1945;; R3 = Number of lines cleared. ;; 1946;; R1, R4, R5 trashed ;; 1947;; ;; 1948;; $1E0..$1E3 used as temp storage. ;; 1949;;==========================================================================;; 1950SCOREDROP PROC 1951@@trip EQU $1E2 1952@@full EQU $1E3 1953 1954 PSHR R5 ; Save return address 1955 CLRR R3 ; Clear count of lines to score 1956 MVO R3, @@full 1957 MVII #3, R4 ; Loop trip count 1958 1959 B @@doscore 1960NUKELINE: 1961 PSHR R5 1962 CLRR R4 1963 MVO R4, @@full 1964 MVO R4, @@trip 1965 B @@nuke 1966 1967@@doscore: 1968 MVO R4, @@trip 1969 1970 MVII #EDGEL, R1 ; Iterate over columns 15..24 1971@@scoreloop: 1972 CALL GETPIXELS ; Read a pixel 1973 TSTR R0 ; Is it zero? 1974 BEQ @@sloop ; If so, stop -- line's not full. 1975 1976 INCR R1 1977 CMPI #EDGER, R1 ; At the end of the well? 1978 BLE @@scoreloop ; If not, keep looping 1979 1980@@nuke: 1981 MVI @@full, R3 1982 INCR R3 1983 MVO R3, @@full 1984 1985 ;; Prepare to set entire row in well to color 7 1986 MVII #EDGEL, R1 ; Start at left edge of well 1987 MVII #7, R0 ; Color = 7 1988@@floop: 1989 CALL PUTPIXELS ; Draw the pixel 1990 INCR R1 ; Move to next column 1991 CMPI #EDGER, R1 ; Are we at the right edge? 1992 BLE @@floop ; No: Keep looping. 1993 1994@@sloop: 1995 MVI @@trip, R4 1996 INCR R2 ; Go to next line 1997 DECR R4 ; Decrement loop count 1998 BPL @@doscore ; Keep going as long as R4 >= 0 1999 2000@@sdone: ; Scoring complete 2001 MVI @@full, R3 2002 2003 ;; If this was a 4-line clear, play the special sound effect 2004 CMPI #4, R3 2005 BNEQ @@return 2006 MVII #2, R1 ; High priority sound effect 2007 CALL PLAY.sfxp 2008 DECLE FXBOMB4 2009@@return: 2010 MOVR R3, R0 ; Return number of cleared lines. 2011 PULR PC ; Return to caller. 2012 2013 ENDP 2014 2015;;==========================================================================;; 2016;; DOSCORE ;; 2017;; Calls SCOREDROP to score a dropped piece and mark lines for deletion. ;; 2018;; ;; 2019;; Calculates the score based on number of downward moves the player ;; 2020;; made, as well as the number of lines cleared. ;; 2021;; ;; 2022;; Collapses lines that have been marked for deletion by the scoring ;; 2023;; routines. Processes the four lines ABOVE R2. (This works pretty well, ;; 2024;; since the scoring routines leave R2 pointing to the line AFTER the four ;; 2025;; lines that were scored.) Lines are scanned in the leftmost column, ;; 2026;; and lines containing a pixel of color 7 in this column are cleared. ;; 2027;; ;; 2028;; INPUTS: ;; 2029;; R2 == Line at top of 4-line window to score ;; 2030;; ;; 2031;; OUTPUTS: ;; 2032;; R0, R1, R2, R4, R5 trashed? ;; 2033;; ;; 2034;; Locations $1E0..$1E3, $1E6 used as temporary storage. ;; 2035;;==========================================================================;; 2036SCORETABLE DECLE 500, 1500, 3000, 6000 2037DOSCORE PROC 2038@@bot EQU $1E2 2039@@top EQU $1E3 2040@@clr EQU $1E6 2041 2042 PSHR R5 2043 2044 2045 MVII #EDGEB-4, R3 ; Make sure we're not too close to 2046 CMPR R3, R2 ; the bottom of the well. 2047 BLE @@rowok 2048 MOVR R3, R2 ; If we are, move our window up some. 2049@@rowok: 2050 PSHR R2 2051 2052 ; First, add in any "move points" 2053 MVI MSCORE, R0 2054 2055 2056 MVI PSCORL, R1 ; Add this to our score 2057 MVI PSCORH, R4 2058 ADDR R0, R1 ; Add moves to lower half of 32-bit score 2059 ADCR R4 ; Propogate carry from lower to upper half 2060 MVO R1, PSCORL 2061 MVO R4, PSCORH 2062 2063 CLRR R2 2064 MVO R2, MSCORE ; Clear the total. 2065 2066 ; Wait one tick to make sure any queued pixels have been drawn. 2067 ; This works because the ISR always drains the committed pixel 2068 ; queue completely, and WAIT busywaits on a counter maintained 2069 ; by the ISR. Cool, eh? 2070 CALL WAIT 2071 DECLE 1 2072 2073 PULR R2 2074 ; Next, see if we need to clear any lines due to this drop 2075 CALL SCOREDROP ; Score the drop. 2076 2077 MOVR R0, R5 ; If we didn't score anything, return 2078 BEQ @@done ; (chain return through PICKPIECE) 2079@@hmm 2080 ADDI #SCORETABLE-1, R5 ; R5 = &scoretable[lines] 2081 2082 MVI@ R5, R0 ; Load base score for this # of lines 2083 2084 MVI LEVEL, R3 2085 2086 MVI PSCORL, R1 ; Otherwise, add this to our score 2087 MVI PSCORH, R4 2088@@scoreloop: 2089 ADDR R0, R1 ; (multiply by level thru repeated addition) 2090 ADCR R4 2091 2092 DECR R3 2093 BPL @@scoreloop ; Go around 'level + 1' times. 2094 2095 MVO R1, PSCORL 2096 MVO R4, PSCORH 2097 2098@@notnuke: 2099 SUBI #4, R2 ; Find top of window 2100 MVO R2, @@top ; Remember top row 2101 ADDI #3, R2 ; Move to bottom of window 2102 2103 CALL SPAWN ; Fork the line clearing as new task. 2104 DECLE 13, 3 ; Period == 6 ticks (10Hz), task #3 2105 2106 ;;--------------------------------------------------------------------;; 2107 ;; NOTE: THIS IS A NEW TASK HERE!!! IT IS CALLED FOR EACH LINE!!! ;; 2108 ;; We do this to avoid having tasks pile up in the queue while we ;; 2109 ;; do our work. Things such as score updates (most common) tend to ;; 2110 ;; pile up in the queue otherwise. ;; 2111 ;;--------------------------------------------------------------------;; 2112 2113@@scan: 2114 MVII #EDGEL, R1 ; Scan along left edge 2115 MVO R2, @@bot ; Remember current y position 2116 CALL GETPIXEL ; Grab a pixel. 2117 MVI @@bot, R2 ; Restore row position 2118 MVI @@top, R1 ; Restore top of window 2119 CMPI #7, R0 ; Is this pixel color 7? 2120 BEQ @@collapseok ; It is: go collapse this row. 2121 2122 DECR R2 ; Move up a row on the screen 2123 CMPR R1, R2 ; Are we out of our window? 2124 BGE @@scan ; If not, keep scanning 2125 2126@@done: 2127 CALL SLEEP 2128 DECLE 31, 3 ; Sleep for 1/4th second after drop. 2129 PULR R5 2130 B PICKPIECE ; Chain return through PICKPIECE 2131 2132@@collapseok: 2133 2134 ;; When we collapse a line, the top of our scanning window 2135 ;; actually moves down a row, and our scan position does NOT move 2136 ;; up a row. So, look at the scan row, and increment the top 2137 ;; row value. 2138 INCR R1 ; Move top down one row 2139 MVO R1, @@top ; Store new top of window 2140 2141 ;; Increment our "cleared lines" count and queue up the "show lines" 2142 ;; task. 2143 MVI LINES, R1 2144 INCR R1 2145 MVO R1, LINES 2146 2147 MVII #UPDATESTATS, R0 2148 JSRD R5, QTASK 2149 2150 ;; We spawned as a one-shot task. We retrigger only after we're 2151 ;; sure we might need it, so that we don't get over-triggered. 2152 ;; (We will get triggered up to 5 times, with the fifth trigger 2153 ;; doing the call to PICKPIECE.) 2154 MVII #3, R3 2155 CALL RETRIGGERTASK 2156 2157@@nukeit: 2158 MVII #1, R1 2159 CALL PLAY.sfxp ; Trigger the collapse soundeffect. 2160 DECLE FXBOMB 2161 2162@@nosfx: 2163 MVII #EDGEL, R1 ; Start at left edge. 2164 2165@@xloop: 2166 MVI HEIGHT, R2 ; Start at top of screen 2167 MVO R1, XSAVE ; Save our current column 2168 CLRR R4 ; Propogate a black pixel from top 2169 2170@@yloop: 2171 MVO R4, @@clr ; Remember prev row's color 2172 MVO R2, YSAVE ; Save our current row 2173 CALL PIXCALC ; Calculate mask/addr for this pixel 2174 MOVR R1, R4 ; Remember mask 2175 CALL GETPIXELR ; Get color to copy to next row 2176 MOVR R4, R1 ; Restore mask 2177 MOVR R0, R4 ; Remember color for next row 2178 MVI @@clr, R0 ; Restore previous rows color 2179 CALL PUTPIXELR ; Draw prev rows color in this row 2180 MVI YSAVE, R2 ; Restore row position 2181 MVI XSAVE, R1 ; Restore column position 2182 INCR R2 ; Move down one row 2183 CMP @@bot, R2 ; Are we at the last row? 2184 BLE @@yloop ; No? Keep looping. 2185 2186 INCR R1 ; Move to next column 2187 CMPI #EDGER, R1 ; Are we at the right edge yet? 2188 BLE @@xloop ; No? Keep looping. 2189 2190 ; Decrement one from our well height here, so that music speed 2191 ; changes after we collapse a row, and our scan window changes as 2192 ; well for speed. 2193 MVI HEIGHT, R1 ; Decrement the well height now. 2194 INCR R1 ; (Note: 'HEIGHT' is actually 2195 MVO R1, HEIGHT ; EDGEB - height. Hence the INCR.) 2196 2197 PULR PC ; Return... we will be retriggered 2198 ; if there are more lines to scan. 2199 ENDP 2200 2201 2202;;==========================================================================;; 2203;; UPDATESCORE ;; 2204;; Shows current score, by incrementally updating a displayed score value. ;; 2205;;==========================================================================;; 2206UPDATESCORE PROC 2207 MVII #PSCORL,R4 ; Point to player/displayed score 2208 MVI@ R4, R2 ; Get lo 16 of player's actual score 2209 MVI@ R4, R3 ; Get hi 16 of player's actual score 2210 MVI@ R4, R0 ; Get lo 16 of displayed score 2211 MVI@ R4, R1 ; Get hi 16 of displayed score 2212 CMPR R3, R1 ; Compare upper halves. 2213 BEQ @@next ; Displayed = Actual: Test lower halves 2214 BNC @@doincr ; Displayed < Actual: Do the increment 2215 BNEQ @@uhoh ; Displayed > Actual? UHOH! 2216@@next: 2217 CMPR R2, R0 ; Compare lower halves. 2218 BEQ @@return ; Displayed = Actual: Do nothing. 2219 BNC @@doincr ; Displayed < Actual: Do the increment 2220 ; Displayed > Actual? UHOH! 2221@@uhoh: 2222 ; If we get here, we accidentally overshot the player's actual 2223 ; score somehow. Compensate by setting the displayed score to the 2224 ; actual score. :-P (Note: We also use this code to force a 2225 ; redisplay of the score on purpose by setting the displayed 2226 ; score to 0xFFFFFFFF, which is always higher than the player's 2227 ; score (or is it? How good are YOU?)). 2228 MOVR R2, R0 ; Set lo half of displayed to actual 2229 MOVR R3, R1 ; Set hi half of displayed to actual 2230 B @@disp ; Display the fixed score. 2231 2232@@return: 2233 JR R5 2234 2235@@doincr: 2236 ; First, find the difference between the displayed and actual 2237 ; scores. This is a 32-bit subtract. 2238 SUBR R0, R2 ; Subtract the lo halves. 2239 ADCR R3 ; Subtract the borrow from the hi half 2240 DECR R3 ; as a "not borrow" 2241 SUBR R1, R3 ; Subtract the hi halves 2242 2243 ; Divide the difference by 8, rounding upwards. 2244 ; This is effectively "num = (num + 7) / 8". It should 2245 ; produce a non-zero number if num starts out non-zero. 2246 ADDI #7, R2 ; Add rounding factor 2247 ADCR R3 ; ... and carry into upper half 2248 2249 ; Unsigned right shift by 3 2250 CLRC 2251 RRC R3, 1 ; Shift the first bit. Can't SARC (sign bit) 2252 RRC R2, 1 ; 2253 SARC R3, 2 ; SARC is safe now: sign bit is for sure 0 2254 RRC R2, 2 ; 2255 2256; TSTR R2 2257; BNEQ @@addit 2258; TSTR R3 2259; BNEQ @@addit 2260; HLT ; THIS SHOULD NEVER HAPPEN 2261; INCR R2 2262@@addit: 2263 ADDR R2, R0 ; Add the new increment's lo half 2264 ADCR R1 ; Propogate the carry 2265 ADDR R3, R1 ; Add the new increment's hi half 2266@@disp: 2267 MVO R0, DSCORL ; Store the updated display value (lo) 2268 MVO R1, DSCORH ; Store the updated display value (hi) 2269 2270 MVI SCRDIG, R2 2271 MVI SCRCOL, R3 2272 MVI SCRPOS, R4 2273 B DEC32B ; Display with leading zeros. 2274 ; (Chained return) 2275 2276 ENDP 2277 2278;;==========================================================================;; 2279;; UPDATESTATS ;; 2280;; Updates game information such as # of lines, level, and drop speed. ;; 2281;;==========================================================================;; 2282UPDATESTATS PROC 2283 PSHR R5 2284 MVI SCRCOL, R3 ; Get score color 2285 2286 MVI LINES, R0 ; Get current number of lines 2287 2288 MVI LEVEL, R1 ; Get current level 2289 TSTR R1 2290 BEQ @@initial ; First time through, force an update. 2291 2292 ADDR R1, R1 ; R1 = 2 * (level) 2293 MOVR R1, R2 2294 SLL R2, 2 ; R2 = 8 * (level) 2295 ADDR R2, R1 ; R1 = 10 * (level) 2296 2297 CMPR R1, R0 ; See if we've cleared enough lines to go up 2298 ; a level. 2299 BLT @@samelevel ; If not new level, just update line counter. 2300 2301 MVII #3, R1 ; Sound effect priority == 3 2302 CALL PLAY.sfxp ; 2303 DECLE FXDING3 ; Ding to signify "Next Level". 2304 ;B @@nextlevel 2305 2306@@nextlevel: 2307 MVI LEVEL, R2 ; Get current level number 2308 INCR R2 ; Update our level number 2309 MVO R2, LEVEL ; Store the new level number 2310 2311 B @@skipinitial 2312 2313@@initial: 2314 MVI SLEVEL, R2 ; Get starting level 2315 MVO R2, LEVEL ; Store it to our level number 2316 2317@@skipinitial: 2318 2319 MVII #20, R4 ; See if level > 20. If so, clamp to that 2320 CMPR R4, R2 ; for purposes of setting speed. 2321 BLE @@speedok 2322 MOVR R4, R2 2323@@speedok: 2324 ADDI #SPDTBL+1, R2 ; Point R2 into Speed Table 2325 MVI@ R2, R2 2326 MVO R2, SPEED ; Set piece speed from speed table 2327 2328 MVII #LVLLOC + 20, R4 2329 MVII #2, R2 2330 MVI LEVEL, R0 ; Increment the level number 2331 CALL DEC16A ; Display the updated level number 2332 2333 MVI LINES, R0 2334@@samelevel: 2335 MVII #LINLOC + 20, R4 2336 MVII #2, R2 2337 PULR R5 2338 B DEC16A 2339 ENDP 2340 2341;;==========================================================================;; 2342;; SPDTBL ;; 2343;; Speed table. Contains rates at which pieces drop for all the levels. ;; 2344;; Rates are all in "Ticks * 2". ;; 2345;;==========================================================================;; 2346SPDTBL PROC 2347 DECLE 160 + 0 ; Level 1 2348 DECLE 120 + 0 ; Level 2 2349 DECLE 100 + 0 ; Level 3 2350 DECLE 80 + 0 ; Level 4 2351 DECLE 60 + 0 ; Level 5 2352 DECLE 50 + 0 ; Level 6 2353 DECLE 40 + 0 ; Level 7 2354 DECLE 34 + 0 ; Level 8 2355 DECLE 28 + 0 ; Level 9 2356 DECLE 24 + 0 ; Level 10 2357 DECLE 20 + 0 ; Level 11 2358 DECLE 18 + 0 ; Level 12 2359 DECLE 16 + 0 ; Level 13 2360 DECLE 14 + 0 ; Level 14 2361 DECLE 12 + 0 ; Level 15 2362 DECLE 10 + 0 ; Level 16 2363 DECLE 8 + 0 ; Level 17 2364 DECLE 6 + 0 ; Level 18 2365 DECLE 4 + 0 ; Level 19 2366 DECLE 2 + 0 ; Level 20 and beyond 2367 ENDP 2368 2369;;==========================================================================;; 2370;; MARQUEE ;; 2371;; Cycles colors on a string of text. ;; 2372;; ;; 2373;; INPUTS: ;; 2374;; R2 -- Task instance data: ;; 2375;; Low byte of instance data gives screen offset. ;; 2376;; High byte of instance data gives length. ;; 2377;;==========================================================================;; 2378MARQUEE PROC 2379 2380 MOVR R2, R3 2381 ANDI #$FF, R3 ; R3 == screen offset. 2382 XORR R3, R2 2383 SWAP R2 ; R2 == length. 2384 ADDI #$200, R3 ; Convert screen offset into actual address. 2385 2386 MVI@ R3, R0 ; Get first display word from string. 2387 MVII #$7, R1 ; Color mask for three LSBs. 2388 ANDR R1, R0 ; Get color from leading character in string. 2389 COMR R1 ; Invert our mask, so we can replace colors. 2390@@loop: 2391 DECR R0 ; Cycle color by decrementing 2392 BNEQ @@ok ; Make sure it didn't go to zero! 2393 MVII #$7, R0 ; If it did, set it back to 7. 2394@@ok: 2395 MVI@ R3, R4 ; Get a character from the display. 2396 ANDR R1, R4 ; Mask away its color bits. 2397 ADDR R0, R4 ; Replace them with our new color bits. 2398 MVO@ R4, R3 ; Write the modified character to the display. 2399 INCR R3 ; Move to next character. 2400 DECR R2 ; Loop until done. 2401 BNEQ @@loop 2402 2403 JR R5 ; Return. 2404 ENDP 2405 2406;;==========================================================================;; 2407;; ANIDROP ;; 2408;; Color cycles CS1 and CS3 so that collapse lines "flash", or sets a ;; 2409;; solid color while piece is active. ;; 2410;;==========================================================================;; 2411ANIDROP PROC 2412 MVI CURPC.C, R2 ; Is there a current piece active? 2413 TSTR R2 2414 BNEQ @@active 2415 MVI CS1, R2 ; No? Just flash colors then. 2416 INCR R2 2417@@active: 2418 MVO R2, CS1 ; Set up colorstack #1 and #3 2419 MVO R2, CS3 2420 JR R5 ; Return. 2421 ENDP 2422 2423;;==========================================================================;; 2424;; MUTETOGGLE ;; 2425;; Toggles MUTE state ;; 2426;;==========================================================================;; 2427MUTETOGGLE PROC 2428 DIS 2429 MVI MUTE, R0 ; Get current mute status 2430 TSTR R0 ; If zero, we're muted. 2431 BEQ @@unmute 2432 ; Mute the music by zeroing its volume registers. 2433 CLRR R0 2434 MVI SNDSTRM+3,R1 ; Get current stream mask 2435 SLL R1, 2 ; Move volume-register bits to top 2436 2437 SLLC R1 ; If channel C's vol control is enabled .... 2438 BNC @@notc 2439 MVO R0, PSG0+13 ; ... clear it 2440@@notc: 2441 SLLC R1 ; If channel B's vol control is enabled .... 2442 BNC @@notb 2443 MVO R0, PSG0+12 ; ... clear it 2444 2445@@notb: 2446 SLLC R1 ; If channel A's vol control is enabled .... 2447 BNC @@nota 2448 MVO R0, PSG0+11 ; ... clear it 2449 2450@@nota: 2451 INCR PC ; skip INCR R0. 2452@@unmute: 2453 INCR R0 ; Un-mute the audio 2454 2455 MVO R0, MUTE ; Store the new mute status 2456 EIS 2457 JR R5 2458 ENDP 2459 2460 2461;;==========================================================================;; 2462;; ROTPIECE_CW ;; 2463;; Rotates a piece clockwise by updating the two LSBs of its piece ;; 2464;; index #. ;; 2465;; ;; 2466;; ROTPIECE_CCW ;; 2467;; Rotates a piece counter clockwise by updating the two LSBs of its ;; 2468;; piece index #. ;; 2469;; ;; 2470;; INPUTS: ;; 2471;; R3 -- Piece number/rotation ;; 2472;; R5 -- Return address ;; 2473;; ;; 2474;; OUTPUTS ;; 2475;; R3 -- New piece/rotation ;; 2476;; R0 -- Trashed ;; 2477;;==========================================================================;; 2478ROTPIECE_CW: PROC 2479 SETC ; Flag that this is a clockwise rotate 2480 INCR PC ; (skip the CLRC) 2481ROTPIECE_CCW: 2482 CLRC ; Flag that this is a counter-clockwise rotate 2483 MOVR R3, R0 ; Copy piece number/rotation to R0 2484 ADCR PC ; If counter-clockwise, complement R0, 2485 COMR R0 ; ... else don't. 2486 SETC 2487 RLC R0, 1 2488 ANDI #3, R0 2489 XORR R0, R3 ; Magic! 2490 JR R5 2491 ENDP 2492 2493;;==========================================================================;; 2494;; GRAVITY ;; 2495;; Makes the pieces fall! ;; 2496;; ;; 2497;; MVPIECE ;; 2498;; Moves/Rotates a piece around on the screen. ;; 2499;; ;; 2500;; INPUTS: ;; 2501;; R2 -- Event number: ;; 2502;; 13: Disc Left Move piece left ;; 2503;; 14: Disc Right Move piece right ;; 2504;; 15: Disc Down Move piece down ;; 2505;; 16: Action Top Rotate CCW ;; 2506;; 17: Action Left Rotate CW ;; 2507;; 18: Action Right Rotate CW ;; 2508;; ;; 2509;; THIS CODE SUCKS. ;; 2510;; (Although, it sucks less than the original code that I had here.) ;; 2511;;==========================================================================;; 2512GRAVITY PROC 2513 MVII #15, R2 ; Event #15 is 'down' 2514 CLRR R0 ; Flag that this is NOT a hand-ctrl input 2515 INCR PC ; (skip 'MOVR') 2516MVPIECE: 2517 MOVR R2, R0 ; Flag that this IS a hand-ctrl input 2518 MVO R0, WASHAND ; Store 'WASHAND' flag. 2519 2520 PSHR R5 ; Save return address 2521 PSHR R2 ; Save keypad # 2522 2523 MVII #CURPC.C, R4 2524 CALL GETPIECEREC ; Load current piece's record into R0..R3 2525 2526 PULR R4 ; Get keypad # into R4 2527 2528 TSTR R0 ; Is there an active piece? 2529 BEQ @@return ; Do nothing if no piece is active. 2530 2531 MVII #@@check, R5 ; Set up "return address" 2532 2533 MVO R4, WASDOWN ; Default "WAS DOWN" to "NOT" 2534 2535 SUBI #13, R4 ; Scale range down to 0..6, check for R4==13 2536 BMI @@return ; Bogus input R4 < 13 (should never happen) 2537 BNEQ @@not_left ; If taken, R4 != 13 2538 2539 DECR R1 ; Move left 2540 JR R5 ; Jump to '@@check' 2541 2542@@not_left: 2543 SUBI #2, R4 ; Check for R4==14 or R4==15 2544 BMI @@is_right ; Bogus input R4 == 13 (should never happen) 2545 BNEQ @@not_down ; If taken, R4 != 14 2546 2547 MVO R4, WASDOWN ; Set 'WASDOWN' to 'Yes, it was DOWN'. 2548 INCR R2 ; Move down one 2549 JR R5 ; Jump to '@@check' 2550 2551@@is_right: 2552 INCR R1 ; Move right 2553 JR R5 ; Jump to '@@check' 2554 2555@@not_down: 2556 DECR R4 ; Check for R4==16 or R4 > 16 2557 ; Note: Both of these branches chain return through ROTPIECE_xxx 2558 BEQ ROTPIECE_CCW ; If taken, R4==16 (top action button) 2559 B ROTPIECE_CW ; If taken, R4>=17 (bottom action buttons) 2560 2561@@check: 2562 ;; All movement paths lead to here. At this point, R0..R3 2563 ;; contain the proposed piece state (rotation, X, Y) 2564 2565 CALL TESTPIECE ; Is the piece placeable here? 2566 TSTR R0 2567 BNEQ @@cantmove ; If we couldn't move the piece, leave. 2568 2569 2570 ; Prepare to move the piece. 2571 CALL GETPIECECOLOR ; Get the piece's color. 2572 MVO R0, CURPC.C ; Set piece's color in its record. 2573 MVO R0, CS1 ; Force color-stack to piece's color 2574 MVO R0, CS3 ; Force color-stack to piece's color 2575 2576 MVII #TMPPC.X, R4 2577 CALL PUTPIECEREC.NC ; Save updated piece in temp storage 2578 CALL GETPIECEREC ; Restore original piece info 2579 CLRR R0 ; Force color to 0 for erase. 2580 CALL DRAWPIECE ; Erase previous piece 2581 2582 MVII #TMPPC.X, R4 2583 CALL GETPIECEREC.NC ; Restore updated piece info 2584 INCR R4 2585 MVII #7, R0 ; Always draw in color 7 here! 2586 CALL PUTPIECEREC.NC ; Store it to the current piece info 2587 2588 CALL DRAWPIECE ; Draw the updated piece on the screen 2589 2590 ; Bip and increment the per-move total 2591 MVI WASHAND, R0 ; Was this a hand-controller input? 2592 TSTR R0 2593 BEQ @@nothand ; No... don't bip, and don't add to MSCORE 2594 2595 CALL PLAY.sfx ; Yes... BIP! 2596 DECLE FXBIP 2597 2598 MVI WASDOWN, R0 ; If this was a hand movement that pushed 2599 TSTR R0 ; down, increment the MSCORE. 2600 BNEQ @@notdown ; No... don't increment MSCORE 2601 2602 MVI MSCORE, R0 2603 ADDI #MOVEINC,R0 ; Yes... increment MSCORE! 2604 MVO R0, MSCORE 2605@@notdown: 2606@@nothand: 2607 2608 PULR R5 ; Chain Return via COMMITPXQ 2609 B COMMITPXQ ; Commit the pixels to be drawn to screen. 2610 2611@@cantmove: 2612 MVI WASDOWN, R0 ; Was this a downward move? 2613 TSTR R0 2614 BNEQ @@return ; No? Just return then. 2615 PULR R5 2616 B PLACEPIECE ; Yes? Place the piece (w/ chained return) 2617 2618@@return: 2619 PULR PC ; Return 2620 ENDP 2621 2622;;==========================================================================;; 2623;; NEXTSHOW ;; 2624;; Shows the currently displayed piece in the next-piece box. ;; 2625;; ;; 2626;; NEXTCLEAR ;; 2627;; Clears the currently displayed piece in the next-piece box. ;; 2628;; ;; 2629;; INPUTS: ;; 2630;; Location 'NXTPC' holds current next piece. ;; 2631;; R5 -- return address. ;; 2632;; ;; 2633;; OUTPUTS: ;; 2634;; R0 -- saved ;; 2635;; R1 -- NXTPCX ;; 2636;; R2 -- NXTPCY ;; 2637;; R3 -- Next piece's piece number/rotation ;; 2638;; R4, R5 -- trashed ;; 2639;;==========================================================================;; 2640NEXTSHOW PROC 2641 MVO PC, DIDNXT ; Set the "We showed next piece" flag. 2642 ; (Note: PC guaranteed to be non-zero) 2643 SETC ; Set the "This is NEXTSHOW" flag. 2644 INCR PC ; Skip the CLRC. 2645NEXTCLEAR: 2646 CLRC ; Set the "This is NEXTCLEAR" flag. 2647 PSHR R5 ; Save our return address. 2648 PSHR R0 ; Save R0. 2649 MVI NXTPC, R3 ; Get the next piece's piece number/rotation 2650 BNC @@clear ; If carry cleared, set color to 0. 2651 CALL GETPIECECOLOR ; Otherwise, get the piece's color. 2652 INCR PC ; Skip the CLRR. 2653@@clear: 2654 CLRR R0 ; Clear the piece's color. 2655 MVII #NXTPCX, R1 ; Position ourself in the next-piece box. 2656 MVII #NXTPCY, R2 2657 CALL DRAWPIECE 2658 2659 PULR R0 ; Restore R0. 2660 PULR R5 ; Chain return via COMMITPXQ 2661 B COMMITPXQ ; Commit the pixels for display immediately. 2662 ENDP 2663 2664 2665;;==========================================================================;; 2666;; NEXTTOGGLE ;; 2667;; Toggles whether next-piece is shown. ;; 2668;;==========================================================================;; 2669NEXTTOGGLE PROC 2670 MVI SHOWNXT, R1 ; Are we currently showing next piece? 2671 TSTR R1 2672 BEQ @@turnon ; No... then start showing it. 2673@@turnoff: 2674 CLRR R1 ; Yes... turn it off! 2675 MVO R1, SHOWNXT 2676 B NEXTCLEAR ; Chain return through NEXTCLEAR 2677@@turnon: 2678 INCR R1 2679 MVO R1, SHOWNXT 2680 B NEXTSHOW ; Chain return through NEXTSET 2681 ENDP 2682 2683 2684;;==========================================================================;; 2685;; PLACEPIECE ;; 2686;; Make piece permanent where it is at. ;; 2687;;==========================================================================;; 2688PLACEPIECE PROC 2689 DIS 2690 PSHR R5 2691 MVII #CURPC.C, R4 ; Write to "current piece" record. 2692 CALL GETPIECEREC ; Make this the current piece. 2693 EIS 2694 2695 TSTR R0 ; Make sure we actually had an active piece 2696 BNEQ @@active 2697 PULR PC ; Not active? Just return! 2698@@active: 2699 2700 CALL DRAWPIECE ; Draw it. 2701 CALL COMMITPXQ ; Commit the pixels. 2702 CALL WAIT ; Wait one tick, so pixels are committed. 2703 DECLE 1 2704 2705 CLRR R5 ; Mark our current piece as inactive. 2706 MVO R5, CURPC.C ; It will get reactivated by DOSCORE. 2707 ; We do this after the 'wait' so that we're 2708 ; sure our queued pixels are drawn. 2709 2710 CMPI #EDGEB, R4 ; Is this piece off-screen? 2711 BLE @@notgameover ; No... keep going 2712 CALL NEXTCLEAR 2713 PULR R5 2714 CLRR R0 2715 MVO R0, HEIGHT 2716 B SCHEDEXIT ; Yes... game over! 2717 2718@@notgameover: 2719 CMP HEIGHT, R4 ; Did this piece form the new top-of-well? 2720 BGE @@nottop 2721 MVO R4, HEIGHT 2722 2723@@nottop: 2724 MVI DIDNXT, R0 ; Was 'next piece' displayed during this 2725 TSTR R0 ; piece? If not, double MSCORE 2726 BNEQ @@didnext 2727 MVI MSCORE, R0 2728 ADDR R0, R0 ; double the MSCORE 2729 MVO R0, MSCORE 2730@@didnext: 2731 2732 PSHR R4 2733 CALL PLAY.sfx 2734 DECLE FXBYUMP 2735 2736 PULR R1 ; Make row # the task-instance data 2737 MVII #DOSCORE, R0 ; Queue the scoring task. 2738 PULR R5 2739 JD QTASK ; Queue and chain return 2740 ENDP 2741 2742 2743;;==========================================================================;; 2744;; TITLESCREEN ;; 2745;; Draws the title screen and waits for user input. ;; 2746;;==========================================================================;; 2747TITLESCREEN PROC 2748 PSHR R5 2749 2750 CLRR R0 2751 MVO R0, CS0 ; Set color stack to BLACK 2752 MVO R0, CS1 2753 MVO R0, CS2 2754 MVO R0, CS3 2755 MVO R0, CB ; Set display border to BLACK 2756 2757 MVII #$F0, R1 ; Set entire display to BLACK 2758 MVII #$200, R4 2759 CALL FILLMEM 2760 2761 CALL DRAWSTRING3 2762 DECLE 6, $200+0*20+3 2763 BYTE "Joseph Zbiciak",0 2764 2765 CALL DRAWSTRING4 2766 DECLE $200+1*20+6 2767 BYTE "presents",0 2768 2769 CALL DRAWSTRING5 2770 DECLE TITLE + 1 ; Point to the title 2771 DECLE $807 ; In GRAM font. 2772 DECLE $200+5*20+10-3 ; Point R4 to location on screen 2773 2774 CALL DRAWSTRING3 2775 DECLE 1, $200+10*20+2 2776 DECLE "Copyright ", COPYR, " 2000", 0 2777 MVO R1, SCRCOL 2778 2779 ; Animate the '2000' in Copyright 2000. 2780 SUBI #4, R4 ; Back up to the '2000' 2781 MVO R4, SCRPOS 2782 MVII #6, R4 ; Suppress 6 digits 2783 MVO R4, SCRDIG 2784 MVII #2000, R0 2785 CLRR R1 2786 MVII #PSCORL,R4 2787 MVO@ R0, R4 ; PSCORL = 2000 2788 MVO@ R1, R4 ; PSCORH = 0 --> PSCOR = 2000 2789 SUBI #25, R0 2790 MVO@ R0, R4 ; DSCORL = 1975 2791 MVO@ R1, R4 ; DSCORH = 0 --> DSCOR = 1975 2792 2793 CALL STARTTASK 2794 DECLE 2 ; Task # 2 2795 DECLE UPDATESCORE ; Update displayed date 2796 DECLE 4, 4 ; 15Hz 2797 2798 ; Start a Marquee task on the 4-TRIS title string. 2799 MVII #107 + 256*6, R2 ; Length 6, Offset 107 2800 CALL STARTTASK 2801 DECLE 0 ; Task #0 2802 DECLE MARQUEE ; MARQUEE task 2803 DECLE 10, 10 ; Period: every 5 ticks 2804 2805 ; Make all three tasks active. 2806 MVII #3, R3 2807 MVO R3, TSKACT 2808 2809 ; Start off title screen music. 2810 CALL PLAY.mus 2811 DECLE M_TITLE 2812 2813 ; Set our keypad dispatch 2814 CLRR R0 2815 MVO R0, HANDFN 2816 2817 PULR PC 2818 ENDP 2819 2820;;==========================================================================;; 2821;; GAMEHAND ;; 2822;; Table of keypad dispatches for INGAME ;; 2823;;==========================================================================;; 2824GAMEHAND PROC 2825@@kp0: DECLE STUB ; 0: KP0 2826@@kp1: DECLE STUB ; 1: KP1 2827@@kp2: DECLE STUB ; 2: KP2 2828@@kp3: DECLE STUB ; 3: KP3 2829@@kp4: DECLE NEXTTOGGLE ; 4: KP4 2830@@kp5: DECLE STUB ; 5: KP5 2831@@kp6: DECLE NEXTTOGGLE ; 6: KP6 2832@@kp7: DECLE MUTETOGGLE ; 7: KP7 2833@@kp8: DECLE STUB ; 8: KP8 2834@@kp9: DECLE MUTETOGGLE ; 9: KP9 2835@@kpC: DECLE NEXTTOGGLE ; 10: KPC 2836@@kpE: DECLE STUB ; 11: KPE 2837@@dsU: DECLE STUB ; 12: Disc Up 2838@@dsL: DECLE MVPIECE ; 13: Disc Left 2839@@dsR: DECLE MVPIECE ; 14: Disc Right 2840@@dsD: DECLE MVPIECE ; 15: Disc Down 2841@@acT: DECLE MVPIECE ; 16: Action Top 2842@@acL: DECLE MVPIECE ; 17: Action Left 2843@@acR: DECLE MVPIECE ; 18: Action Right 2844 ENDP 2845 2846;;==========================================================================;; 2847;; INGAME ;; 2848;; Sets up the display for the game by clearing the screen to "colored ;; 2849;; squares" mode, etc. Also sets up some initial tasks for starting the ;; 2850;; game. ;; 2851;;==========================================================================;; 2852INGAME: PROC 2853 PSHR R5 2854 ; Clear screen to colored squares mode 2855 MVII #$10, R0 2856 SWAP R0 ; $1000 corresponds to four black sq 2857 MVII #$200, R4 ; Fill memory starting at $200 2858 MVII #$F0, R1 ; ... through $2EF (all BACKTAB) 2859 CALL FILLMEM 2860 2861 ; Set up color stack advance bits to left and right of the "well". 2862 ; Also, draw well edges in yellow. 2863 MVII #$206, R4 2864 MVII #$22F8, R0 ; Black w/ "advance" bit, no colsq 2865 MVII #11, R1 ; 11 rows 2866 MVII #$1186, R2 ; Yellow in blocks 0, 2 2867 MVII #$3430, R3 ; Yellow in blocks 1, 3 2868@@csloop: 2869 MVO@ R0, R4 2870 MVO@ R2, R4 2871 ADDI #4, R4 2872 MVO@ R3, R4 2873 MVO@ R0, R4 2874 ADDI #12, R4 2875 DECR R1 2876 BNEQ @@csloop 2877 2878 ; Draw bottom of the "well" 2879 MVII #EDGEL, R1 2880 MVII #EDGEB, R2 2881 MVII #6, R0 2882@@botloop: 2883 CALL PUTPIXELS 2884 INCR R1 2885 CMPI #EDGER, R1 2886 BLE @@botloop 2887 2888 2889 ; Draw the 'next piece' box 2890 MVII #NXTPCX-3, R1 2891 MVII #NXTPCY-3, R2 2892 MVII #6, R0 2893@@npxloop: 2894 CALL PUTPIXELS 2895 ADDI #7, R2 2896 CALL PUTPIXELS 2897 SUBI #7, R2 2898 INCR R1 2899 CMPI #NXTPCX+3, R1 2900 BLE @@npxloop 2901 2902 INCR R2 2903 DECR R1 2904@@npyloop: 2905 CALL PUTPIXELS 2906 SUBI #6, R1 2907 CALL PUTPIXELS 2908 ADDI #6, R1 2909 INCR R2 2910 CMPI #NXTPCY+4, R2 2911 BLE @@npyloop 2912 2913 ; draw 'Lines', 'Level' and 'Next' onscreen in cyan 2914 MVII #$1809, R1 2915 MVII #LVLLOC, R4 2916 MVII #8, R2 2917 CALL TLA ; Draw 'Level' 2918 ADDI #LINLOC - LVLLOC - 3, R4 2919 CALL TLA ; Draw 'Lines' 2920 SUBI #3 + LINLOC - NXTLOC, R4 2921 CALL TLA ; Draw 'Next' 2922 2923 ; Spool up the random number generator a bit 2924 MVII #$FF, R2 2925 CALL NEXTRANDX 2926 2927 ; Pick a starting piece and a starting 'next piece' 2928 CLRR R0 2929 MVO R0, NXTPC 2930 CALL PICKPIECE ; Pick a random piece (starting piece) 2931 MVII #$FF, R2 2932 CALL NEXTRANDX ; Spin the random number generator some more. 2933 CALL PICKPIECE ; Pick a random piece (next piece) 2934 2935 ; Set PREVBL routine to animate CS1 and CS3 2936 MVII #ANIDROP, R0 2937 MVO R0, PREVBL 2938 2939 ; Task 0: Step blocks down the screen / score drops. 2940 ; Task 1: Key/disc/pad repeat manager. 2941 ; Task 2: Score display update. 2942 ; Task 3: Sleep task. 2943 2944 ; Set up piece drop task 2945 CALL STARTTASK 2946 DECLE 0 2947 DECLE GRAVITY 2948 DECLE 120, 120 ; 1Hz 2949 2950 ; Set up the score update. 2951 CLRR R2 ; Do not suppress any digits. 2952 MVII #$1802, R3 ; Display in orange, using GRAM font 2953 MVII #20*11 + 5, R4 ; Put at bottom of screen in middle 2954 MVO R2, SCRDIG 2955 MVO R3, SCRCOL 2956 MVO R4, SCRPOS 2957 CALL STARTTASK 2958 DECLE 2 ; Task # 2 2959 DECLE UPDATESCORE ; Update displayed score 2960 DECLE 4, 4 ; 30Hz 2961 2962 2963 ; Allow up to four active tasks. (Task 3 is used for SLEEP). 2964 MVII #4, R3 2965 MVO R3, TSKACT 2966 2967 ; Set up color stack. Entries 0 and 2 are black, 1 and 3 are white. 2968 ; Entries 1 and 3 will be "cycled" during a drop animation sequence. 2969 MVII #CS0, R4 2970 CLRR R0 2971 MVO@ R0, R4 2972 MVO@ R0, R4 2973 MVO@ R0, R4 2974 MVO@ R0, R4 2975 MVII #PSCORL,R4 ; (interruptible, for STIC) 2976 2977 ; Clear our total lines count and level number. (UPDATESTATS 2978 ; will move the level number to 1 for us.) 2979 MVO R0, LINES ; Lines cleared so far 2980 MVO R0, LEVEL ; Clear level number to force an update 2981 2982 2983 ; Clear our starting score 2984 MVO@ R0, R4 ; PSCORL 2985 MVO@ R0, R4 ; PSCORH 2986 DECR R0 ; Set to -1 to force a redisplay. 2987 MVO@ R0, R4 ; DSCORL 2988 MVO@ R0, R4 ; DSCORH 2989 2990 ; Clear the well height. 2991 MVII #EDGEB, R0 2992 MVO R0, HEIGHT 2993 2994 ; Set our keypad dispatch 2995 MVII #GAMEHAND, R0 2996 MVO R0, HANDFN 2997 2998 ; Play some background music 2999 CALL PLAY.mus 3000 DECLE M_GAME 3001 3002 ; Chain through "UPDATESTATS" 3003 PULR R5 3004 B UPDATESTATS 3005 ENDP 3006 3007;;==========================================================================;; 3008;; TLA ;; 3009;; 'Three Letter Acronym' ;; 3010;; Used to draw the labels over 'Level', 'Lines' and 'Next' on the screen. ;; 3011;; ;; 3012;; INPUTS: ;; 3013;; R1 -- Screen format word, including starting GRAM/GROM card #. ;; 3014;; R2 -- Index value for stepping between GRAM/GROM card #'s. ;; 3015;; R4 -- Display pointer. ;; 3016;; R5 -- Return address. ;; 3017;; ;; 3018;; OUTPUTS: ;; 3019;; R1 -- Screen format word, with GRAM/GROM card # advanced by 3. ;; 3020;; R4 -- Display pointer, advanced by 3 ;; 3021;; R2, R5 untouched. ;; 3022;;==========================================================================;; 3023TLA PROC 3024 MVO@ R1, R4 3025 ADDR R2, R1 3026 MVO@ R1, R4 3027 ADDR R2, R1 3028 MVO@ R1, R4 3029 ADDR R2, R1 3030 JR R5 3031 ENDP 3032 3033 3034;;==========================================================================;; 3035;; TESTHAND ;; 3036;; Table of keypad dispatches for SOUNDTEST ;; 3037;;==========================================================================;; 3038TESTHAND PROC 3039@@kp0: DECLE MUTETOGGLE ; KP0 3040@@kp1: DECLE SOUNDTEST.sfx ; KP1 3041@@kp2: DECLE SOUNDTEST.sfx ; KP2 3042@@kp3: DECLE SOUNDTEST.sfx ; KP3 3043@@kp4: DECLE SOUNDTEST.sfx ; KP4 3044@@kp5: DECLE SOUNDTEST.sfx ; KP5 3045@@kp6: DECLE SOUNDTEST.sfx ; KP6 3046@@kp7: DECLE SOUNDTEST.mus ; KP7 3047@@kp8: DECLE SOUNDTEST.mus ; KP8 3048@@kp9: DECLE SOUNDTEST.mus ; KP9 3049@@kpC: DECLE SOUNDTEST.soff ; KPC 3050@@kpE: DECLE SOUNDTEST.moff ; KPE 3051@@dsU: DECLE SCHEDEXIT ; Disc Up 3052@@dsL: DECLE SCHEDEXIT ; Disc Left 3053@@dsR: DECLE SCHEDEXIT ; Disc Right 3054@@dsD: DECLE SCHEDEXIT ; Disc Down 3055@@acT: DECLE SOUNDTEST.spd ; Action Top 3056@@acL: DECLE SOUNDTEST.spd ; Action Left 3057@@acR: DECLE SOUNDTEST.spd ; Action Right 3058 ENDP 3059 3060 3061 3062;;==========================================================================;; 3063;; SOUNDTEST ;; 3064;; Extremely simple Sound Test routine ;; 3065;;==========================================================================;; 3066SOUNDTEST PROC 3067 PSHR R5 3068 3069 ; Set our keypad dispatch 3070 MVII #TESTHAND, R0 3071 MVO R0, HANDFN 3072 3073 ; Force music and sound effects off 3074 CALL @@soff ; Force sound effects off. 3075 CALL @@moff ; Force music off. 3076 3077 ; Display instructions 3078 MVII #$F0, R1 ; Set entire display to BLACK 3079 MVII #$200, R4 3080 CALL FILLZERO 3081 3082 MVII #$1807, R1 ; Pastel Purple, GRAM font 3083 MVII #$200 + 5, R4 3084 3085 CALL DRAWSTRING2 3086 BYTE "SOUND TEST",0 3087 3088 CALL DRAWSTRING3 3089 DECLE 6, $200 + 40 ; Yellow, top of screen 3090 ; 01234567890123456789 3091 BYTE "1 - 6: Sound Effect" 3092 BYTE "7,8,9: Bkgnd Music " 3093 BYTE "0: Toggle Mute " 3094 BYTE "Clear: Stop SFX " 3095 BYTE "Enter: Stop Music " 3096 BYTE "Action: Music speed " 3097 BYTE "Disc: Exit", 0 3098 3099 CALL DRAWSTRING3 3100 DECLE 1, $2F0 - 40 ; Blue, bottom of screen. 3101 3102 ; 01234567890123456789 3103 BYTE "intvnut AT gmail.com" 3104 BYTE " v1.12",0 3105 3106 ; Start a Marquee task on the Sound Test string. 3107 MVII #5 + 256*10, R2 ; Length 10, Offset 5 3108 CALL STARTTASK 3109 DECLE 0 ; Task #0 3110 DECLE MARQUEE ; MARQUEE task 3111 DECLE 14, 14 ; Period: every 7 ticks 3112 3113 ; Make maquee task active. 3114 MVII #1, R3 3115 MVO R3, TSKACT 3116 3117 PULR PC ; Return 3118 3119@@soff: 3120 MVII #$FF, R1 3121 MVII #FX_OFF, R4 3122 B PLAY.sfxn ; Force sound effects off 3123 3124@@moff: 3125 MVII #FX_OFF, R4 3126 B PLAY.musn ; Force music off 3127 3128@@sfx: ; Play a sound effect 3129 DECR R2 3130 ADDR R2, R2 3131 MOVR R2, R4 3132 B PLAY.sfxn 3133 3134@@mus: ; Play new music 3135 DECR R2 3136 ADDR R2, R2 3137 MOVR R2, R4 3138 B PLAY.musn 3139 3140@@spd: ; Toggle music speed 3141 MVII #EDGEB, R0 3142 XOR HEIGHT, R0 3143 MVO R0, HEIGHT 3144 JR R5 3145 3146 ENDP 3147 3148;;==========================================================================;; 3149;; GAMEOVER ;; 3150;; Guess what? It's game over time!! ;; 3151;;==========================================================================;; 3152GAMEOVER PROC 3153 PSHR R5 3154 3155 ; Set our keypad dispatch 3156 CLRR R0 3157 MVO R0, HANDFN 3158 3159 ; Stop the current game music & sfx, and play a loud BOOM! 3160 MVO R0, SNDSTRM+7 ; Make SFX enable mask go to 0 3161 DECR R0 3162 MVO R0, MUTE ; Force music to be un-muted 3163 MVO R0, DSCORL ; set 'displayed score' to 0xFFFFFFFF 3164 MVO R0, DSCORH ; (moved from below to save some code) 3165 CALL PLAY.mus ; Play loud BOOM on music channel 3166 DECLE FXBOOM2 3167 3168 ; Force the displayed score to be fully updated. 3169 CALL UPDATESCORE 3170 3171 ; Wait for "BOOM2" to finish 3172 MVII #2, R0 3173@@spin: 3174 CMP SNDSTRM, R0 3175 BLE @@spin 3176 3177 ; Clear the well! 3178 CALL CLEARWELL 3179 CALL WAIT 3180 DECLE 20 ; Wait 1/3rd second afterwards 3181 3182 ; Put up the GAME OVER string. 3183 CALL DRAWSTRING3 3184 DECLE $7 3185 DECLE $200 + 20*5 + 5 3186 BYTE "Game Over!",0 3187 3188 ; Start a Marquee task on the Game Over string. 3189 MVII #105 + 256*10, R2 ; Length 10, Offset 105 3190 CALL STARTTASK 3191 DECLE 0 ; Task #0 3192 DECLE MARQUEE ; MARQUEE task 3193 DECLE 10, 10 ; Period: every 5 ticks 3194 3195 MVII #1, R0 3196 MVO R0, TSKACT 3197 3198 ; Play the Game Over music 3199 CALL PLAY.mus 3200 DECLE M_OVER 3201 3202 PULR PC 3203 3204 ENDP 3205 3206;;==========================================================================;; 3207;; SETLEVEL ;; 3208;; Receives a keypad number and sets up our starting score and level ;; 3209;; number appropriately. ;; 3210;; ;; 3211;; INPUT: ;; 3212;; R2 -- Key/disc/action button number (0..18) ;; 3213;; ;; 3214;; OUTPUT: ;; 3215;; SLEVEL set up appropriately. ;; 3216;;==========================================================================;; 3217SETLEVEL PROC 3218 TSTR R2 ; See if it's zero 3219 BEQ @@got_level ; Yes... Set it anyway. Triggers 'sound test' 3220 CMPI #10, R2 ; See if it's > 10 3221 BLE @@got_level ; No, use this as the level number 3222 3223@@default_lev: 3224 MVII #4, R2 ; Default is to start at level #4 3225 3226@@got_level: 3227 MVO R2, SLEVEL ; Store starting level number 3228 3229 B SCHEDEXIT ; Return via SCHEDEXIT 3230 ENDP 3231 3232;;==========================================================================;; 3233;; HANDTASK ;; 3234;; Handles a keypad press. ;; 3235;; ;; 3236;; INPUT: ;; 3237;; R2 -- Keypad key number (0..11), disc dir (12..15), or action ;; 3238;; button (16..18) ;; 3239;; R5 -- Return address ;; 3240;; ;; 3241;; OUTPUT: ;; 3242;; Jumps to appropriate function. R2 is still set to the keypad number. ;; 3243;;==========================================================================;; 3244HANDTASK PROC 3245 MVI HANDFN, R4 3246 TSTR R4 3247 BEQ SETLEVEL 3248 ADDR R2, R4 3249 MVI@ R4, PC 3250 ENDP 3251 3252;;==========================================================================;; 3253;; KEYREPEAT ;; 3254;; Key repeat task, scheduled on task #1. De-schedules self if nothing ;; 3255;; is currently pressed. Works by clearing LHAND/LHKPD. ;; 3256;;==========================================================================;; 3257KEYREPEAT PROC 3258 MVII #1, R3 ; Prepare to disable task if needed 3259 MVI LHAND, R0 ; See if something was pressed 3260 MVO R0, REPEAT ; Update "repeating" flag. 3261 TSTR R0 ; 3262 BEQ STOPTASK ; No? Disable task and chain the return 3263 CLRR R0 ; Yes? Clear last-pressed info. 3264 MVO R0, LHAND 3265 MVO R0, LHKPD 3266 JR R5 ; Return. 3267 ENDP 3268 3269;;==========================================================================;; 3270;; SCANHAND ;; 3271;; Scan the hand controllers and schedule tasks based on the input ;; 3272;; triggers we see. Hand controller debouncing is done here too, which ;; 3273;; makes scanning controllers somewhat time-consuming. (I need to ;; 3274;; eventually experiment with debounce parameters on real hardware if I ;; 3275;; plan to make this a cartridge.) ;; 3276;; ;; 3277;; Hand controller docs: ;; 3278;; Bit 0: Down, Row 0 keys --------- 1 2 3 ;; 3279;; Bit 1: Right, Row 1 keys --------- 4 5 6 ;; 3280;; Bit 2: Up, Row 2 keys --------- 7 8 9 ;; 3281;; Bit 3: Left, Row 3 keys --------- C 0 E ;; 3282;; Bit 4: Corner | | | ;; 3283;; Bit 5: T/L Col 2 keys --------- | -- | ---+ ;; 3284;; Bit 6: L/R Col 1 keys --------- | ---+ ;; 3285;; Bit 7: T/R Col 0 keys ----------+ ;; 3286;;==========================================================================;; 3287 ; E C 9 8 7 6 5 4 3 2 1 0 3288PADTBL: BYTE $28, $88, $24, $44, $84, $22, $42, $82, $21, $41, $81, $48 3289 ; rgt lft top 3290ACTTBL: BYTE $C0, $60, $A0 3291 ; bot rgt lft top 3292DSCTBL: BYTE $01, $02, $08, $04 3293 3294SCANHAND PROC 3295DBOUT EQU 5 ; Debounce outer loop 3296DBIN EQU 120 ; Debounce inner loop 3297DB80 EQU (80*DBIN)/100 ; 80% of inner loop iters 3298 3299@@startover: 3300 CLRR R2 3301 MVO R2, PAUSED ; Clear 'we paused' flag 3302 3303 MVII #DBIN, R2 3304@@dbnothing: 3305 MVI CTRL0, R0 3306 AND CTRL1, R0 3307 XORI #$FF, R0 3308 3309 ; See if it is the same as our last hand-controller input. 3310 ; If so, just ignore it. 3311 CMP LHAND, R0 3312 BEQ @@nothing 3313 3314 MOVR R0, R1 ; Lets see if it's anything at all 3315 BNEQ @@input ; If it comes up non-empty, process it. 3316 3317 DECR R2 ; If it comes up empty, debounce before 3318 BNEQ @@dbnothing ; saying it's just nothing. 3319 B @@noinput 3320@@input: 3321 3322 ; If last input was keypad, ignore this if they share any bits. 3323 MVI LHKPD, R0 3324 ANDR R1, R0 3325 BNEQ @@nothing 3326 3327 ; Debounce the input. If we read the same value >80% of the 3328 ; iterations of the debounce inner loop, we say it's good. 3329 ; Otherwise we iterate the outer loop. 3330@@debounce: 3331 MVII #DBOUT, R3 ; Outer loop max count 3332@@oloop: 3333 CLRR R2 ; Match count 3334 MVII #DBIN, R0 ; Inner loop count 3335 3336 MVI CTRL0, R1 3337 AND CTRL1, R1 3338@@iloop: 3339 MVI CTRL0, R4 3340 AND CTRL1, R4 3341 CMPR R4, R1 3342 BNEQ @@notsame ; Yes... don't count it 3343 INCR R2 ; No... do count it. 3344@@notsame: 3345 DECR R0 ; Count down inner loop 3346 BNEQ @@iloop 3347 XORI #$FF, R1 3348 3349 CMPI #DB80, R2 ; Was it same >80% of inner loop? 3350 BGE @@ok ; Yes, keep it. 3351 3352 DECR R3 ; No, iterate outer loop 3353 BNEQ @@oloop 3354 ; If we fall through here, just parse whatever we read last. 3355 ; Somebody really needs to clean their controllers! 3356@@ok: 3357 MVI PAUSED, R3 ; If we paused during debounce, start this 3358 TSTR R3 ; whole thing over. 3359 BNEQ @@startover 3360 3361 ; Parse controller info in R1. 3362 ; If bit 4 is set, assume this is NOT a keypad press. 3363 MOVR R1, R0 3364 ANDI #$10, R0 3365 BNEQ @@nopad 3366 3367 ; See if it EXACTLY matches a keypad key 3368 MVII #PADTBL,R4 3369 MVII #11, R0 3370@@kloop: 3371 CMP@ R4, R1 3372 BEQ @@gotpad 3373 DECR R0 3374 BGE @@kloop 3375 B @@nopad 3376 3377@@gotpad: 3378 MVO R1, LHAND ; Set last-hand value 3379 3380 ; If we _think_ we got a keypad input, make DAMN SURE by debouncing 3381 ; AGAIN with 3x the debounce inner loop. 3382 MVII #3*DBIN, R3 ; Inner loop count 3383@@kpdloop: 3384 MVI CTRL0, R4 3385 AND CTRL1, R4 3386 XORI #$FF, R4 3387 CMPR R4, R1 3388 BNEQ @@debounce ; Miscompare? Hell with this! 3389@@kpnotsame: 3390 DECR R3 ; Count down inner loop 3391 BNEQ @@kpdloop 3392 3393 MVO R1, LHKPD ; Record that last input was keypad. 3394 3395 MOVR R0, R1 3396 B @@dispatch ; Keypad keys don't repeat. 3397 3398@@notrepeating: 3399 PSHR R5 3400 CALL STARTTASK ; Make the keypress repeat 3401 DECLE 1 ; Task #1 3402 DECLE KEYREPEAT ; KEYREPEAT task 3403 DECLE 30, 8 ; First repeat 250ms, rest @ 15Hz 3404 PULR R5 3405 3406@@repeating: 3407 PULR R1 3408@@dispatch: 3409 MVII #HANDTASK, R0 ; Queue the HANDTASK to process key 3410 3411 JD QTASK 3412 3413@@nopad: 3414 CLRR R0 3415 MVO R0, LHKPD 3416 3417 MOVR R1, R0 ; Copy input to R0 to decode action buttons. 3418 ANDI #$E0, R0 3419 BEQ @@noaction ; Don't decode action btns if there are none 3420 3421 ; Action buttons shouldn't repeat like the disc, so avoid doing 3422 ; action buttons if these bits match bits in REPEAT. 3423@@hmm 3424 MVI REPEAT, R2 3425 ANDI #$E0, R2 3426 CMPR R2, R0 3427 BEQ @@noaction 3428 3429 MVI LHAND, R2 3430 ANDI #$E0, R2 3431 CMPR R2, R0 3432 BEQ @@noaction 3433 3434 PSHR R1 3435 ; Ok, decode the action buttons. 3436 MVII #ACTTBL,R4 ; R4 points to the "action table" 3437 MVII #3, R1 3438@@actloop: 3439 CMP@ R4, R0 3440 BEQ @@gotaction 3441 DECR R1 3442 BNEQ @@actloop 3443 B @@notaction 3444@@gotaction: 3445 ; Queue an action-key event 3446 ADDI #15, R1 3447 PSHR R5 3448 MVII #HANDTASK, R0 ; Queue the HANDTASK to process key 3449 JSRD R5, QTASK 3450 PULR R5 3451@@notaction: 3452 PULR R1 3453 3454@@noaction: 3455 ; Now process disc events. 3456 MVI LHAND, R2 ; Get our previous controller input 3457 MVO R1, LHAND ; Save current input 3458 MOVR R1, R3 3459 3460 MVII #DSCTBL,R4 ; R4 points to the "disc table" 3461 MVII #4, R1 3462@@discloop: 3463 MOVR R3, R0 ; Restore word. 3464 AND@ R4, R0 ; Compare against a direction bit. 3465 BNEQ @@gotdisc ; Is this bit set? 3466 DECR R1 3467 BNEQ @@discloop 3468 B @@nothing 3469@@gotdisc: 3470 ADDI #11, R1 3471 PSHR R1 3472 MOVR R0, R3 3473 AND REPEAT, R0 ; See if we're repeating 3474 BNEQ @@repeating 3475 ANDR R3, R2 ; See if this is just our prev keypress 3476 BEQ @@notrepeating ; Yup, this is new. 3477 PULR R1 3478 B @@nothing ; ... otherwise ignore it. 3479 3480@@noinput: 3481 CLRR R1 3482 MVO R1, LHAND ; Clear last-hand 3483 MVO R1, LHKPD ; Clear last-was-keypad 3484 MVO R1, REPEAT ; Clear repeating key 3485@@nothing: 3486 JR R5 ; Return. 3487 3488 ENDP 3489 3490;;==========================================================================;; 3491;; NEXTRAND ;; 3492;; Simple task which updates the random number generator. It advances ;; 3493;; the random number generator's state by one bit. This is a good one ;; 3494;; to call from the background task to keep things random. ;; 3495;; ;; 3496;; NEXTRANDX ;; 3497;; Same as NEXTRAND, only it iterates for R2 iterations, generating R2 ;; 3498;; new random bits. (default is only one new random bit) ;; 3499;; ;; 3500;; INPUTS: ;; 3501;; R2 -- Number of iterations (NEXTRANDX only) ;; 3502;; R5 -- Return address ;; 3503;; Random state in RANDLO, RANDHI ;; 3504;; ;; 3505;; OUTPUTS: ;; 3506;; R0, R1 -- Contains current rand # state. ;; 3507;; R2 -- Zeroed ;; 3508;; R4 -- Trashed ;; 3509;;==========================================================================;; 3510NEXTRAND PROC 3511 MVII #1, R2 3512NEXTRANDX 3513 MVII #RANDLO, R4 3514 MVI@ R4, R0 3515 MVI@ R4, R1 3516 3517@@loop: 3518 SLLC R0, 1 3519 RLC R1, 1 3520 BNC @@nocarry 3521; XORI #$5, R0 ; period==(2**31 - 1) polynomial 3522 XORI #$04C1, R1 ; period==(2**32 - 1) polynomial (CRC-32 poly) 3523 XORI #$1DB7, R0 3524@@nocarry: 3525 DECR R2 3526 BNEQ @@loop 3527 3528 SUBI #2, R4 3529 MVO@ R0, R4 3530 MVO@ R1, R4 3531 JR R5 3532 3533 ENDP 3534 3535;;==========================================================================;; 3536;; POW10 ;; 3537;; Look-up table with powers of 10 as 32-bit numbers (little endian) ;; 3538;; ;; 3539;; NPW10 ;; 3540;; Same as POW10, only -10**x instead of 10**x ;; 3541;;==========================================================================;; 3542 3543POW10 3544POW10_9 DECLE 1000000000 AND $FFFF, 1000000000 SHR 16 ; 10**9 3545POW10_8 DECLE 100000000 AND $FFFF, 100000000 SHR 16 ; 10**8 3546POW10_7 DECLE 10000000 AND $FFFF, 10000000 SHR 16 ; 10**7 3547POW10_6 DECLE 1000000 AND $FFFF, 1000000 SHR 16 ; 10**6 3548POW10_5 DECLE 100000 AND $FFFF, 100000 SHR 16 ; 10**5 3549POW10_4 DECLE 10000 AND $FFFF, 10000 SHR 16 ; 10**4 3550POW10_3 DECLE 1000 AND $FFFF, 1000 SHR 16 ; 10**3 3551POW10_2 DECLE 100 AND $FFFF, 100 SHR 16 ; 10**2 3552POW10_1 DECLE 10 AND $FFFF, 10 SHR 16 ; 10**1 3553POW10_0 DECLE 1 AND $FFFF, 1 SHR 16 ; 10**0 3554 3555NPW10 3556NPW10_9 DECLE -1000000000 AND $FFFF,(-1000000000 SHR 16) AND $FFFF ;-10**9 3557NPW10_8 DECLE -100000000 AND $FFFF,(-100000000 SHR 16) AND $FFFF ;-10**8 3558NPW10_7 DECLE -10000000 AND $FFFF,(-10000000 SHR 16) AND $FFFF ;-10**7 3559NPW10_6 DECLE -1000000 AND $FFFF,(-1000000 SHR 16) AND $FFFF ;-10**6 3560NPW10_5 DECLE -100000 AND $FFFF,(-100000 SHR 16) AND $FFFF ;-10**5 3561NPW10_4 DECLE -10000 AND $FFFF,(-10000 SHR 16) AND $FFFF ;-10**4 3562NPW10_3 DECLE -1000 AND $FFFF,(-1000 SHR 16) AND $FFFF ;-10**3 3563NPW10_2 DECLE -100 AND $FFFF,(-100 SHR 16) AND $FFFF ;-10**2 3564NPW10_1 DECLE -10 AND $FFFF,(-10 SHR 16) AND $FFFF ;-10**1 3565NPW10_0 DECLE -1 AND $FFFF,(-1 SHR 16) AND $FFFF ;-10**0 3566 3567;;==========================================================================;; 3568;; DEC16 ;; 3569;; Displays a 16-bit decimal number on the screen with leading blanks ;; 3570;; in a field up to 5 characters wide. ;; 3571;; ;; 3572;; DEC16A ;; 3573;; Same as DEC16, only displays leading zeroes. ;; 3574;; ;; 3575;; DEC16B ;; 3576;; Same as DEC16, only leading zeros are controlled by bit 15 of R3. ;; 3577;; (If set, suppress leading zeros. If clear, show leading zeros.) ;; 3578;; ;; 3579;; DEC16C ;; 3580;; Same as DEC16B, except R1 contains an amount to add to the first digit ;; 3581;; ;; 3582;; INPUTS: ;; 3583;; R0 -- Decimal number ;; 3584;; R1 -- (DEC16C only): Amount to add to initial digit. ;; 3585;; R2 -- Number of digits to suppress (If R2<=5, it is 5-field_width) ;; 3586;; R3 -- Color mask ;; 3587;; R4 -- Screen offset (8-bit) ;; 3588;; ;; 3589;; OUTPUTS: ;; 3590;; R0 -- Zeroed ;; 3591;; R1 -- Trashed ;; 3592;; R2 -- Remaining digits to suppress (0 if initially <= 5.) ;; 3593;; R3 -- Color mask, with bit 15 set if no digits displayed. ;; 3594;; R4 -- Pointer to character just right of string ;; 3595;; R5 -- Trashed ;; 3596;; ;; 3597;; Routine uses $110, $111 for temporary storage. ;; 3598;;==========================================================================;; 3599DEC16 PROC 3600@@so EQU $110 3601@@fw EQU $111 3602 SETC ; Prepare to set bit 15 of color mask 3603 INCR PC ; Skip the CLRC 3604DEC16A 3605 CLRC ; Prepare to clear bit 15 of clrmask 3606 SLL R3, 1 3607 RRC R3, 1 ; Set/clear bit 15 of color mask 3608DEC16B 3609 CLRR R1 3610DEC16C 3611 PSHR R5 ; Save return address 3612 MVII #POW10_4, R5 ; Point to '10000' entry in POW10 3613 MVO R4, @@so ; Save screen offset 3614 MVO R2, @@fw ; Save field width 3615 MVII #5, R4 ; Iterate 5 (16-bit goes to 65536) 3616 MOVR R1, R2 3617 INCR PC 3618@@digitlp: 3619 3620 CLRR R2 ; Start with division result == 0 3621 MVI@ R5, R1 ; Load power of 10 3622 INCR R5 ; Point to next smaller power of 10 3623@@divloop: 3624 INCR R2 3625 SUBR R1, R0 ; Divide by repeated subtraction 3626 BC @@divloop 3627 ADDR R1, R0 ; Loop iterates 1 extra time: Fix it. 3628 DECR R2 ; Fix extra iter. Also test if 0 3629 BNEQ @@disp ; If digit != 0, display it. 3630 TSTR R3 ; If digit == 0 and no lead 0, skip 3631 BMI @@blank 3632@@disp: 3633 SLL R3, 1 ; Clear "no leading 0" flag 3634 SLR R3, 1 ; 3635 MVI @@fw, R1 ; Get field width 3636 DECR R1 ; Are we in active field yet? 3637 BMI @@ok ; Yes: Go ahead and display 3638 MVO R1, @@fw ; No: Save our count-down till field 3639 B @@iter ; and don't display the digit. 3640@@blank: 3641 MOVR R3, R2 ; Blank character _just_ gets format 3642 MVI @@fw, R1 ; Get field width 3643 DECR R1 ; Are we in active field yet? 3644 BMI @@drawit ; Yes: Go ahead and display 3645 MVO R1, @@fw ; No: Save our count-down till field 3646 B @@iter ; and don't display the digit. 3647@@ok: 3648 ADDI #$10, R2 ; Pseudo-ASCII digits start at 0x10 3649 SLL R2, 2 ; Put pseudo-ASCII char in position 3650 ADDR R2, R2 ; ... by shifting left 3 3651 XORR R3, R2 ; Merge with display format 3652@@drawit: 3653 MVI @@so, R1 ; Get screen offset 3654 XORI #$200, R1 ; Move to screen 3655 MVO@ R2, R1 ; Put character on screen 3656 INCR R1 ; Move the pointer 3657 MVO R1, @@so ; Save the new offset 3658@@iter: 3659 DECR R4 ; Count down our digit count 3660 BNEQ @@digitlp ; Keep iterating 3661 3662 MVI @@so, R4 ; Restore offset 3663 MVI @@fw, R2 ; Restore digit suppress ocunt 3664 3665 PULR PC ; Whew! Done! 3666 ENDP 3667 3668;;==========================================================================;; 3669;; DEC32 ;; 3670;; Displays a 32 bit number without leading zeros. It performs this feat ;; 3671;; by calling DEC16 twice. ;; 3672;; ;; 3673;; DEC32A ;; 3674;; Same as DEC32, except leading zeros are displayed. ;; 3675;; ;; 3676;; DEC32B ;; 3677;; Same as DEC32, except leading zeros are controlled by bit 15 of R3 ;; 3678;; (If set, suppress leading zeros. If clear, show leading zeros.) ;; 3679;; ;; 3680;; INPUTS: ;; 3681;; R0 -- Low half of 32-bit number ;; 3682;; R1 -- High half of 32-bit number ;; 3683;; R2 -- Number of leading digits to suppress (10 - field width) ;; 3684;; R3 -- Screen format word ;; 3685;; R4 -- Screen offset (8-bit) ;; 3686;; ;; 3687;; $110..$113 used for temporary storage ;; 3688;;==========================================================================;; 3689DEC32 PROC 3690@@so EQU $110 3691@@fw EQU $111 3692@@fmt EQU $35D 3693 3694 SETC ; Prepare to set bit 15 of color mask 3695 INCR PC ; Skip the CLRC 3696DEC32A 3697 CLRC ; Prepare to clear bit 15 of clrmask 3698 SLL R3, 1 3699 RRC R3, 1 ; Set/clear bit 15 of color mask 3700 NOP ; (interruptible for STIC) 3701DEC32B 3702 PSHR R5 ; Save return address 3703 MVO R2, @@fw ; Save field width 3704 MVO R4, @@so ; Save screen offset 3705 MVO R3, @@fmt 3706 3707 CLRR R3 3708 PSHR R3 ; Push accumulator (init'd to 0) 3709 3710 ; Use division by repeated subtraction to generate a 16-bit 3711 ; value which represents the first 5 digits of the 10 digit number. 3712 MVII #NPW10_9, R5 ; Point to -10**9 3713 3714@@digitlp: 3715 CLRR R3 3716 MVI@ R5, R2 ; Load low half of 32-bit -10**x 3717 MVI@ R5, R4 ; Load high half of 32-bit -10**x 3718@@divlp: 3719 SLR R3, 1 3720@@divlpb: 3721 INCR R3 3722 ADDR R2, R0 ; Add the low half 3723 ADCR R1 ; Add carry from low half 3724 RLC R3, 1 ; See if adding the carry carried 3725 ADDR R4, R1 ; Add high half 3726 BC @@divlp ; Loop if we had a carry from either 3727 SARC R3, 1 ; upper half ADD. (We can't get 3728 BC @@divlpb ; a carry from both, though.) 3729 3730 ; Subtract off the extra iteration 3731 SUBR R2, R0 ; Subtract the low half. 3732 ADCR R1 ; Add in the "not-borrow" 3733 DECR R1 ; Turn "not-borrow" into "borrow" 3734 SUBR R4, R1 ; Subtract the high half. 3735 3736 DECR R3 3737 BEQ @@nxtdigit 3738 3739 ; Take our count and multiply it by the appropriate power of 10. 3740 MOVR R5, R2 3741 MOVR R3, R4 3742 SUBI #NPW10_4, R2 ; Translate 10**x to 10**(x-5) 3743 BEQ @@donemult 3744@@mult: 3745 ADDR R4, R4 ; To mult by 10, do (x<<1)+(x<<3) 3746 MOVR R4, R3 3747 SLL R3, 2 3748 ADDR R3, R4 3749 ADDI #$4, R2 3750 BLT @@mult 3751@@donemult: 3752 ADD@ SP, R4 ; Add this to our 16-bit accum. 3753 PSHR R4 ; that we keep on top-of-stack 3754@@nxtdigit: 3755 CMPI #NPW10_4, R5 3756 BLT @@digitlp 3757 3758 MVI @@fw, R2 ; Restore field width 3759 MVI @@so, R4 ; Restore screen offset 3760 MVI @@fmt, R3 ; ... 3761 MVO R0, @@fmt ; Save low half momentarily. 3762 PULR R0 ; Get accumulated word for display 3763 PSHR R1 ; Save upper bit 3764 CALL DEC16B ; Display first five digits 3765 3766 ; Now, our 32-bit number should be less than 100000. That 3767 ; means R1 should be 0 or 1. We display the last five digits 3768 ; as a single 16-bit number by handling that bit separately. 3769 3770 MVI @@fmt, R0 ; Restore lower 16 bits 3771 3772 PULR R1 ; Get upper bit 3773 TSTR R1 ; Was it zero? 3774 BEQ @@noextra ; Yes: Nothing special to do 3775 MVII #6, R1 ; No: Add 6 to the leading digit 3776 ADDI #5536, R0 ; ... and "5536" to remaining digits 3777@@noextra: 3778 PULR R5 ; Chain the return. 3779 B DEC16C ; Display remaining digits. WHEW! 3780 ENDP 3781 3782;;==========================================================================;; 3783;; DEC32Z ;; 3784;; Same as DEC32, except a zero is displayed in the final position if ;; 3785;; the whole number's value is zero. ;; 3786;; ;; 3787;; DEC16Z ;; 3788;; Same as DEC16, except a zero is displayed in the final position if ;; 3789;; the whole number's value is zero. ;; 3790;;==========================================================================;; 3791DEC32Z PROC 3792 TSTR R0 ; Is lower half non-zero? 3793 BNEQ DEC32 ; Yes: Go display the number as per usual. 3794 TSTR R1 ; Is upper half non-zero? 3795 BNEQ DEC32 ; Yes: Go display the number as per usual. 3796 MVII #10, R1 ; Otherwise, display a 0 in a field that is 3797 B @@dozero ; ... up to 10 spaces wide. 3798DEC16Z: 3799 TSTR R0 ; Is the number non-zero? 3800 BNEQ DEC16 ; Yes: Go display the number as per usual. 3801 MVII #5, R1 ; Otherwise, display a 0 in a field that is 3802 ; ... up to 5 spaces wide. 3803@@dozero: 3804 SUBR R2, R1 ; Account for suppressed digits. 3805 BLE @@nodisp ; Display nothing if # of suppressed digits 3806 ; is greater than or equal to our field width. 3807 3808 ADDI #$200, R4 ; Point into display memory. 3809 INCR PC ; Skip the first MVO@ in loop below. 3810@@loop: 3811 MVO@ R3, R4 ; Output a blank space (leading blanks) 3812 DECR R1 ; Decrement our count of leading blanks. 3813 BNEQ @@loop ; Keep looping until we've filled the blank 3814 ; ... portion of the displayed number. 3815 3816 XORI #$80, R3 ; Now, display the digit 0 in last position. 3817 MVO@ R3, R4 3818 XORI #$80, R3 ; Finally, restore our display format word. 3819 3820@@nodisp: 3821 JR R5 ; Return to the caller. 3822 ENDP 3823 3824;;==========================================================================;; 3825;; DRAWSTRING1 ;; 3826;; Puts an ASCIIZ string pointed to by R0 onscreen. ;; 3827;; ;; 3828;; DRAWSTRING2 ;; 3829;; Puts an ASCIIZ string after a JSR R5 onscreen. ;; 3830;; ;; 3831;; DRAWSTRING3 ;; 3832;; Reads R1, R4 from @ R5, then does DRAWSTRING2. ;; 3833;; ;; 3834;; DRAWSTRING4 ;; 3835;; Reads R4 from @ R5, then does DRAWSTRING2. ;; 3836;; ;; 3837;; DRAWSTRING5 ;; 3838;; Reads R0, R1, R4 from @ R5, then does DRAWSTRING1. ;; 3839;; ;; 3840;; INPUTS: ;; 3841;; R0 -- String pointer (if DRAWSTRING) ;; 3842;; R1 -- Screen format word ;; 3843;; R4 -- Output pointer ;; 3844;; R5 -- Return address (also, string if DRAWSTRING2). ;; 3845;; ;; 3846;; OUTPUTS: ;; 3847;; R0 -- Zero if DRAWSTRING2, one if DRAWSTRING ;; 3848;; R1 -- Untouched EXCEPT bit 15 is cleared. ;; 3849;; R4 -- Points just after displayed string. ;; 3850;; R5 -- Points just past end of string. ;; 3851;; R2 and R3 are not modified. ;; 3852;;==========================================================================;; 3853DRAWSTRING PROC 3854 3855DRAWSTRING5 3856 MVI@ R5, R0 3857 MVI@ R5, R1 3858 MVI@ R5, R4 3859DRAWSTRING1 3860 PSHR R5 3861 MOVR R0, R5 3862 SETC 3863 INCR PC 3864DRAWSTRING2: 3865 CLRC 3866 SLL R1, 1 3867 RRC R1, 1 3868 MVI@ R5, R0 ; Get first char of string 3869@@tloop: 3870 SUBI #32, R0 ; Shift ASCII range to charset 3871 SLL R0, 2 ; Move it to position for BTAB word 3872 SLL R0, 1 3873 XORR R1, R0 ; Merge with color info 3874 MVO@ R0, R4 ; Write to display 3875 MVI@ R5, R0 ; Get next character 3876 TSTR R0 ; Is it NUL? 3877 BNEQ @@tloop ; --> No, keep copying then 3878 3879 SLLC R1, 1 3880 ADCR R0 3881 SLR R1, 1 3882 ADDR R0, PC 3883 JR R5 3884 PULR PC 3885DRAWSTRING3: 3886 MVI@ R5, R1 3887DRAWSTRING4: 3888 MVI@ R5, R4 3889 B DRAWSTRING2 3890 ENDP 3891 3892;;==========================================================================;; 3893;; FILLZERO ;; 3894;; Fills memory with zeros ;; 3895;; ;; 3896;; FILLMEM ;; 3897;; Fills memory with a constant ;; 3898;; ;; 3899;; INPUTS: ;; 3900;; R0 -- Fill value (FILLMEM only) ;; 3901;; R1 -- Number of words to fill ;; 3902;; R4 -- Start of fill area ;; 3903;; R5 -- Return address ;; 3904;; ;; 3905;; OUTPUTS: ;; 3906;; R0 -- Zeroed (if FILLZERO) ;; 3907;; R1 -- Zeroed ;; 3908;; R4 -- Points to word after fill area ;; 3909;;==========================================================================;; 3910FILLZERO PROC 3911 CLRR R0 3912FILLMEM 3913 MVO@ R0, R4 3914 DECR R1 3915 BNEQ FILLMEM 3916 JR R5 3917 ENDP 3918 3919;;==========================================================================;; 3920;; SNDPRIO ;; 3921;; ;; 3922;; Task which prioritizes sound streams by giving stream #0 ;; 3923;; all of the channel bits that aren't being used by stream #1. This is ;; 3924;; called by the ISR during sound-stream updates. ;; 3925;; ;; 3926;; This task also implements the "Mute" functionality. ;; 3927;;==========================================================================;; 3928SNDPRIO PROC 3929 MVI SNDSTRM+4,R0 ; load current hold count for strm 2 3930 CLRR R1 3931 CMPI #2, R0 ; is it less than 2? 3932 BLT @@inactive ; yes: stream is inactive 3933 MVI SNDSTRM+7,R1 3934@@inactive: 3935 XORI #$3FFF, R1 3936 3937 MVI MUTE, R0 3938 TSTR R0 3939 BNEQ @@notmute 3940 ANDI #$7FF, R1 ; Disallow access to volume. 3941@@notmute: 3942 3943 MVO R1, SNDSTRM+3 3944 3945 JR R5 3946 ENDP 3947 3948;;==========================================================================;; 3949;; DOSNDSTREAM -- This is the sound engine! ;; 3950;; ;; 3951;; PSG memory map ;; 3952;; ;; 3953;; $1F4:$1F0 Channel A period (12 bits) ;; 3954;; $1F5:$1F1 Channel B period (12 bits) ;; 3955;; $1F6:$1F2 Channel C period (12 bits) ;; 3956;; $1F7:$1F3 Envelope period (16 bits) ;; 3957;; ;; 3958;; $1F8 Channel enables ;; 3959;; $1F9 Noise period (5 bits) ;; 3960;; $1FA Envelope mode ;; 3961;; ;; 3962;; $1FB Channel A volume ;; 3963;; $1FC Channel B volume ;; 3964;; $1FD Channel C volume ;; 3965;; ;; 3966;; $1FE, $1FF Hand controllers (not used for sound.) ;; 3967;; ;; 3968;; Sound is microprogrammed with a series of records of the following ;; 3969;; format: ;; 3970;; ;; 3971;; INSTRUCTION WORD (two formats): ;; 3972;; ;; 3973;; Format 1 (BREF = 0) ;; 3974;; +---+---+---+---+---+---+---+---+---+---+---+---+----+----+----+----+ ;; 3975;; | DURATION TO HOLD THIS FRAME |QUIT|ZERO|PCTL|BREF| ;; 3976;; +---+---+---+---+---+---+---+---+---+---+---+---+----+----+----+----+ ;; 3977;; ;; 3978;; Format 2 (BREF = 1) ;; 3979;; +---+---+---+---+---+---+---+---+---+---+---+---+----+----+----+----+ ;; 3980;; | BREF OFFSET RELATIVE TO $ - 2 | DUR. TO HOLD |QUIT|ZERO|PCTL|BREF| ;; 3981;; +---+---+---+---+---+---+---+---+---+---+---+---+----+----+----+----+ ;; 3982;; ;; 3983;; CONTROL WORD ;; 3984;; +---+----+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ;; 3985;; | 0 |LINK|snD|snC|snB|snA|sn9|sn8|sn7|sn6|sn5|sn4|sn3|sn2|sn1|sn0| ;; 3986;; +---+----+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ;; 3987;; ;; 3988;; DATA BYTES, 1 word for each two snX bits set. ;; 3989;; +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ;; 3990;; | SECOND BYTE TO WRITE | FIRST BYTE TO WRITE | ;; 3991;; +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ;; 3992;; ;; 3993;; LINK ADDRESS (if LINK set), 1 word, as-is. ;; 3994;; +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ;; 3995;; | ADDRESS TO CONTINUE SOUND PROCESSING AT. | ;; 3996;; +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ;; 3997;; ;; 3998;; ;; 3999;; Instruction Word details: ;; 4000;; ;; 4001;; The DUR field in the Instruction Word specifies how long a set of ;; 4002;; values is held in the PSG before it processes the next word. The ;; 4003;; duration is specified in ticks, where 1 tick == 1/60th second. A ;; 4004;; duration of 0 means "immediately"--the sound processor will start ;; 4005;; processing the next sound record as soon as it completes this one. ;; 4006;; This is useful for combining operations, such as setting ZERO & ;; 4007;; PCTL to zero the previous notes, and then sending new ones (if they ;; 4008;; are on different channels). ;; 4009;; ;; 4010;; The QUIT, ZERO, PCTL, BREF and LINK flags control the following ;; 4011;; behavior: ;; 4012;; ;; 4013;; QUIT When set, this terminates the sound given sound microprogram ;; 4014;; after the duration specified. ;; 4015;; ;; 4016;; ZERO When set, this tells the sound processor that no bytes are ;; 4017;; coded for this frame since all were zeros. This causes the ;; 4018;; processor to write a pattern of zeros guided by the control ;; 4019;; word. ;; 4020;; ;; 4021;; PCTL When set, this tells the sound processor to use the previous ;; 4022;; control word. The previous control word is not defined when ;; 4023;; the stream is started, but it is defined across a link. ;; 4024;; ;; 4025;; BREF When set, this frame is coded only as a back-reference. All ;; 4026;; control decles and data bytes come from the back reference. ;; 4027;; Note: When using a back-reference, the QUIT, ZERO, PCTL, and ;; 4028;; DUR settings are used from the Instruction Word for the ;; 4029;; _current_ frame! Also, the duration is limited to four bits ;; 4030;; when BREF=1, since the upper byte gives the back-reference ;; 4031;; relative offset. ;; 4032;; ;; 4033;; LINK When set, this tells the sound processor that this frame ;; 4034;; is followed by the address for the next frame. Otherwise, ;; 4035;; the sound processor assumes that the next frame is after this ;; 4036;; one. LINK is effectively a "jump after this" instruction. ;; 4037;; NOTE: LINK is actually in the Control Word, and is remembered ;; 4038;; when PCTL=1 in the next record. ;; 4039;; ;; 4040;; Control Word details: ;; 4041;; ;; 4042;; The bits sn0 ... snD report whether bytes are coded for each of the ;; 4043;; fourteen sound registers on the PSG. The PCTL bit in the Instruction ;; 4044;; word specifies whether the previous control word is reused for the ;; 4045;; current frame or is read from the stream. ;; 4046;; ;; 4047;; Sound Engine details: ;; 4048;; ;; 4049;; Up to two sound programs may be running at once. It is up to the ;; 4050;; programmer to ensure that they do not try to control the same channels ;; 4051;; at once. The intended use is to have music playing on one stream ;; 4052;; and sound effects on a second stream. ;; 4053;; ;; 4054;; The following state is kept for each active sound stream in 16-bit ;; 4055;; memory: ;; 4056;; ;; 4057;; HOLDCNT -- Number of ticks left for current record times 2 ;; 4058;; If the LSB of HOLDCNT is set, stream processing stops ;; 4059;; after this record. ;; 4060;; ;; 4061;; PREVCTL -- Previous control word. ;; 4062;; ;; 4063;; NEXTREC -- Pointer to the next stream record. ;; 4064;; ;; 4065;; STRMMSK -- Stream channel mask. This is used to prevent a ;; 4066;; stream from updating certain channel parameters and ;; 4067;; is most useful when, say, a sound-effect needs to ;; 4068;; borrow a channel briefly. ;; 4069;; ;; 4070;; NOTE: Bit 15 of control word (eg. bit 7 of second word) MUST BE 0. ;; 4071;; Otherwise, copy loop will loop forever. Also, bit 14 of stream ;; 4072;; channel mask MUST BE 0, otherwise the copy loop will write part ;; 4073;; of the link address to one of the hand controller addresses. :-) ;; 4074;; ;; 4075;;==========================================================================;; 4076DOSNDSTREAM: PROC 4077 MVI@ R5, R4 4078 PSHR R5 4079 4080 MVI@ R4, R1 ; Load tick/hold count (REC[0]) 4081 SUBI #2, R1 ; Count down the hold count 4082 BMI @@inactive ; If count goes negative, channel is 4083 ; inactive. 4084 BEQ @@nextrec ; If count goes to zero, load next 4085 ; sound record into the channel 4086 4087 DECR R4 ; Otherwise, store the updated count. 4088 MVO@ R1, R4 ; count ==> REC[0] 4089 4090@@inactive: 4091 PULR PC ; Return. 4092 4093@@backup: 4094 DECR R4 4095@@nextrec: 4096 MVI@ R4, R5 ; Load the stream record ptr (REC[1]) 4097 MVI@ R5, R2 ; Load hold tick-count from stream 4098 MOVR R2, R3 4099 SARC R2, 1 ; See if this is a back-reference 4100 BNC @@notbref 4101 ANDI #$7F, R2 ; Clear BREF offset out of upper bits. 4102 SWAP R3 4103 ANDI #$FF, R3 ; Extract BREF offset from upper 8 bits 4104 ADDI #2, R3 ; Make BREF relative to just before rec 4105 DECR R4 4106 MVO@ R5, R4 ; Store updated record ptr (REC[1]) 4107 SUBR R3, R5 ; Subtract relative offset 4108@@notbref: 4109 SARC R2, 1 ; See if we have new control word. 4110 BC @@prevctl ; If PCTL = 1, use prev control word. 4111 MVI@ R5, R1 ; Load new control-word from stream 4112 MVO@ R1, R4 ; Store updated ctrl-word to str info 4113 ; (REC[2]) 4114 INCR PC ; Skip the following MVI@. 4115@@prevctl: 4116 MVI@ R4, R1 ; Load previous ctrl-word from str info 4117 ; (REC[2]) 4118 4119 MVI@ R4, R0 ; Load the stream channel mask (REC[3]) 4120 SUBI #4, R4 ; Point back at hold tick-count 4121 SARC R2, 1 ; See if we are writing a zero-record 4122 BNEQ @@notzero 4123 ; If this was a zero hold count, set up so that we will process 4124 ; next record. This is almost like tail-recursion. 4125 MVII #@@backup, R3 4126 PSHR R3 4127@@notzero: 4128 MVO@ R2, R4 ; Store new tick count (REC[0]) 4129 4130 ; We generate a write mask by ANDing the stream channel mask 4131 ; with the control word. The result is a bit-mask describing 4132 ; which PSG registers we'll actually write to. 4133 ANDR R1, R0 ; Generate channel write mask 4134 MVII #$1F0, R2 ; Point to the PSG for writing. 4135 4136 BC @@zerorec ; If we're writing a zero record, 4137 ; don't read any bytes from stream. 4138 4139 PSHR R4 4140 MVII #1, R4 4141 COMR R0 4142 SARC R1, 1 ; 6 ; See if we have a coded word. 4143@@loop: 4144 BNC @@noread ; 9 7 ; If not, skip the read. 4145@@read: 4146 SWAP R3, 1 4147 XORI #1, R4 4148 ADDR R4, PC 4149 MVI@ R5, R3 ; 8 ; ... otherwise read word from stream 4150 SARC R0, 1 ; 6 ; See if we are to write this word. 4151 ADCR PC ; 6 4152 MVO@ R3, R2 ; 8 ; ... otherwise write to the PSG 4153 INCR R2 ; 6 ; Always update the PSG pointer. 4154 4155 SARC R1, 1 ; 6 ; See if we have a coded word. 4156 BNEQ @@loop ; 9 7 ; Yes: Keep looping! 4157 BC @@read ; ; affects last bit ONLY. 4158 4159@@finish: 4160 PULR R4 4161 4162 CMPI #$1FF, R2 ; Did we have a link pointer in this 4163 ; record? 4164 BLT @@nolink ; If not, then don't grab one here. 4165 MOVR R3, R5 ; Move link pointer to record pointer 4166 B @@waslink ; Skip the back-ref test. 4167@@nolink: 4168 CMP@ R4, R5 ; Detect if this was a back-ref 4169 BLE @@return ; If it was, our pointer didn't advance 4170 DECR R4 ; Else, prepare to store new rec ptr 4171@@waslink: 4172 MVO@ R5, R4 ; Store record ptr to stream info 4173@@return: 4174 PULR PC ; Return 4175 4176@@noread: 4177 INCR R2 ; 6 4178 SARC R0, 1 ; 6 4179 SARC R1, 1 ; 6 4180 BNEQ @@loop ; 9 7 4181 BC @@read ; ; affects last bit ONLY. 4182 B @@finish 4183 4184 ; 56 coded and written 4185 ; 48 coded, not written 4186 ; 36 not coded, not written 4187@@zerorec: 4188 CLRR R3 4189 SARC R0, 1 ; 6 ; See if we are to write this word. 4190@@zloop: 4191 BNC @@nozero ; 9 7 ; Skip write if not in write mask 4192 MVO@ R3, R2 ; 8 ; ... otherwise write to the PSG 4193@@nozero: 4194 INCR R2 ; 6 ; Always update the PSG0pointer. 4195 SARC R0, 1 ; 6 ; See if we are to write this word. 4196 BNEQ @@zloop ; 9 7 ; Yes: Keep looping! 4197 BNC @@zlink 4198 MVO@ R3, R2 4199 4200 ; We need to handle link records specially here. 4201@@zlink: 4202 SLLC R1, 2 ; Check bit 14. 4203 BNOV @@nolink ; If set, we have link, else, we don't 4204 MVI@ R5, R3 ; Load the link address 4205 MVO@ R3, R4 ; Store record ptr to stream info 4206 4207 PULR PC 4208 4209 ENDP 4210 4211 4212;;==========================================================================;; 4213;; PLAY.sfx ;; 4214;; Plays a sound effect on stream #1. ;; 4215;; ;; 4216;; PLAY.sfxp ;; 4217;; Plays a sound effect with raised priority on stream #1. ;; 4218;; ;; 4219;; PLAY.mus ;; 4220;; Plays a music stream on stream #0. ;; 4221;; ;; 4222;; PLAY.sfxn ;; 4223;; Same as PLAY.sfxp, only sound effect number is passed in R4 ;; 4224;; instead of in the invocation record. ;; 4225;; ;; 4226;; PLAY.musn ;; 4227;; Same as PLAY.mus, only sound effect number is passed in R4 ;; 4228;; instead of in the invocation record. ;; 4229;; ;; 4230;; INPUTS: ;; 4231;; R1 -- Priority (if PLAY.sfxp or PLAY.sfxn) ;; 4232;; R5 -- Invocation record (if PLAY.sfx, PLAY.sfxp, PLAY.mus) ;; 4233;; DECLE Index into SNDTBL. ;; 4234;; ;; 4235;; OUTPUTS: ;; 4236;; R1, R4 is clobbered ;; 4237;; Sound is triggered on requested channel. ;; 4238;; Returns at R5, or just after if invocation record is used. ;; 4239;;==========================================================================;; 4240PLAY PROC 4241 4242@@musn: CLRC ; Flag that this is music 4243 INCR PC ; (skip the SETC) 4244@@sfxn: SETC ; Flag that this is a sound effect 4245 B @@go2 ; Start the music/sound effect 4246 4247@@sfx: CLRR R1 ; If PLAY.sfx, set prio == 0. 4248@@sfxp: SETC ; Flag that this is a sound effect 4249 INCR PC ; (skip the CLRC) 4250@@mus: CLRC ; Flag that this is music. 4251 4252 4253@@go: MVI@ R5, R4 ; Read invocation record. 4254@@go2: PSHR R5 ; Save return address 4255 PSHR R0 ; Save R0 (minimize clobbering) 4256 4257 MVII #SNDSTRM+4, R5 ; point to sound effect stream 4258 BC @@notmusic 4259 SUBI #4, R5 ; If carry was set, point to sfx stream 4260 CLRR R1 4261 DECR R1 4262@@notmusic: 4263 4264 ; Note: The following is non-interruptible, and that's necessary 4265 ; to avoid a race w/ the sound code that runs in the ISR. 4266 DIS 4267 ; First see if something is already playing on this stream 4268 MVII #2, R0 4269 CMP@ R5, R0 ; Compare hold delay vs. 2. 4270 BGT @@notbusy ; If delay > 2, stream is busy 4271 4272 ; If the channel is busy, check our sound priority. This test is 4273 ; performed only for sound effects. (INCR R1/BEQ is always taken 4274 ; for music). 4275 INCR R1 ; If this is music, don't do prio. 4276 BEQ @@music ; 4277 4278 CMP SFXPRIO, R1 ; Now see if this sound effect is 4279 ; high enough priority to play. 4280 BLT @@noplay ; No... don't play it. 4281 4282@@play: 4283 MVO R1, SFXPRIO ; Update the sound effect priority 4284 4285@@music: 4286 ADDI #2, R5 ; Point to stream mask 4287 MVI@ R5, R0 ; Get the current stream mask 4288 SUBI #3, R5 ; Move the pointer back 4289 SWAP R0, 1 ; We're interested in vol bits 4290 ; and channel enables. 4291 4292 SLR R0, 2 ; Shift away bits we don't want 4293 4294 SARC R0, 2 ; ChanEn->C VolA->OV 4295 BNC @@no_chan_en ; ChanEn set? 4296 MVII #$38, R1 ;Yes... 4297 MVO R1, PSG0+10 ;Set channel enables to "Tone Only" 4298@@no_chan_en: 4299 CLRR R1 ; Prepare to write zeros. 4300 BNOV @@no_vol_a ; VolA set? 4301 MVO R1, PSG0+11 ;Yes... Clear Volume for channel A 4302 MVO R1, PSG0+0 ;Yes... Clear PeriodLo for channel A 4303 MVO R1, PSG0+4 ;Yes... Clear PeriodHi for channel A 4304@@no_vol_a: 4305 SARC R0, 2 ; VolB->C VolC->OV 4306 BNC @@no_vol_b ; VolB set? 4307 MVO R1, PSG0+12 ;Yes... Clear Volume for channel B 4308 MVO R1, PSG0+1 ;Yes... Clear PeriodLo for channel B 4309 MVO R1, PSG0+5 ;Yes... Clear PeriodHi for channel B 4310@@no_vol_b: 4311 BNOV @@no_vol_c ; VolC set? 4312 MVO R1, PSG0+13 ;Yes... Clear Volume for channel C 4313 MVO R1, PSG0+2 ;Yes... Clear PeriodLo for channel C 4314 MVO R1, PSG0+6 ;Yes... Clear PeriodHi for channel C 4315@@no_vol_c: 4316 B @@dorecord 4317 4318@@notbusy: 4319 INCR R1 4320 BEQ @@dorecord 4321 MVO R1, SFXPRIO ; Update the sound effect priority 4322@@dorecord: 4323 4324 ; At this point R4 is an index into the sound table 4325 ; and R5 points to the second word of the stream record. 4326 4327 ADDI #SNDTBL,R4 ; Point R5 into sound table 4328 4329 DECR R5 4330 MVII #2, R1 ; Start off with hold count = 2 4331 MVO@ R1, R5 ; Store hold count == 2 4332 MVI@ R4, R1 4333 MVO@ R1, R5 ; Store record pointer 4334 INCR R5 4335 MVI@ R4, R1 4336 MVO@ R1, R5 ; Store channel mask 4337 4338@@noplay: 4339 EIS 4340 PULR R0 4341 PULR PC 4342 ENDP 4343 4344;;==========================================================================;; 4345;; SOUND EFFECTS ;; 4346;; BOMB -- Plays a descending pitch following by an explosion. ;; 4347;; DING3 -- Dings 3 times (signals "next level") ;; 4348;; BOOM -- Just the explosion ;; 4349;; SILENCE -- The calm after the storm ;; 4350;;==========================================================================;; 4351BOMB 4352 DECLE 16 ; 1/60th second 4353 DECLE $2144 ; Channel C period/vol, Channel Enable 4354 DECLE $0010, $0C38 ; Period: $010, ChEnable: $38, Vol: $0C 4355 4356 DECLE 16 ; 1/60th second 4357 DECLE $04 ; Rapid descening pitch on channel C 4358 DECLE $18, $12, $20, $12, $30, $12, $40 4359 DECLE $12, $50, $12, $60, $12, $70, $12, $80 4360BOOM 4361 DECLE 800 ; Long delay (5/6ths second) 4362 DECLE $2788 ; Env & noise period, Ch.Enable, C Vol 4363 DECLE $28FF ; Env Period == $28FF 4364 DECLE $1F1C ; Noise on channel C only., period 1F 4365 DECLE $3F00 ; Envelope type = 0000 4366SILENCE 4367 DECLE 0 4368 DECLE $0100 4369 DECLE $38 ; No noise enabled 4370 4371 DECLE 12 4372 DECLE $3AFF ; All except envelope trigger 4373 4374BOOM2 4375 DECLE 4 ; No delay 4376 DECLE $3AFF ; Clear nearly everything 4377 4378 DECLE $3F0 ; Long delay 4379 DECLE $3F88 ; Env & noise period, Ch.Enable, Vols 4380 DECLE $1FFF 4381 DECLE $1F07 ; Noise on all channels, period 1F 4382 DECLE $3F00 ; Envelope type = 0000 4383 DECLE $3F3F ; Volume = 15 + Envelope 4384 4385 DECLE $0F0 ; Long delay 4386 DECLE $4000 4387 DECLE SILENCE 4388 4389BIP 4390 DECLE $20 ; 1/30th second 4391 DECLE $2144 ; Chan C 4392 DECLE $00FF, $0938 ; Period: $0FF, ChEnable: $38, Vol: $09 4393 4394 DECLE $0A ; 0/60th second, PCTL, QUIT 4395 DECLE $0000, $0038 ; Zero all but channel enables. 4396 4397BYUMP 4398 DECLE 16 ; 1/60th second 4399 DECLE $2144 ; Channel C period/vol, Channel Enable 4400 DECLE $0410, $0C38 ; Period: $510, ChEnable: $38, Vol: $0D 4401 DECLE 16 ; 1/60th second 4402 DECLE $04 ; Rapid descening pitch on channel C 4403 DECLE $50, $12, $90 4404 DECLE $10 ; Last frame, chain to SILENCE 4405 DECLE $4044 4406 DECLE $04E0 4407 DECLE SILENCE 4408 4409DING3 4410 DECLE 15*16 ; 1/4th second 4411 DECLE $35EE ; Channel C 4412 DECLE $6160, $00FF 4413 DECLE $1800 ; B = $060 C = $061, Env Per = $18FF 4414 DECLE $0038 ; Channel enables, Envelope type 0 4415 DECLE $3F3F ; Volume = 15 + envelope 4416 4417 DECLE 15*16 + 4 ; 1/4th second + zero 4418 DECLE $0400 ; Hit envelope register w/ Zero 4419 4420 DECLE 15*16 + 4 ; 1/4th second, + zero 4421 DECLE $4400 ; Hit envelope register w/ Zero & Link 4422 DECLE SILENCE ; Silence afterwards. 4423 4424BOMB4 4425 DECLE $14 ; One tick 4426 DECLE $3FFF ; Clear everything. 4427 ; (makes the 'gong' more consistent). 4428 4429 DECLE $10 ; 4430 DECLE $1DBB ; 4431 DECLE $1419, $07FF, $3807 4432 DECLE $0038, $3F3F 4433 4434 DECLE 16 ; 1/60th second 4435 DECLE $2044 ; Channel C period/vol, Channel Enable 4436 DECLE $0010, $000C ; Period: $010, Vol: $0C 4437 4438 DECLE 16 ; 1/60th second 4439 DECLE $04 ; Rapid descening pitch on channel C 4440 DECLE $18,$12,$20,$12,$28,$12,$30,$12,$38,$12,$40 4441 DECLE $12,$10,$12,$18,$12,$20,$12,$28,$12,$30,$12,$38,$12,$40 4442 DECLE $12,$10,$12,$18,$12,$20,$12,$28,$12,$30,$12,$38,$12,$40 4443 DECLE 0 4444 DECLE $4000, BOMB 4445 4446;;==========================================================================;; 4447;; Background music data ;; 4448;;==========================================================================;; 4449 INCLUDE "nut1mrch.asm" 4450 INCLUDE "chindnce.asm" 4451 INCLUDE "behappy.asm" 4452 4453;;==========================================================================;; 4454;; FONT ;; 4455;;==========================================================================;; 4456 INCLUDE "font.asm" 4457 4458 4459 DECLE "http://spatula-city.org/~im14u2c/intv/", 0 4460 DECLE "Copyright 2000 Joseph Zbiciak",0 4461 DECLE "JZ" 4462 DECLE 0 4463