Subject: [stella] The Reflex! From: "Lee Fastenau" <stella@xxxxxxxxxxxxxxx> Date: Tue, 1 Mar 2005 00:08:01 -0500 |
> Past > "level creation" contests on AtariAge have been very successful, and > such a design contest for Crazy Balloon would be the first for a new > 2600 homebrew game (the others have been for hacks of existing Atari > games). Well, this wasn't hosted by AtariAge... and the game has yet to be completed, but does this count? http://www.ioyu.com/io/atari/reflex/level_index.asp (And the super-secret, never-before-seen "view all" page) http://www.ioyu.com/io/atari/reflex/level_index_big.asp I haven't built the encoder for this particular data yet, but the decoder in the ROM itself is done... or at least the hugely bloated version of the decoder is done. Not the Jentzsch-ified 12-byte decoder. ;) And here's the latest (totally unplayable) source for Reflex. Feel free to poke around and criticize... oh, and definitely feel free to fix the paddle English bug that's driving me insane. Although I'm sure there aren't enough comments, there should be more there than last time. (After long periods away from the code, it helps to go back and step through functions, adding comments along the way.) Enjoy, (or sneer in disgust, take your pick) -Lee Archives (includes files) at http://www.biglist.com/lists/stella/archives/ Unsub & more at http://stella.biglist.com ; Codename: Reflex ; by Lee W. Fastenau ; 03/26/2004 ; Assemble with DASM using: ; dasm.exe reflex.asm -f3 -oreflex.bin ; (Ensure vcs.h and macro.h are in the same folder or specify the include path using the -I option) ; TO-DO ; -- HIGH PRIORITY -- ; OK Fix var allocation bug ; OK Driving control support ; W Improve state handling ; OK Add sound ; W Add ball velocity table ; W Add ball velocity multiplier ; OK Two-player controller support ; OK Two-player paddle update ; OK Two-player score kernel ; OK Two-player game kernel ; OK Better paddle reflection ; W Paddle "english" ; * Add "New game" state ; * Board clear (win) state ; * Improve pregame state ; OK Improve ball out detection ; * Improve ball out state ; OK Add "lives" counter ; OK Add PAL50/60 support ; OK "Bootup" video config (NTSC/PAL50/PAL60) ; * Fire button starts game ; * Build online level editor ; W Add level decompressor ; * Level selection using Game Select while holding Game Reset ; W Optimize program structure ; - Moved game kernel to beginning of ROM ; - Removed all ALIGNs ; - Changed game kernel to us skipdraw ; -- LOW PRIORITY -- ; * Add unique board designs ; * Re-code ball effects ; * Implement difficulty switches ; W Intro music ; * Fun scrolly bottom message ; OK Improve ALT_CHAR_SET data ; OK Better SECAM support (When Assembly Option set) ; -- FOR OKGE04 PREVIEW -- ; OK One-player paddle ; OK Two-player paddles ; OK Board clear detect and reset board ; OK Initial ball speed/direction ; OK Ball speed increase during game ; OK Two-player scoring ; OK Two-player win detect ; CLEANUP REMINDERS ; * Bring PF priority to front ; * Check default modes ; * Check debug constants ; * Check state delay values ; * Check for debug "ALIGN 256" or "ds.b" (in RAM is okay) processor 6502 include "vcs.h" include "macro.h" ; ------------------------------------ ; Global Macros ; ------------------------------------ MAC WARN_BOUNDARY .start SET {1} .end SET * IF >.start != >.end echo "Page boundary crossed (",{2},.start,"-",.end,")" ENDIF ENDM MAC REQUIRED_BOUNDARY .start SET {1} .end SET * IF >.start == >.end echo "Expected page boundary not crossed (",{2},.start,"-",.end,")" ENDIF ENDM ; ------------------------------------ ; Assembly Options ; ------------------------------------ SECAM_PALETTE equ 0 ; 0 = PAL palette, 1 = SECAM palette DEFAULT_GAME_MODE equ 0 ; See gameModes tab (currently modes 0-5 available) CHAR_SET equ 0 ; 0 = default, 1 = original VU_METERS equ 1 ; 0 = No VU meters, 1 = VU meters on title screen TITLE_SONG equ 2 ; 0 = Devil's Workshop ; 1 = Lost in Legoland ; ------------------------------------ ; DEBUG_CONSTANTS ; ------------------------------------ NO_MUSIC equ 0 ; Disable music (used for remaining sane during development) TEST_PADDLE equ 0 ; Temporary test paddle JOYSTICK_BALL equ 0 ; Control ball with joystick STATIC_BALL equ 0 ; Ball doesn't move NO_COLLISION equ 0 ; Turn collision/reflection off (always off when JOYSTICK_BALL is 1) NO_REMOVE equ 0 ; Turn brick removal off NO_PADDLE equ 0 ; Solid border, no paddle FAST_BALL equ 0 ; Make ball move really fast DEBUG_COLLISION equ 0 ; Use leftmost digit of score to show last collision INFINITE_LIVES equ 0 ; Hmm... what's this do? ; ------------------------------------ ; Constant declarations ; ------------------------------------ white equ $0E ; The NTSC color palette lightgreen equ $CC green equ $CC yellow equ $2E orange equ $2A darkgray equ $02 black equ $00 lightblue equ $8A lightred equ $3A scoreColor equ yellow scoreBgColor equ black bgColor equ darkgray paddleColor equ yellow livesColor equ orange ballHighlight equ white ballShadow equ green flashColor1 equ white flashColor2 equ white paddle0Color equ lightblue paddle1Color equ lightred IF !SECAM_PALETTE PAL_white equ $0E ; Und die PAL Farbe Palette PAL_lightgreen equ $5C PAL_green equ $58 PAL_yellow equ $2E PAL_orange equ $2A PAL_darkgray equ $02 PAL_black equ $00 PAL_lightblue equ $BA PAL_lightred equ $6A PAL_scoreColor equ PAL_yellow PAL_scoreBgColor equ PAL_black PAL_bgColor equ PAL_darkgray PAL_paddleColor equ PAL_yellow PAL_livesColor equ PAL_orange PAL_ballHighlight equ PAL_white PAL_ballShadow equ PAL_green PAL_flashColor1 equ PAL_white PAL_flashColor2 equ PAL_white PAL_paddle0Color equ PAL_lightblue PAL_paddle1Color equ PAL_lightred ELSE PAL_white equ $0E ; La palette de couleur de SECAM PAL_lightgreen equ $0E PAL_green equ $08 PAL_yellow equ $0C PAL_orange equ $0C PAL_darkgray equ $00 PAL_black equ $00 PAL_lightblue equ $02 PAL_lightred equ $04 PAL_scoreColor equ PAL_yellow PAL_scoreBgColor equ PAL_black PAL_bgColor equ PAL_darkgray PAL_paddleColor equ PAL_white PAL_livesColor equ PAL_white PAL_ballHighlight equ PAL_white PAL_ballShadow equ PAL_green PAL_flashColor1 equ PAL_white PAL_flashColor2 equ PAL_white PAL_paddle0Color equ PAL_lightblue PAL_paddle1Color equ PAL_lightred ENDIF deathDelay equ 50 pregameDelay equ 50 clearDelay equ 180 playfieldWidth equ 32 playfieldHeight equ 16 paddleBound equ 46 paddle1Width equ 16 paddle2Width equ 6 rowHeight equ 8 flashDuration equ 5 ballStartX equ 83 ballStartY equ 22 startingLives equ 3 state_options equ 0 state_newgame equ 1 state_newlevel equ 2 state_pregame equ 3 state_ingame equ 4 state_ballout equ 5 state_gameover equ 6 state_clear equ 7 sfxBrickFreq equ 4 sfxPaddleFreq equ 9 sfxEchoDelay equ 4 IF !NO_MUSIC musicVolume equ 15 ELSE musicVolume equ 0 ENDIF brickScore equ $0020 ; Score is BCD, so use hex... (weird, huh?) levelScore equ $1000 p1score equ score p1scoreDigit equ digit5 p2score equ score+1 p2scoreDigit equ digit3 ball_minSpeed equ 1 ; Base multiplier for speed ball_maxSpeed equ 10 ; Max multiplier for speed mode_preset equ %00011111 ; Inverted mode_flags equ %11101100 ; Inverted mode_switched equ %00000100 ; 1=switched last frame, 0=not switched mode_twoplayers equ %00000001 ; 1=two players, 0=one player mode_twoscores equ %00000010 ; 1=competition, 0=cooperative/one player mode_driving equ %00010000 ; 1=driving controller, 0=joystick (ALWAYS d4) mode_oneplayer equ 0 ; This is here to make the gameModes tab more readable mode_onescore equ 0 ; This is here to make the gameModes tab more readable mode_joystick equ 0 ; This is here to make the gameModes tab more readable flag_lives equ %00000111 ; Life counter flag_lastcbsw equ %00001000 ; Last color/bw switch state flag_palColors equ %10000000 ; PAL color palette flag flag_pal50 equ %01000000 ; PAL 50Hz flag (else 60Hz) seg.u vars variables org $80 ; ------------------------------------ ; Variable declarations ; ------------------------------------ blockPtr dc.w ; Pointer for removing blocks blockData block1l ds.b playfieldHeight ; Columns 0-7 block2l ds.b playfieldHeight ; Columns 8-15 block2r ds.b playfieldHeight ; Columns 16-23 block1r ds.b playfieldHeight ; Columns 24-31 temp dc.b ; Temp storage gameMode dc.b ; See mode bits above gameState dc.b ; See state values above gameFlags dc.b ; Other game flags stateTimer dc.b ; General timer levelByte dc.b ; Compressed level data byte index levelBit dc.b ; Compressed level data bit index ballx dc.w ; Ball x pos bally dc.w ; Ball y pos ballmx dc.w ; Ball x move ballmy dc.w ; Ball y move ballDirection dc.b ; Ball direction ballSpeed dc.b ; Ball speed lineCount ; Shares space with blockX blockX dc.b ; Ball block x location blockY dc.b ; Ball block y location offsetX dc.b ; Collide x offset offsetY dc.b ; Collide y offset tryX dc.b ; Ball collision PF bit pattern tryY dc.b ; Y offset for ball collision tryX_store dc.b oldSWCHA dc.b ; Controller nibbles (Player 1 & 2) paddle0 dc.b ; Paddle movement (Player 1) paddle1 dc.b ; Paddle movement (Player 2) paddlePos dc.w ; Paddle position (player 1 & 2) paddleSize dc.w ; Paddle size -4 (player 1 & 2) blockSection dc.b ; Section 0-3 pcol dc.b ; Paddle color for 1-player mode / wall color p0col dc.b ; Paddle colors for 2-player mode p1col dc.b digit0 dc.w ; Score digit pointers digit1 dc.w digit2 dc.w digit3 dc.w digit4 dc.w digit5 dc.w score ds.b 3 ; BCD score (1 nibble per digit) blockCount dc.b ; Number of bricks remaining sfxTimer0 dc.b sfxEcho0 dc.b sfxVolume0 dc.b noteTimer equ score noteCounter equ score+2 noteVolume equ sfxEcho0 stack ds.w 3 ; Reserved for 3 levels of JSR's echo "------------------------------------" echo "RAM Bytes Used:",[*-variables]d echo "RAM Bytes Free:",[$100-*]d seg prog program org $F000 ; ; gameArea (130 scanlines) ; gameArea subroutine ldy #playfieldHeight-1 ldx bally inx lda #rowHeight-1 sta lineCount lda p0col sta COLUPF lda #>rowColor sta tryY jmp setColors skipdraw0a lda #0 ;+2 69 9 beq skipreturn0a ;+3 72 12 skipdraw0b lda #0 ;+2 72 13 beq skipreturn0b ;+3 75 16 startGameKernel sta WSYNC SLEEP 60 ;+60 60 Running start, so to speak row0 .loop1 dex ;+2 62 2 Skipdraw in all its wonderful glory! cpx #ballHeight ;+2 64 4 http://www.biglist.com/lists/stella/archives/200309/msg00056.html bcs skipdraw0a ;+2/+3 66/67 6/7 lda ballShape,x ;+4 70 10 SLEEP 2 ;+2 72 12 skipreturn0a sta GRP1 ;+3 75 15 SLEEP 13 ;+13 12 lda block1l,y ;+4 16 sta PF1 ;+3 *19* Update left half of playfield lda block2l,y ;+4 23 sta PF2 ;+3 26 SLEEP 8 ;+8 34 lda block1r,y ;+4 38 sta PF1 ;+3 41 lda block2r,y ;+4 45 sta PF2 ;+3 *48* Update right half of playfield SLEEP 4 ;+4 52 dec lineCount ;+5 57 bne .loop1 ;+3/2 *60*/59 WARN_BOUNDARY row0,"row0 INSIDE" dex ;+2 61 cpx #ballHeight ;+2 63 bcs skipdraw0b ;+2/+3 65/66 lda ballShape,x ;+4 69 SLEEP 2 ;+2 71 skipreturn0b sta GRP1 ;+3 74 SLEEP 12 ;+12 10 lda block1l,y ;+4 14 sta PF1 ;+3 *17* Update left half of playfield lda block2l,y ;+4 21 sta PF2 ;+3 24 lda #rowHeight-1 ;+2 26 sta lineCount ;+3 29 SLEEP 5 ;+5 34 lda block1r,y ;+4 38 sta PF1 ;+3 41 lda block2r,y ;+4 45 sta PF2 ;+3 *48* Update right half of playfield SLEEP 13 ;+13 61 dey ;+2 63 bne .enterHere ;+3 66 Unconditional branch WARN_BOUNDARY row0,"row0 OUTSIDE" skipdraw1a lda #0 ;+2 75 beq skipreturn1a ;+3 2 skipdraw1b lda #0 ;+2 68 jmp skipreturn1b ;+3 71 rows1thru14 .loop2 lda pcol ;+3 63 sta COLUPF ;+3 66 Set paddle color .enterHere dex ;+2 68 cpx #ballHeight ;+2 70 bcs skipdraw1a ;+2/+3 72/73 WARN_BOUNDARY skipdraw1a,"skipdraw1a" lda ballShape,x ;+4 76 SLEEP 2 ;+2 2 skipreturn1a sta.w GRP1 ;+4 6 lda pcol ;+3 9 sta COLUPF ;+3 12 lda block1l,y ;+4 16 sta PF1 ;+3 *19* Update left half of playfield lda block2l,y ;+4 23 sta PF2 ;+3 26 lda (tryX),y ;+5 31 sta COLUPF ;+3 *34* Set board color lda block1r,y ;+4 38 sta PF1 ;+3 41 lda block2r,y ;+4 45 sta PF2 ;+3 *48* Update right half of playfield SLEEP 4 ;+4 52 dec lineCount ;+5 57 bne .loop2 ;+3/2 *60*/59 WARN_BOUNDARY rows1thru14,"rows1thru14 INSIDE" lda pcol ;+3 62 sta COLUPF ;+3 65 Set paddle color dex ;+2 61 cpx #ballHeight ;+2 63 bcs skipdraw1b ;+2/+3 65/66 WARN_BOUNDARY skipdraw1b,"skipdraw1b" lda ballShape,x ;+4 69 SLEEP 2 ;+2 71 skipreturn1b sta GRP1 ;+3 74 lda pcol ;+3 7 sta COLUPF ;+3 10 lda block1l,y ;+4 14 sta PF1 ;+3 *17* Update left half of playfield lda block2l,y ;+4 21 sta PF2 ;+3 24 lda #rowHeight-1 ;+2 26 sta lineCount ;+3 29 lda #0 ;+2 31 sta COLUPF ;+3 *34* Set board color lda block1r,y ;+4 38 sta PF1 ;+3 41 lda block2r,y ;+4 45 sta PF2 ;+3 *48* Update right half of playfield SLEEP 3 ;+3 51 lda pcol ;+3 54 dey ;+2 56 bne .loop2 ;+4/+2 60/58 REQUIRED_BOUNDARY rows1thru14,"rows1thru14 OUTSIDE" sta COLUPF ;+3 61 jmp .enterHere1 ;+3 64 row15 .loop3 SLEEP 4 ;+4 64 .enterHere1 dex ;+2 66 cpx #ballHeight ;+2 68 bcs skipdraw2a ;+2/+3 70/71 lda ballShape,x ;+4 74 SLEEP 2 ;+2 76 skipreturn2a sta GRP1 ;+3 3 SLEEP 3 ;+3 6 lda p1col ;+3 9 sta COLUPF ;+3 12 lda block1l,y ;+4 16 sta PF1 ;+3 *19* Update left half of playfield lda block2l,y ;+4 23 sta PF2 ;+3 26 SLEEP 8 ;+8 34 lda block1r,y ;+4 38 sta PF1 ;+3 41 lda block2r,y ;+4 45 sta PF2 ;+3 *48* Update right half of playfield SLEEP 4 ;+4 52 dec lineCount ;+5 57 bne .loop3 ;+3/2 *60*/59 WARN_BOUNDARY row15,"row15 INSIDE" dex ;+2 61 cpx #ballHeight ;+2 63 bcs skipdraw2b ;+2/+3 65/66 lda ballShape,x ;+4 69 SLEEP 2 ;+2 71 skipreturn2b sta GRP1 ;+3 74 SLEEP 12 ;+12 10 lda block1l,y ;+4 14 sta PF1 ;+3 *17* Update left half of playfield lda block2l,y ;+4 21 sta PF2 ;+3 24 lda #rowHeight-1 ;+2 26 sta lineCount ;+3 29 SLEEP 5 ;+5 34 lda block1r,y ;+4 38 sta PF1 ;+3 41 lda block2r,y ;+4 45 sta PF2 ;+3 *48* Update right half of playfield dey ;+2 50 WARN_BOUNDARY row15,"row15 OUTSIDE" SLEEP 26 ;+26 76 WHEW! lda #$00 ;Clear playfield (1 scanline) sta PF0 sta PF1 sta PF2 sta GRP1 rts skipdraw2a lda #0 ;+2 75 jmp skipreturn2a ;+3 2 skipdraw2b lda #0 ;+2 68 jmp skipreturn2b ;+3 71 setColors bit gameFlags bmi .palColors lda #<rowColor sta tryX lda #ballHighlight sta COLUP0 lda #ballShadow sta COLUP1 jmp startGameKernel .palColors lda #<PAL_rowColor sta tryX lda #PAL_ballHighlight sta COLUP0 lda #PAL_ballShadow sta COLUP1 jmp startGameKernel ; ; scoreArea (22 scanlines) ; scoreArea subroutine lda #0 sta GRP0 sta GRP1 lda gameMode and #mode_twoscores beq .oneScore .twoScores sta WSYNC ; Sync up so we can do some rough positioning sleep 29 sta RESP0 ; Position left score sleep 26 sta RESP1 ; Position right score sta WSYNC ; Sync again and lda #$0E ; Set color sta COLUBK ; Store in register sta WSYNC lda #$00 ; Set color sta COLUBK ; Store in register lda p0col sta COLUP0 lda p1col sta COLUP1 ldy #15 .loop sta WSYNC sty lineCount tya lsr tay lda (p1scoreDigit),y sta GRP0 lda (p2scoreDigit),y sta GRP1 ldy lineCount dey bne .loop sta WSYNC sta WSYNC lda #0 sta GRP0 sta GRP1 jmp .end .oneScore sta WSYNC ; Score positioning begins at hblank ; Thus, the counting begins... ;cycles total pixel position SLEEP 37 ;37 37 43px (43px = 37 cycles * 3 - 68 hblank) sta RESP0 ;+3 40 52px Since TIA moves at 3 color clocks [or pixels] sta RESP1 ;+3 43 61px per cycle, we have to multiply cycles by 3. lda #$10 ; Nudge P0 1px left (P0 is at 51px, theoretically) sta HMP0 lda #$20 ; Nudge P1 2px left (p1 is at 59px, theoretically) sta HMP1 sta WSYNC ; Sync again and lda #$0E ; Set color sta COLUBK ; Store in register bit gameFlags bmi .pal0 lda #scoreColor jmp .endPal0 .pal0 lda #PAL_scoreColor .endPal0 sta COLUP0 ; Set the color registers for P0 sta COLUP1 ; And P1 lda #1 sta VDELP0 ; Set the delay registers for P0 sta VDELP1 ; And P1 ... Why? Visit this link: ; http://www.biglist.com/lists/stella/archives/199704/msg00137.html lda #3 sta NUSIZ0 ; Set the number size registers to show three clones for P0 sta NUSIZ1 ; And (you guessed it) P1 ldy #15 ; This will be our row counter for the score ; Counts down 15 to 0 scoreKernel sta WSYNC ; And let's count again... sta HMOVE ;+3 3 Move lda #scoreBgColor ;+2 5 Set background color for score sta COLUBK ;+3 8 Store in register SLEEP 54 ;+54 62 The score loop doesn't use WSYNC, ; so we have to burn cycles to kind ; of "hit the ground running" .score ;cycles total pixel position sty lineCount ;+3 65 Y is multi-use, so we have to store it tya ;+2 70 Move Y to A lsr ;+2 72 and divide by 2 tay ;+2 74 to give us double height Y offset lda (digit0),y ;+5 76 ; This is where HBLANK begins! ; Don't need WSYNC when exactly 76 cycles are used sta GRP0 ;+3 3 lda (digit1),y ;+5 8 sta GRP1 ;+3 11 VDELP0/1 are integral to how this works lda (digit2),y ;+5 16 See an EXCELLENT description of exactly sta GRP0 ;+3 19 what's happening here: lda (digit5),y ;+5 24 sta temp ;+3 27 http://www.biglist.com/lists/stella/archives/199704/msg00137.html lda (digit4),y ;+5 32 tax ;+2 34 lda (digit3),y ;+5 39 ldy temp ;+3 42 1st digit begins displaying here sta GRP1 ;+3 45 (see score positioning above to see why) stx GRP0 ;+3 48 sty GRP1 ;+3 51 sta GRP0 ;+3 54 ldy lineCount ;+3 57 dey ;+2 59 bpl .score ;+3 62 sta WSYNC ; Blank row WARN_BOUNDARY scoreKernel,"scoreKernel" lda #0 ; Here, we reset the graphics registers sta GRP0 sta GRP1 sta GRP0 sta GRP1 sta VDELP0 sta VDELP1 sta NUSIZ0 sta NUSIZ1 .end sta WSYNC ; White line below score lda #$0E sta COLUBK sta WSYNC ; Black row lda #bgColor bit gameFlags bpl .skipPalColors lda #PAL_bgColor .skipPalColors sta COLUBK rts ; ; positionBall (2 scanlines) ; positionBall subroutine ldx #1 ; Positioning code by: RMundschau on Stella list (THANKS!) ; Inputs: A = Desired position. ; X = Desired object to be positioned (0-5). ; Outputs: X = unchanged ; A = Fine Adjustment value. ; Y = the "remainder" of the division by 15 minus an additional 15. lda ballx sta WSYNC ; 00 Sync to start of scanline. sec ; 02 Set the carry flag so no borrow will be applied during the division. positionBallLoop1 .loop1 sbc #15 ; 04 ; Waste the necessary amount of time dividing X-pos by 15! bcs .loop1 ; 06/07 - 11/16/21/26/31/36/41/46/51/56/61/66 WARN_BOUNDARY positionBallLoop1,"positionBallLoop1" tay ; 08 ; At this point the value in A is -1 to -15. In this code I use a table ; to quickly convert that value to the fine adjust value needed. lda fineAdjustTable,Y ; 13 -> Consume 5 cycles by guaranteeing we cross a page boundary sta HMP0,X ; 17 Store the fine adjustment value. sta RESP0,X ; 21/ 26/31/36/41/46/51/56/61/66/71 - Set the rough position. sta WSYNC sta HMOVE rts ; ------------------------------------ ; Data ; ------------------------------------ ballShape dc.b %00001000 ; Ball dc.b %00010100 dc.b %00011000 dc.b %00011100 dc.b %00011100 dc.b %00001000 ballHeight equ *-ballShape titleData titlePF1l dc.b %10000101 dc.b %10000101 dc.b %10000101 dc.b %10000101 dc.b %10000110 dc.b %10000101 dc.b %10000101 dc.b %10000111 titlePF2l dc.b %00101110 dc.b %00100010 dc.b %00100010 dc.b %00100010 dc.b %01100110 dc.b %00100010 dc.b %00100010 dc.b %11101110 titlePF2r dc.b %01101110 dc.b %01001000 dc.b %01001000 dc.b %01001000 dc.b %01001100 dc.b %01001000 dc.b %01001000 dc.b %01001110 titlePF1r dc.b %10000101 dc.b %10000101 dc.b %10000101 dc.b %10000101 dc.b %10000010 dc.b %10000101 dc.b %10000101 dc.b %10000101 ;WARN_BOUNDARY titleData,"titleData" ; (Page crossing OK here) rowColorData rowColor dc.b paddleColor ; Paddle and brick color (NTSC) dc.b paddleColor dc.b paddleColor dc.b paddleColor dc.b lightred+4 dc.b lightred+2 dc.b lightred dc.b lightred-2 dc.b lightblue+4 dc.b lightblue+2 dc.b lightblue dc.b lightblue-2 dc.b paddleColor dc.b paddleColor dc.b paddleColor dc.b paddleColor IF !SECAM_PALETTE PAL_rowColor dc.b PAL_paddleColor ; Paddle and brick color (PAL) dc.b PAL_paddleColor dc.b PAL_paddleColor dc.b PAL_paddleColor dc.b PAL_lightred+4 dc.b PAL_lightred+2 dc.b PAL_lightred dc.b PAL_lightred-2 dc.b PAL_lightblue+4 dc.b PAL_lightblue+2 dc.b PAL_lightblue dc.b PAL_lightblue-2 dc.b PAL_paddleColor dc.b PAL_paddleColor dc.b PAL_paddleColor dc.b PAL_paddleColor ELSE PAL_rowColor dc.b PAL_paddleColor ; Paddle and brick color (SECAM) dc.b PAL_paddleColor dc.b PAL_paddleColor dc.b PAL_paddleColor dc.b PAL_lightred dc.b PAL_lightred dc.b PAL_lightred dc.b PAL_lightred dc.b PAL_lightblue dc.b PAL_lightblue dc.b PAL_lightblue dc.b PAL_lightblue dc.b PAL_paddleColor dc.b PAL_paddleColor dc.b PAL_paddleColor dc.b PAL_paddleColor ENDIF WARN_BOUNDARY rowColorData,"rowColorData" livesTab dc.b %00000000 ; Also used for VU meters dc.b %01000000 dc.b %01010000 dc.b %01010100 dc.b %01010101 WARN_BOUNDARY livesTab,"livesTab" singleBrickTab dc.b %10000000 ; Thanks Manuel! :) dc.b %01000000 dc.b %00100000 dc.b %00010000 dc.b %00001000 dc.b %00000100 dc.b %00000010 dc.b %00000001 WARN_BOUNDARY singleBrickTab,"singleBrickTab" doubleBrickTab dc.b %11000000 dc.b %11000000 dc.b %00110000 dc.b %00110000 dc.b %00001100 dc.b %00001100 dc.b %00000011 dc.b %00000011 WARN_BOUNDARY doubleBrickTab,"doubleBrickTab" dx1 equ 8 ; Ball velocity lookup dx2 equ 22 dx3 equ 33 dx4 equ 39 dy1 equ (dx4*2) dy2 equ (dx3*2) dy3 equ (dx2*2) dy4 equ (dx1*2) deltaXTab dc.b dx1,dx2,dx3,dx4 deltaYTab dc.b dy1,dy2,dy3,dy4 englishTab dc.b -2 ; "English" as in paddle english, dc.b -1 ; not language... okay? dc.b 0 dc.b 0 dc.b 0 dc.b 0 dc.b 1 dc.b 2 gameModes dc.b mode_joystick | mode_oneplayer | mode_onescore ; Game mode 0 dc.b mode_joystick | mode_twoplayers | mode_twoscores ; Game mode 1 dc.b mode_joystick | mode_twoplayers | mode_onescore ; Game mode 2 dc.b mode_driving | mode_oneplayer | mode_onescore ; Game mode 3 dc.b mode_driving | mode_twoplayers | mode_twoscores ; Game mode 4 dc.b mode_driving | mode_twoplayers | mode_onescore ; Game mode 5 ;WARN_BOUNDARY gameModes,"gameModes" ; (Page crossing OK here) gameModesAvailable dc.b *-gameModes spinRightTab dc.b $DD ; 1100 -> 1101 dc.b $FF ; 1101 -> 1111 dc.b $CC ; 1110 -> 1100 dc.b $EE ; 1111 -> 1110 WARN_BOUNDARY spinRightTab,"spinRightTab" spinLeftTab dc.b $EE ; 1100 -> 1110 dc.b $CC ; 1101 -> 1100 dc.b $FF ; 1110 -> 1111 dc.b $DD ; 1111 -> 1101 WARN_BOUNDARY spinLeftTab,"spinLeftTab" playerMask dc.b $F0 dc.b $0F WARN_BOUNDARY playerMask,"playerMask" IF CHAR_SET==0 digitData hex 7C FE C6 C6 C6 FE 7C 00 ;0 hex 18 18 18 18 18 18 38 38 ;1 hex FE FE C0 FC 7E 06 FE FC ;2 hex FC FE 06 FE FE 06 FE FC ;3 hex 06 06 06 7E FE C6 C6 C6 ;4 hex FC FE 06 FE FC C0 FE FE ;5 hex 7C FE C6 FE FC C0 FC 7C ;6 hex 06 06 06 06 06 06 FE FC ;7 hex 7C FE C6 FE FE C6 FE 7C ;8 hex 7C 7E 06 7E FE C6 FE 7C ;9 hex 00 FE 86 AA AA AA FE 00 ;WIN hex 00 FE C6 DE DE DE FE 00 ;LOSE WARN_BOUNDARY digitData,"digitData" ELSE digitData hex 7C C6 C6 C6 C6 C6 C6 7C ;0 hex FE 18 18 18 18 78 38 18 ;1 hex FE C0 60 3C 06 06 C6 7C ;2 hex 7C C6 06 06 3C 06 C6 7C ;3 hex 0C 0C FE CC CC CC 6C 0C ;4 hex 7C C6 06 06 FC C0 C0 FE ;5 hex 7C C6 C6 C6 FC C0 60 38 ;6 hex 30 30 30 30 18 0C 06 FE ;7 hex 7C C6 C6 C6 7C C6 C6 7C ;8 hex 38 0C 06 7E C6 C6 C6 7C ;9 hex 00 FE 86 AA AA AA FE 00 ;WIN hex 00 FE C6 DE DE DE FE 00 ;LOSE WARN_BOUNDARY digitData,"digitData" ENDIF fineAdjustBegin dc.b %01110000 ; Left 7 dc.b %01100000 ; Left 6 dc.b %01010000 ; Left 5 dc.b %01000000 ; Left 4 dc.b %00110000 ; Left 3 dc.b %00100000 ; Left 2 dc.b %00010000 ; Left 1 dc.b %00000000 ; No movement. dc.b %11110000 ; Right 1 dc.b %11100000 ; Right 2 dc.b %11010000 ; Right 3 dc.b %11000000 ; Right 4 dc.b %10110000 ; Right 5 dc.b %10100000 ; Right 6 dc.b %10010000 ; Right 7 fineAdjustTable EQU fineAdjustBegin - %11110001 ; NOTE: %11110001 = -15 WARN_BOUNDARY fineAdjustBegin,"fineAdjustBegin" optionData optionJS dc.b %01111110 dc.b %10100001 dc.b %11010001 dc.b %10100001 dc.b %11111111 dc.b %11100111 dc.b %11011011 dc.b %11111111 dc.b %01010000 dc.b %00011000 dc.b %00010000 dc.b %00011000 dc.b %00010000 dc.b %00011000 dc.b %00010000 dc.b %00011000 optionDC dc.b %00011000 dc.b %00111100 dc.b %01111110 dc.b %01011010 dc.b %11011011 dc.b %10011001 dc.b %10011001 dc.b %10111101 dc.b %11111111 dc.b %11000011 dc.b %10000001 dc.b %11000011 dc.b %01000010 dc.b %01100110 dc.b %00111100 dc.b %00011000 WARN_BOUNDARY optionData,"optionData" levelData dc.b %00001100 ; One entire compressed level! levelDataTable dc.b %00010101 ; Huffman table used for decompression dc.b $80 | 2 dc.b %00010000 dc.b %01000000 levelBrickTable dc.b %00000000 ; Brick layout lookup table dc.b %11000000 dc.b %00110000 dc.b %11110000 dc.b %00001100 dc.b %11001100 dc.b %00111100 dc.b %11111100 dc.b %00000011 dc.b %11000011 dc.b %00110011 dc.b %11110011 dc.b %00001111 dc.b %11001111 dc.b %00111111 dc.b %11111111 echo "Level Data Length:",[*-levelData]d IF TITLE_SONG==0 ; Devil's Workshop echo "Title Song: Devil's Workshop" melodyChannel dc.b 4 ; High notes dc.b 10 ; Low notes beatChannel equ 8 ; White noise beatHi equ $80 | 8 ; Snare drum beatLo equ $80 | 31 ; Bass drum noteData0 dc.b -96 dc.b 30,96 dc.b 27,48 dc.b 25,48 dc.b 30,72 dc.b 27,24 dc.b -96 dc.b 30,96 dc.b 25,24 dc.b 25,24 dc.b 25,48 dc.b 27,72 dc.b 27,24 dc.b -96 dc.b 30,96 dc.b 27,96 dc.b 25,96 dc.b -96 dc.b 30,96 dc.b 27,96 dc.b 25,96 dc.b -96 dc.b -96 measureLength0 equ *-noteData0 noteData1 dc.b 17,23 dc.b beatLo,1 dc.b beatHi,24 dc.b 15,24 dc.b 15,23 dc.b beatLo,1 measureLength1 equ *-noteData1 noteOffset dc.b 0 measureLength dc.b measureLength0 dc.b measureLength1 echo "Music Data Length:",[*-noteData0]d ENDIF IF TITLE_SONG==1 ; Lost in Legoland echo "Title Song: Lost in Legoland" melodyChannel dc.b 7 ; High notes dc.b 6 ; Low notes beatChannel equ 8 ; White noise beatHi equ $80 | 8 ; Snare drum beatLo equ $80 | 31 ; Bass drum noteData0 IF 0 ; 0 = Music, 1 = Silence dc.b -96 ELSE dc.b 16,80 dc.b 21,20 dc.b 17,40 dc.b 17,40 dc.b 16,60 dc.b 19,20 dc.b 14,40 dc.b 16,60 dc.b 14,40 dc.b 21,20 dc.b 17,40 dc.b 17,40 dc.b 16,60 dc.b 19,20 dc.b 14,40 dc.b -20 dc.b 16,80 dc.b 21,20 dc.b 17,40 dc.b 17,40 dc.b 16,60 dc.b 19,20 dc.b 14,40 dc.b 16,60 dc.b 14,40 dc.b 21,20 dc.b 17,40 dc.b 17,40 dc.b 16,60 dc.b 19,20 dc.b 14,40 dc.b -20 dc.b -80 dc.b -80 dc.b -80 dc.b -80 dc.b -80 dc.b -80 dc.b -80 dc.b -80 ENDIF measureLength0 equ *-noteData0 noteData1 bassLo equ 19 bassHi equ 9 IF 0 ; 0 = Music, 1 = Silence dc.b -96 ELSE dc.b bassHi,17 dc.b beatHi,3 dc.b -7 dc.b beatLo,3 dc.b bassHi,10 dc.b bassHi,9 dc.b beatLo,1 dc.b -9 dc.b beatLo,1 dc.b bassLo,9 dc.b beatHi,1 dc.b bassLo,10 dc.b -10 dc.b bassLo,18 dc.b beatLo,2 dc.b -9 dc.b beatHi,1 dc.b -9 dc.b beatLo,1 dc.b bassLo,8 dc.b beatHi,2 dc.b bassHi,19 dc.b beatLo,1 ENDIF measureLength1 equ *-noteData1 noteOffset dc.b 0 measureLength dc.b measureLength0 dc.b measureLength1 echo "Music Data Length:",[*-noteData0]d ENDIF IF TITLE_SONG==2 ; new echo "Title Song: new" melodyChannel dc.b 4 ; High notes dc.b 10 ; Low notes beatChannel equ 8 ; White noise beatHi equ $80 | 8 ; Snare drum beatLo equ $80 | 31 ; Bass drum noteData0 dc.b 28,12 dc.b 28,12 dc.b 28,12 dc.b 28,12 dc.b 27,12 dc.b 27,12 dc.b 27,12 dc.b 27,12 dc.b 25,12 dc.b 25,12 dc.b 25,12 dc.b 25,12 dc.b 30,12 dc.b 30,12 dc.b 30,12 dc.b 30,12 measureLength0 equ *-noteData0 noteData1 dc.b beatHi,24 dc.b -23 dc.b beatLo,1 dc.b beatHi,24 dc.b -23 dc.b beatLo,1 measureLength1 equ *-noteData1 noteOffset dc.b 0 measureLength dc.b measureLength0 dc.b measureLength1 echo "Music Data Length:",[*-noteData0]d ENDIF ; ; startCart ; startCart subroutine CLEAN_START ; Clear mem/setup registers resetGame ldx #$ff ; Reset stack pointer txs jsr initGame jsr startOptions jmp mainLoop ; ; initGame ; initGame subroutine lda #%00000101 ; Reflect playfield and bring to front sta CTRLPF lda #$00 ; PF0 will always be empty sta PF0 lda #bgColor ; Initialize background color sta COLUBK ; sta collideFlag ; Clear collision flag sta CXCLR ; Clear collision registers lda #>digitData ; Set the MSB for each of our digit pointers sta digit0+1 ; They are all the same since they don't sta digit1+1 ; cross any page boundaries sta digit2+1 sta digit3+1 sta digit4+1 sta digit5+1 lda #0 sta SWACNT sta SWBCNT lda SWCHB ; Check color/bw switch for ntsc/pal switching and #flag_lastcbsw ; Isolate color/bw switch ora gameFlags ; Set color/bw flag in gameFlags based on switch sta gameFlags ; Basically initialize the flag to prevent unwanted mode switch lda SWCHB ; Now check for power-on video mode lsr ; Check for Reset button (PAL50) bcs .checkSelect ; If carry set, then button not held lda gameFlags ; Else, set gameFlags to PAL50 ora #%11000000 sta gameFlags bne .setDefaults .checkSelect lsr ; Check for Select button (PAL60) bcs .setDefaults ; If carry set, then button not held lda gameFlags ; Else, set gameFlags to PAL60 and #%00111111 ora #%10000000 sta gameFlags .setDefaults ldy #DEFAULT_GAME_MODE ; Set default game mode flags lda gameModes,y sta gameMode lda #DEFAULT_GAME_MODE asl asl asl asl asl ora gameMode ora #mode_switched sta gameMode jsr setPaddleColors .continue jsr updateScore ; Initialize score pointers ;lda gameFlags ; Force 50Hz ;ora #flag_pal50 ;sta gameFlags rts ; ; mainLoop ; mainLoop subroutine lda gameState ; gameState determines flow of each frame and #$0F ; ; optionsFrame ; optionsFrame subroutine cmp #state_options bne .checkNext jsr startVBlank jsr calcBallSpeed jsr moveBall jsr playMusic jsr endVBlank jsr optionsKernel jsr startOverscan jsr reflectAndRemove jsr readConsole jsr endOverscan jmp mainLoop .checkNext ; ; pregameFrame ; pregameFrame subroutine cmp #state_pregame bne .checkNext jsr startVBlank jsr updateSounds jsr pregameTimer jsr readControllers jsr updatePaddles jsr endVBlank jsr gameKernel jsr startOverscan jsr readConsole jsr endOverscan jmp mainLoop .checkNext ; ; ingameFrame ; ingameFrame subroutine cmp #state_ingame bne .checkNext jsr startVBlank jsr updateSounds jsr readControllers jsr updatePaddles IF JOYSTICK_BALL jsr joystickBall ENDIF IF !STATIC_BALL jsr calcBallSpeed jsr moveBall ENDIF jsr endVBlank jsr gameKernel jsr startOverscan jsr reflectAndRemove jsr readConsole jsr endOverscan jmp mainLoop .checkNext ; ; balloutFrame ; balloutFrame subroutine cmp #state_ballout bne .checkNext jsr startVBlank jsr updateSounds lda #0 jsr updatePaddles jsr endVBlank jsr gameKernel jsr startOverscan jsr readConsole jsr deathTimer jsr endOverscan jmp mainLoop .checkNext ; ; clearFrame ; clearFrame subroutine cmp #state_clear bne .checkNext jsr startVBlank jsr updateSounds lda #0 jsr updatePaddles jsr clearTimer jsr endVBlank jsr gameKernel jsr startOverscan jsr readConsole jsr endOverscan jmp mainLoop .checkNext ; ; invalid state... ; brk ; If this gets executed, then I screwed up somewhere! ; BRK will reset everything. ; ; startVBlank ; startVBlank subroutine VERTICAL_SYNC bit gameFlags ; Let's see if the 50Hz flag is set bvs .pal50 ; It's stored in the overflow bit, so check that lda #43 ; 60Hz mode sta TIM64T rts .pal50 lda #73 ; 50Hz mode sta TIM64T rts ; ; endVBlank ; endVBlank subroutine .wait lda INTIM ; Wait for timer to expire bne .wait sta WSYNC sta VBLANK rts ; ; startOptions ; startOptions subroutine lda #11 sta ballSpeed lda #13 sta ballDirection lda #ballStartX ; Set ball starting position sta ballx lda #ballStartY sta bally lda #0 sta ballx+1 sta bally+1 lda #$FF .topborder sta block1l sta block2l sta block2r sta block1r .bottomborder sta block1l+15 sta block2l+15 sta block2r+15 sta block1r+15 lda #$80 ldx #14 .sideborders sta block1l,x sta block1r,x dex bne .sideborders ldy #7 ; Draw REFLEX title screen .loop lda titlePF1l,y sta block1l+4,y lda titlePF2l,y sta block2l+4,y lda titlePF2r,y sta block2r+4,y lda titlePF1r,y sta block1r+4,y dey bpl .loop lda #state_options sta gameState lda #0 ; Silence! sta AUDV0 sta AUDV1 sta noteCounter sta noteCounter+1 lda #1 sta noteTimer sta noteTimer+1 rts ; ; playMusic ; playMusic subroutine ; This fun "little" music routine was inspired by the ; one Kirk Israel wrote in his excellent game JoustPong. ; http://www.alienbill.com/joustpong/ ; It's a bit heavier than Kirk's, I think, but it ; does volume decay and allows notes and percussion ; in both channels. The data's also a little compressed since ; rests are stored as a single negative byte while notes ; and percussion are stored as a word (freq,duration). ; For a note to be percussion, set freq to a negative value. ; As usual, music data is stored backwards. ldx #1 ; Loop through audio channels 1 and 0 .loop dec noteTimer,x ; Decrement the note timer bne .noteContinue ; If not 0, then note continues to play lda noteCounter,x ; Else examine the note counter bne .noteChange ; If positive, then get next note lda measureLength,x ; Else reset the counter sta noteCounter,x ; ...and save it .noteChange dec noteCounter,x ; Decrease the note counter lda noteCounter,x ; ...and load it clc ; Get ready for addition adc noteOffset,x ; Add the note data offset value for channel X tay ; Transfer to Y for indexing lda noteData0,y ; Get new note length bmi .rest ; If negative, then it's a rest sta noteTimer,x ; Else, store it in the note timer dec noteCounter,x ; Prepare to get note pitch dey ; Decrease the note data index lda noteData0,y ; Get new note pitch bmi .percussion ; If negative, then it's percussion sta AUDF0,x ; Else, set pitch lda melodyChannel,x sta AUDC0,x ; ...and channel (note waveform) bne .setVolume ; Unconditional branch .percussion and #%01111111 ; Mask out negative bit sta AUDF0,x ; Set frequency lda #beatChannel sta AUDC0,x ; Set channel (percussion waveform) .setVolume lda #musicVolume ; Read in constant (basically max volume) sta noteVolume,x ; Save it sta AUDV0,x ; ...and set volume bne .skipVolume ; Unconditional branch .rest eor #$FF ; If rest, then get 2's complement clc adc #1 sta noteTimer,x ; Save rest duration lda #0 sta noteVolume,x sta AUDV0,x ; Set the volume to 0 for rest beq .skipVolume .noteContinue ldy noteVolume,x ; See if we should decrease the note volume... beq .skipVolume ; If volume at 0, then do nothing dey ; Else decrease volume sty noteVolume,x ; Save it sty AUDV0,x ; Set volume .skipVolume dex ; Decrease channel counter bpl .loop ; Loop if positive rts ; ; decompressLevel ; decompressLevel subroutine block equ blockX ; Base block pointer blockExtra equ tryX ; Base "extra" block pointer blockIndex equ offsetX ; Board offset index treeIndex equ offsetY ; Huffman tree index lda #80 ; OKGE04 sta blockCount ; Solid board for gaming expo demo lda #7 sta blockIndex ; Start at the top of the board lda #0 ; Init vars... sta treeIndex ; Start at the root of the huffman tree sta block+1 ; Initialize high bytes of block pointers sta blockExtra+1 lda #block2l+4 ; Initialize low bytes of block pointers sta block lda #block1l+4 sta blockExtra ldx levelByte ; Prepare to grab a byte of compressed data lda levelData,x ; Load A with compressed byte ldx levelBit ; Prepare to locate bit... .bitLoop asl ; Shift bits into carry flag dex ; Bits are numbered 0-7 from left to right bpl .bitLoop ; Keep looping until x<0 tay ; Save (shifted) compressed byte for later .lookupLoop lda #0 ; Prep to add carry to treeIndex adc treeIndex ; Add carry bit to treeIndex inc levelBit ; Keep track of bit location for next call to decompressLevel ldx levelBit cpx #8 ; Compare levelBit to 8 bne .skipAdjustment ; If level bit < 8, then don't adjust pointers ldx #0 ; Else, reset levelBit stx levelBit inc levelByte ; And increase levelByte ldx levelByte ; Load levelByte for indexing ldy levelData,x ; Load a new compressed level data byte .skipAdjustment tax ; Prep treeIndex for indexing lda levelDataTable,x ; Grab byte from lookup table bpl .endNode ; If positive, then it's an end node and #%01111111 ; Else, mask out negative bit sta treeIndex ; Use positive value (0-127) for new treeIndex tya ; Load up our level data byte asl ; Shift a bit into the carry flag tay ; Save the level data byte jmp .lookupLoop ; And rotate a new bit into "our byte" .endNode asl ; Move overflow bit (D6) into sign (D7) for easy checking bmi .checkForEnd ; If negative, then finished getting data lsr ; Shift it back sty temp ; Else, save level data byte tax ; Save levelDataTable value and #%00001111 ; Isolate 4-block lookup index tay ; Prepare for table indexing lda levelBrickTable,y ; Get the brick layout ldy blockIndex ; Get destination offset index sta (block),y ; Save brick layout to board! txa ; Restore levelDataTable value and #%00010000 ; Isolate "extra" brick bit beq .storeBrick ; If zero, then save it lda #%00000011 ; Else, turn on "extra" brick .storeBrick sta (blockExtra),y ; Save "extra" brick to board! dec blockIndex ; Prepare to fill next block bpl .resetIndex ; If >= 0, then continue to reset index lda #block2l+4 ; Else, check to see if still drawing on left side cmp block beq .setRight ; If on left side of screen, jump to right rts ; Else, finished drawing board .setRight lda #block2r+4 ; Set low bytes of block pointers sta block lda #block1r+4 sta blockExtra lda #7 sta blockIndex ; Start at the top of the board .resetIndex lda #0 sta treeIndex ; Reset tree index to root lda temp ; Retrieve level data byte asl ; Shift a bit into the carry flag tay ; Save the level data byte jmp .lookupLoop ; Get the next byte of compressed data! .checkForEnd lda #block2l+4 ; See if block and blockIndex have changed cmp block ; First, check block bne .copy ; If changed, then start mirroring lda #7 ; Else, check blockIndex cmp blockIndex bne .copy ; If changed, then start mirroring lda #0 ; Else, reset the compressed data pointers sta levelByte sta levelBit jmp decompressLevel ; ...and try again echo "Level Decompressor Length:",(*-decompressLevel)d .mirror ldx #3 ; X is used to index copy source on left side .copy ldy blockIndex ; Y is used to index copy destination on both sides lda #block2l+4 ; Check to see which copying function to use cmp block bne .copyRight ; If block ptr on the right, then branch .copyLeft inx ; Copy left side... easy stuff lda block2l+4,x ; Now that I'm done with the decompression routine sta (block),y ; I need to build the compression routine... lda block1l+4,x ; Which will live in an external app... probably online sta (blockExtra),y ; I've done it before, but right now, I need to sleep. Zzzzzz... jmp .moveCopyPtr .copyRight lda block2l+4,y ; Copy right side... easy stuff sta (block),y lda block1l+4,y sta (blockExtra),y .moveCopyPtr dec blockIndex ; Prepare to fill next block bpl .copy ; If >= 0, then continue to reset index lda #block2l+4 ; Else, check to see if still drawing on left side cmp block beq .setRight2 ; If on left side of screen, jump to right rts ; Else, finished drawing board .setRight2 lda #block2r+4 ; Set low bytes of block pointers sta block lda #block1r+4 sta blockExtra lda #7 sta blockIndex ; Start at the top of the board bne .copy ; ; startPregame ; startPregame subroutine lda #ball_minSpeed sta ballSpeed lda #2 sta ballDirection ELSE lda #3 sta ballmx lda #3 sta ballmy ENDIF lda #ballStartX ; Set ball starting position sta ballx lda #ballStartY sta bally lda #state_pregame sta gameState lda #pregameDelay sta stateTimer lda #0 ; Silence! sta AUDV0 sta AUDV1 lda #12 sta AUDC0 rts ; ; calcBallSpeed ; calcBallSpeed subroutine lda ballDirection ; Convert the 4-bit direction into a 2-bit lookup value and #%00000100 ; Check for odd/even quadrant beq .noInvert ; If even, then just isolate 2-bit lookup value lda ballDirection ; Else, invert lookup value eor #%00000011 ; Invert lookup value bpl .getIndex ; Uncondition jump .noInvert lda ballDirection ; Reload direction since it was demolished with the AND .getIndex and #%00000011 ; Isolate the lookup value tay ; And transfer it to Y for indexing lda deltaXTab,y ; Get X delta value sta ballmx+1 ; Save it in the ball X movement register (low order) sta blockX ; And also in blockX for temporary storage lda deltaYTab,y ; Get Y delta value sta ballmy+1 ; Save it in the ball Y movement register (low order) sta blockX+2 ; And also in blockX+2 for temporary storage lda #0 ; Zeroize the high order movement bytes sta ballmx ; Update ball X movement register (high order) sta ballmy ; Update ball Y movement register (high order) ldy #2 ; Prepare to loop through X and Y movement registers .loop ldx ballSpeed ; Load up the ball speed multiplier .loopx clc ; Prepare for addition (multiple times!) lda ballmx+1,y ; Grab low order byte for X or Y movement register adc blockX,y ; Add that looked-up value sta ballmx+1,y ; And save the result lda #0 ; Reset A to prep for adding the carry bit adc ballmx,y ; Add carry bit to the high order byte of the register sta ballmx,y ; And save the result dex ; Decrease multiplier counter bne .loopx ; Loop if > 0 dey ; Decrease Y counter dey ; ... twice, because the movement registers are word length bpl .loop ; And loop if it's positive (in this case, zero) lda ballDirection and #%00001000 beq .skipNegateX sec lda #0 sbc ballmx+1 sta ballmx+1 lda #0 sbc ballmx sta ballmx .skipNegateX lda ballDirection clc adc #4 and #%00001000 beq .skipNegateY sec lda #0 sbc ballmy+1 sta ballmy+1 lda #0 sbc ballmy sta ballmy .skipNegateY rts ; ; pregameTimer ; pregameTimer subroutine dec stateTimer bne .end lda #state_ingame sta gameState .end rts ; ; deathTimer ; deathTimer subroutine jsr updateScore dec stateTimer bne .end lda gameFlags and #flag_lives bne .takeLife jsr startOptions rts .takeLife lda gameMode and #mode_twoscores bne .pregame IF !INFINITE_LIVES dec gameFlags ENDIF .pregame jsr startPregame .end rts ; ; clearTimer ; clearTimer subroutine dec stateTimer bne .end jsr clearBlocks lda #0 sta levelByte sta levelBit jsr decompressLevel jsr resetPaddles jsr startPregame .end rts ; ; updateSounds ; updateSounds subroutine lda sfxEcho0 ; Load echo value beq .end ; If 0, then no sound bpl .doTimer ; If positive, then continue sound lda #$0F ; Else, initialize new sound sta sfxEcho0 ; Echo and volume begin at max (15) sta sfxVolume0 lda #sfxEchoDelay ; Echo timer is reset sta sfxTimer0 .doTimer dec sfxTimer0 ; Decrease echo timer bpl .doVolume ; If positive, then continue sound lda #sfxEchoDelay ; Else, echo timer is reset sta sfxTimer0 dec sfxEcho0 ; Reduce echo value by 3 dec sfxEcho0 dec sfxEcho0 lda sfxEcho0 sta sfxVolume0 ; Set volume to new echo value .doVolume lda sfxVolume0 ; Load volume beq .end ; If zero, then end dec sfxVolume0 ; Reduce volume by 3 dec sfxVolume0 dec sfxVolume0 lda sfxVolume0 sta AUDV0 ; Actually set volume register .end rts ; ; addScore ; addScore subroutine sed ; Decimal mode clc ; Clear carry, we're getting ready to add adc score+2 ; A + LSB (right 2 digits) sta score+2 lda score+1 ; Load middle 2 digits adc temp ; Add carry and high order byte sta score+1 lda score ; Load first 2 digits adc #0 ; Add carry sta score cld ; We're not in decimal mode anymore, Toto .end rts ; ; updateScore ; updateScore subroutine ldy #5 ; Set up score pointers (counts down 5 to 0) .scoreloop tya ; lsr ; Divide by 2 to get score byte offset (save carry) tax ; Move A to X for use as offset lda score,x ; Get 2 digits bcc .highnibble ; If carry clear, then use high nibble and #$0F ; else isolate low nibble asl ; Multiply by 8 (the height of our graphics) asl ; A will be used as the LSB in our pointer asl bcc .endnibble .highnibble and #$F0 ; Isolate high nibble lsr ; Divide by two to get our pointer's LSB .endnibble sta temp ; Store LSB tya ; Calculate offset for the pointer itself asl ; Y*2 because each pointer is a word tax ; Move A to X for use as an offset lda temp ; Get our pointer's LSB clc adc #<digitData sta digit0,x ; And store it dey ; Decrease our counter bpl .scoreloop ; Repeat for the next digit rts ; ; setPaddleColors ; setPaddleColors subroutine bit gameFlags bmi .pal0 lda #paddleColor ; Initialize paddle colors bne .continue0 .pal0 lda #PAL_paddleColor .continue0 sta p0col sta p1col sta pcol lda gameMode lsr ; Shift the twoplayer bit out bcc .end ; If carry clear, then skip two players .twoPlayer bit gameFlags bmi .pal1 lda #paddle0Color ldx #paddle1Color bne .continue1 .pal1 lda #PAL_paddle0Color ldx #PAL_paddle1Color .continue1 sta p0col stx p1col .end rts ; ; readConsole ; readConsole subroutine lda SWCHB ; Check color/bw switch for ntsc/pal switching and #flag_lastcbsw ; Isolate color/bw switch sta temp lda gameFlags and #flag_lastcbsw cmp temp beq .checkReset lda gameFlags bit gameFlags bvs .modeNTSC bmi .modePAL60 .modePAL50 ora #%10000000 bne .flipSwitch .modePAL60 ora #%01000000 bne .flipSwitch .modeNTSC and #%00111111 .flipSwitch eor #flag_lastcbsw sta gameFlags jsr setPaddleColors .checkReset lda SWCHB lsr bcs .checkSelect lda gameMode and #mode_switched beq .startGame rts .startGame lda #0 ; Reset the BCD score sta score sta score+1 sta score+2 jsr updateScore jsr clearBlocks lda #0 sta levelByte sta levelBit jsr decompressLevel jsr resetPaddles jsr startPregame lda #$ff eor #flag_lives and gameFlags ora #startingLives sta gameFlags jmp .setSwitch .checkSelect lsr bcs .noSelect lda gameMode and #mode_switched bne .end lda gameState cmp #state_options beq .notInGame jsr startOptions jmp .setSwitch .notInGame lda #<digitData ; Set the score to display 000000 ldy #10 ; Count down from 10 by 2's (10,8,6,4,2,0) .scrLoop sta digit0,y ; Store the LSB of '0' char into digit pointer dey ; Decrease Y by 2 dey bpl .scrLoop ; Loop if positive lda gameMode lsr lsr lsr lsr lsr clc adc #1 cmp gameModesAvailable bne .switch lda #0 .switch tay asl asl asl asl asl sta temp lda gameMode and #mode_preset and #mode_flags ora temp ora gameModes,y ora #mode_switched sta gameMode jsr setPaddleColors rts .setSwitch lda #mode_switched ora gameMode sta gameMode rts .noSelect lda #mode_switched eor #$ff and gameMode sta gameMode .end rts ; ; readControllers ; readControllers subroutine lda gameMode ; Check for driving controller and #mode_driving ; Isolate controller bit bne .driving ; If set, then driving .joystick ldy #1 ; Loop through both joysticks .jsLoop lda SWCHA ; Check joystick RIGHT bit cpy #0 ; Check for joystick 0 or 1 beq .skipJsShift ; If 0, then don't shift SWCHA register asl ; Else, shift it left 4 bits asl asl asl .skipJsShift sta temp ; Save it because BIT has no immediate mode... bit temp ; Copies negative and overflow bits into processor status .jsRight bmi .jsLeft ; If joystick RIGHT (negative) bit is set, then check left lda #1 ; Set movement offset (clockwise) jmp .jsEndLoop .jsLeft bvs .jsNomove ; If joystick LEFT (overflow) bit is set, then no movement lda #-1 ; Set movement offset (counterclockwise) jmp .jsEndLoop .jsNomove lda #0 ; .jsEndLoop sta paddle0,y dey bpl .jsLoop rts .driving ldx #1 ; Loop through both driving controllers .dcLoop lda oldSWCHA ; Load last controller state and playerMask,x ; Mask out left or right nibble sta temp ; Save nibble lda SWCHA ; Load driving controller (joystick) register and playerMask,x ; Isolate appropriate nibble cmp temp ; Compare with last joystick register beq .dcNomove ; If equal, then no movement and #%00110011 ; Else, isolate controller bits cpx #0 ; Check for joystick 0 or 1 bne .skipDcShift ; If 1, then don't shift controller bits lsr ; Else, shift them right 4 bits lsr lsr lsr .skipDcShift tay ; Transfer controller bits to Y to use as index lda spinRightTab,y ; Load A with bit pattern matching a move RIGHT and playerMask,x ; Isolate appropriate nibble (again) cmp temp ; Compare with last controller state beq .dcRight ; If equal, then move right lda spinLeftTab,y ; Load A with bit pattern matching a move LEFT and playerMask,x ; Isolate appropriate nibble (yet again) cmp temp ; Compare with last controller state beq .dcLeft ; If equal, then move left jmp .dcNoAction ; Else just take the last move value (overspin!) .dcLeft lda #-1 ; Move left (counterclockwise) jmp .dcEndLoop .dcRight lda #1 ; Else, move right (clockwise) jmp .dcEndLoop .dcNomove lda #0 .dcEndLoop ldy paddle0,x ; Double the movement if last movement was non-zero beq .dcNoDouble ; If zero, then no double asl ; Else, double it (make driving controller more nimble) .dcNoDouble sta paddle0,x .dcNoAction dex bpl .dcLoop lda SWCHA sta oldSWCHA rts ; ; clearBlocks ; clearBlocks subroutine lda #0 ; Prepare to clear playfield ldx #4*playfieldHeight ; Set up clearing loop .loop sta blockData,x ; Clear playfield dex ; Decrease loop counter bpl .loop ; Branch for loop ; ; resetPaddles ; resetPaddles subroutine lda #10 sta paddlePos lda #12-4 sta paddleSize ldx #$FC ; One-player paddle lda gameMode ; Check for two player mode and #mode_twoplayers ; Examine the mode_twoplayers bit... beq .setPaddles ; If one player, then set the paddle size ldx #14 ; Else set up side borders .sideborders lda #$80 ; Loop to create borders ora block1l,x sta block1l,x lda #$80 ora block1r,x sta block1r,x dex bne .sideborders ldx #$E0 ; Two-player paddles .setPaddles stx block2l stx block2r stx block2l+playfieldHeight-1 stx block2r+playfieldHeight-1 .end rts ; ; updatePaddles ; updatePaddles subroutine lda gameMode ; Check for two player mode and #mode_twoplayers ; Examine the mode_twoplayers bit... beq .onePlayer ; If clear, then onePlayer jmp .twoPlayers ; If set, then twoPlayers .onePlayer lda paddle0 ; Examine paddle0 (player 1) beq .mirror ; If 0, then just mirror the paddle, don't move it bpl .right1p ; If positive, then move right (clockwise) ; Else, move left (counter clockwise) .left1p eor #$FF ; Convert paddle0 to positive integer tay ; Transfer to Y iny ; And increment to complete the two's complement math .left1pLoop rol block1r+14 ; Slide bottom of paddle to the right ror block1l ; The ROLs and RORs compensate for PF graphics flip-floppiness rol block2l ror block2r rol block1r php ; Save processor flags (only need Carry bit) ldx #13 ; Loop through right side of paddle .loop1 rol block1r,x ; Rotate bits out... ror block1r+1,x ; ... and in to achieve upward slide dex bne .loop1 plp ; Get that Carry bit we had put into cold storage with PHP ror block1r+1,x ; And rotate it into the bottom bit of the right side of paddle inc paddlePos ; Bottom/right paddle position increase lda paddlePos cmp #46 bmi .left1pSkip ; If paddlePos is positive, then we're good... lda #0 sta paddlePos .left1pSkip dey bne .left1pLoop ; Do this as many times as paddle0 dictates jmp .mirror .right1p tay ; Transfer paddle0 to Y .right1pLoop rol block1r+1 ; Slide bottom of paddle to the left ror block1r rol block2r ror block2l rol block1l php ; Save processor flags (only need Carry bit) ldx #1 ; Loop through right side of paddle .loop2 rol block1r+1,x ; Rotate bits out... ror block1r,x ; ... and in to achieve downward slide inx cpx #14 ; Have to compare before branch because this is an incrementing loop bne .loop2 plp ; Get Carry flag ror block1r,x ; Rotate it into the rightmost bit of the bottom of paddle dec paddlePos ; Bottom/right paddle position decrease bpl .right1pSkip ; If paddlePos is positive, then we're good... lda #45 sta paddlePos .right1pSkip dey bne .right1pLoop ; Do this as many times as paddle0 dictates jmp .mirror .mirror ;lda paddlePos ; Paddle debug ;sta block2r+1 ; Paddle debug lda block1l ; Now that we've moved the bottom-right, copy it to the top-left sta block1r+playfieldHeight-1 lda block2l sta block2r+playfieldHeight-1 lda block2r sta block2l+playfieldHeight-1 lda block1r sta block1l+playfieldHeight-1 ldy #14 ; We need two counters in this loop... Y goes down ldx #1 ; And X goes up .mirrorLoop lda block1r,y ; Load a byte from the right side and #%10000000 ; Isolate the paddle bit sta temp ; Save it lda #%01111111 ; Load the mask for the left side and block1l,x ; Get everything in the corresponding left byte EXCEPT paddle bit ora temp ; Apply the paddle bit from the right to the left byte sta block1l,x ; And save it (Code's probably easier to read than these comments!) inx dey bne .mirrorLoop rts .twoPlayers lda paddle0 ; Examine paddle0 (player 1) beq .player2 bpl .p1Right .p1Left eor #$FF ; Convert paddle0 to positive integer tay ; Transfer to Y iny ; And increment to complete the two's complement math .p1LeftLoop lda #%10000000 and block1l+playfieldHeight-1 bne .p1LeftEnd lsr block1r+playfieldHeight-1 ; The ROLs and RORs compensate for PF graphics flip-floppiness rol block2r+playfieldHeight-1 ror block2l+playfieldHeight-1 rol block1l+playfieldHeight-1 .p1LeftEnd dey bne .p1LeftLoop ; Do this as many times as paddle0 dictates jmp .player2 .p1Right tay .p1RightLoop lda #%10000000 and block1r+playfieldHeight-1 bne .p1RightEnd lsr block1l+playfieldHeight-1 ; The ROLs and RORs compensate for PF graphics flip-floppiness rol block2l+playfieldHeight-1 ror block2r+playfieldHeight-1 rol block1r+playfieldHeight-1 .p1RightEnd dey bne .p1RightLoop ; Do this as many times as paddle0 dictates .player2 lda paddle1 ; Examine paddle1 (player 2) beq .end bpl .p2Right .p2Left eor #$FF ; Convert paddle1 to positive integer tay ; Transfer to Y iny ; And increment to complete the two's complement math .p2LeftLoop lda #%10000000 and block1l bne .p2LeftEnd lsr block1r ; The ROLs and RORs compensate for PF graphics flip-floppiness rol block2r ror block2l rol block1l .p2LeftEnd dey bne .p2LeftLoop ; Do this as many times as paddle1 dictates rts .p2Right tay .p2RightLoop lda #%10000000 and block1r bne .p2RightEnd lsr block1l ; The ROLs and RORs compensate for PF graphics flip-floppiness rol block2l ror block2r rol block1r .p2RightEnd dey bne .p2RightLoop ; Do this as many times as paddle1 dictates rts .end rts ; ; joystickBall (DEBUG) ; IF JOYSTICK_BALL joystickBall subroutine ; For debugging purposes lda #0 sta ballmx+1 sta ballmy+1 sta ballmx sta ballmy bit SWCHA bmi .left lda #80 sta ballmx+1 lda #00 sta ballmx .left bit SWCHA bvs .down lda #-80 sta ballmx+1 lda #-1 sta ballmx .down lda SWCHA asl asl sta temp bit temp bmi .up lda #160 sta ballmy+1 lda #0 sta ballmy .up bit temp bvs .end lda #-160 sta ballmy+1 lda #-1 sta ballmy .end rts ENDIF ; ; moveBall ; moveBall subroutine lda bally+1 ; 16-bit addition (bally=bally+ballmy) clc adc ballmy+1 sta bally+1 lda bally adc ballmy cmp #133 ; Check Y > ## (Ball wraps around if no obstructions) bmi .checkYunder jsr ballOut jmp .doneWithBally .checkYunder cmp #0 ; Check Y <= ## bpl .doneWithBally jsr ballOut .doneWithBally sta bally lda ballx+1 ; 16-bit addition (ballx=ballx+ballmx) clc adc ballmx+1 sta ballx+1 lda ballx adc ballmx cmp #147 ; Check X > ## bmi .checkXunder jsr ballOut jmp .doneWithBallx .checkXunder cmp #21 ; Check X <= ## bpl .doneWithBallx jsr ballOut .doneWithBallx sta ballx rts ; ; ballOut ; ballOut subroutine ldx #state_ballout stx gameState ldx #deathDelay stx stateTimer ldx #0 stx paddle0 stx paddle1 sta temp lda gameMode and #mode_twoscores beq .end ldx #1 lda #133 cmp temp bmi .skip inx .skip inc score,x lda score,x cmp #3 bne .end lda gameFlags and #(flag_lives^$FF) sta gameFlags .end lda temp rts ; ; emptyKernel ; emptyKernel subroutine ldy #192 jsr skipLines rts ; ; optionsKernel ; optionsKernel subroutine jsr scoreArea ;+22 22 jsr optionsArea ;+20 42 jsr positionBall ;+2 44 jsr gameArea ;+130 174 IF !VU_METERS ldy #18 jsr skipLines ;+18 192 ELSE ldy #10 jsr skipLines ;+10 184 jsr vuMetersArea ;+8 192 ENDIF rts ; ; gameKernel ; gameKernel subroutine jsr scoreArea ;+22 22 ldy #6 jsr skipLines ;+6 28 jsr positionBall ;+2 30 jsr gameArea ;+130 160 ldy #11 jsr skipLines ;+11 161 jsr livesArea ;+5 166 ldy #16 jsr skipLines ;+16 192 rts ; ; optionsArea (20 scanlines) ; optionsArea subroutine sta WSYNC lda #0 sta GRP0 lda #$0c sta COLUP0 sleep 15 sta RESP0 ;lda #1 ;sta HMP0 sta WSYNC ;sta HMOVE lda gameMode ; One- or two-player controller options and #mode_twoplayers sta NUSIZ0 ; Double sprite accordingly lda gameMode ; Joystick or driving control icon and #mode_driving ; Isolate driving control bit clc adc <#optionJS ; Clean math, takes advantage of driving control bit position sta blockPtr ; Set LSB of sprite pointer lda >#optionJS sta blockPtr+1 ; Set MSB of sprite pointer ldy #15 ldx #$0F .loop sta WSYNC lda (blockPtr),y sta GRP0 dex dex stx COLUP0 inx dey sta WSYNC lda (blockPtr),y sta GRP0 stx COLUP0 dey bpl .loop sta WSYNC ; Blank row lda #0 ; Here, we reset the graphics registers sta GRP0 sta NUSIZ0 sta WSYNC rts ; ; vuMetersArea (8 scanlines) ; vuMetersArea subroutine IF VU_METERS SLEEP 7 sta RESP1 ldx #-1 .loop lda noteVolume+1,x lsr lsr tay iny lda livesTab,y sta WSYNC sta GRP1 sta WSYNC sta WSYNC lda #0 sta WSYNC sta GRP1 inx beq .loop rts ENDIF ; ; livesArea (5 scanlines) ; livesArea subroutine ldy #5 ; Init scanline counter lda gameMode ; Check game mode and #mode_twoscores ; If a competitive game, then don't show lives bne .skipLives bit gameFlags bmi .palColors lda #livesColor ; Set the PF color for lives bne .continue .palColors lda #PAL_livesColor ; Set the PF color for lives .continue sta COLUPF lda gameFlags and #flag_lives ; Isolate the life counter from gameFlags tax ; Transfer to X for indexing lda livesTab,x ; Lookup the bit pattern for x # of lives ldx #0 ; Set x to 0 for use in left half of non-mirrored PF .loop0 sta WSYNC ; Wait for next scanline stx PF1 ; Zeroize left half of PF1 sleep 40 ; Wait until left PF1 drawn before setting right PF1 sta PF1 ; Store lives bit pattern dey ; Decrease scanline counter bne .loop0 ; And loop stx PF1 ; After loop ends, zeroize PF1 again rts .skipLives sta WSYNC dey bne .skipLives rts ; ; skipLines (Y scanlines) ; skipLines subroutine .loop sta WSYNC dey bne .loop rts ; ; startOverscan ; startOverscan subroutine lda #2 bit gameFlags ; Let's see if the 50Hz flag is set bvs .pal50 ; It's stored in the overflow bit, so check that ldx #35 ; 60Hz sta WSYNC stx TIM64T sta VBLANK rts .pal50 ldx #65 ; 50Hz sta WSYNC stx TIM64T sta VBLANK rts ; ; endOverscan ; endOverscan subroutine .wait lda INTIM bne .wait rts ; ; getBallLocation ; getBallLocation subroutine lda ballx ; Calculate ball X position in blocks sec ; blockX = (ballx-19)/4 sbc #19 sta temp and #%00000011 ; offsetX = (ballx-19) mod 4 sta offsetX lda temp lsr lsr sta blockX sec ; Calculate ball Y position in blocks lda #[rowHeight * 16]+1 ; blockY = ((rowHeight*16+1)-bally)/8 sbc bally sta temp and #%00000111 ; offsetY = ((rowHeight*16+1)-bally) mod 8 sta offsetY lda temp lsr lsr lsr sta blockY rts ; ; getBrickValue ; inputs: a,y (block deltas) ; outputs: a (bit position of collision; 0 if none) ; getBrickValue subroutine tya ; Add Y to blockY clc adc blockY sta tryY ; Store result in tryY txa ; Add X to blockX clc adc blockX sta tryX ; Store result in tryX sta tryX_store beq .isPaddle ; Determine if tryX,tryY is a paddle cmp #31 beq .isPaddle lda tryY beq .isPaddle cmp #15 ; beq .isPaddle bne .notPaddle .isPaddle lda #-1 bne .continue .notPaddle lda #0 .continue sta temp lda tryX lsr ; Divide tryX by 8 to get PF byte 0,1,2, or 3 lsr lsr sta blockSection ; Store result in blockSection lda <#blockData ; Load LSB of blockData sta blockPtr ; Store it as LSB of blockPtr ldy blockSection ; Load X for counting beq .skipOffset ; If blockSection is 0, then no offset calculation .loop clc ; Else loop to find blockPtr offset adc #playfieldHeight ; Add playfieldHeight (16) to blockPtr sta blockPtr lda tryX ; And subtract 8 from tryX adc #-8 ; adc vs. sbc because I know carry is clear sta tryX ; Store result lda blockPtr ; Reload blockPtr and loop dey bne .loop .skipOffset lda blockSection lsr ; Determine if blockSection is even or odd bcc .dontFlip ; If even, then don't flip tryX lda tryX ; Else, flip tryX eor #%00000111 ; 0=7,1=6,2=5,etc... jmp .testBrick .dontFlip lda tryX .testBrick sta tryX tay lda singleBrickTab,y ldy tryY and (blockPtr),y ora temp sta temp rts ; ; reflect ; reflect subroutine ; Overview of inputs ; A non-zero value in temp indicates a collision ; x,y contain values between -1 and 3 ; -1 = Reflect ball movement if positive ; 0 = Reflect ball movement (+ to -, or - to +) ; 1 = Reflect ball movement if negative ; 2 = Do not reflect ; 3 = Special ball handling because ball is "buried" ; in a brick, not just touching ; Note: I tried very hard to keep the ball from behaving ; stupidly during "ambiguous" collisions usually caused ; by very high ballmx or ballmy values. This is my excuse ; for the following bit of spaghetti... lda temp ; Check temp bne .tryX ; If non-zero, then process horizontal movement rts ; Else return from sub .tryX txa ; Prep x (-1 to 3) for processing beq .reflectX ; If x=0, then reflect horizontal movement cmp #2 beq .tryY ; If x=2, then don't reflect horizontal movement (try vertical) cmp #3 bne .checkNegative ; If -1 or 1, then conditionally reflect ball in specified direction lda tryX_store ; If x=3, prepare for "buried" ball processing bne .checkRight ; If not buried in left side paddle, then check the right side ldx #1 ; Else force ball to move right (positive) bne .checkBottom ; Unconditional jump to check bottom side paddle .checkRight cmp #31 ; Check if ball buried in right side paddle bne .noHorizontal ; If not, then no horizontal reflection ldx #-1 ; Else force ball to move left (negative) bne .checkBottom ; Unconditional jump to check bottom side paddle .noHorizontal ldx #2 ; No horizontal reflection .checkBottom lda tryY ; Check top/bottom bounds bne .checkTop ; If not buried in bottom side paddle, then check the top side ldy #-1 ; Else force ball to move up (negative) bne .tryX ; Unconditional jump to reprocess reflection with new x,y values .checkTop cmp #15 ; Check if ball buried in top side paddle bne .noVertical ; If not, then no vertical reflection ldy #1 ; Else force ball to move down (positive) bne .tryX ; Unconditional jump to reprocess reflection with new x,y values .noVertical ldy #2 ; No vertical reflection cpx #2 ; See if there's horizontal reflection bne .tryX ; If so, then reprocess reflection with new x,y values ldy #2 ; Else, reflect X only... (This assumption is not 100% right) bne .reflectX ; Unconditional jump to reflect horizontal movement .checkNegative eor ballmx ; EOR with ballmx and #%10000000 ; Isolate Negative bit beq .tryY .reflectX lda #15 sec sbc ballDirection sta ballDirection .tryY tya ; Load signed value in a beq .reflectY ; If 0, then reflect cmp #2 beq .english ; If 2, then skip reflect eor ballmy ; EOR with ballmy and #%10000000 ; Isolate Negative bit beq .english .reflectY lda ballDirection and #%00001000 ldy temp sta temp lda #7 sec sbc ballDirection and #%00000111 ora temp sty temp sta ballDirection .english IF 1 ; Collect some debugging info... .startDebug .endDebug rts ENDIF lda gameState cmp #state_options ; Check for "options" state beq .done ; If so, exit ldy #0 ; paddlePos and paddleSize offset .bottom lda tryY ; Check paddle english for bottom bne .top ; If ball didn't hit bottom, then check top lda gameMode ; Load gameMode flags and #mode_twoplayers ; Isolate two-player bit (bit 0) tay ; Update paddlePos and paddleSize offset lda tryX_store ; Else, get paddle index ldx #12 ; And set a direction offset (I'm confused by my own notes...) bne .getDelta ; Unconditional branch .top cmp #15 ; Check paddle english for top bne .left ; If ball didn't hit top, then check left lda #31 ; Else, get paddle index sec sbc tryX_store ; Carry was set by CMP bpl .topContinue ; If not negative, then continue clc adc #45 .topContinue ldx #4 ; And set a direction offset bne .getDelta ; Unconditional branch .left lda tryX_store ; Check paddle english for left bne .right ; If ball didn't hit left, then check right lda #47 ; Else, get paddle index sbc tryY ; Carry was cleared by CMP in .top ldx #8 ; And set a direction offset bne .getDelta ; Unconditional branch .right cmp #31 ; Check paddle english for right bne .done ; If ball didn't hit right, then no english lda #30 ; Else, get paddle index adc tryY ; Carry was set by CMP ldx #8 ; And set a direction offset .getDelta sec sbc paddlePos,y sbc #2 bmi .addDelta sbc paddleSize,y bmi .done .addDelta stx offsetX clc adc offsetX clc adc ballDirection and #%00000111 sec sbc offsetX and #%00001111 sta ballDirection .done rts ; ; removeBrick ; removeBrick subroutine lda temp bne .zapBlock rts .zapBlock cmp #-1 ; Check for paddle bne .continue ; If not, don't exit lda gameState cmp #state_options ; Check for "options" state beq .end lda #-1 ; Ping! sta sfxEcho0 lda #sfxPaddleFreq sta AUDF0 jmp .end .continue lda gameState cmp #state_options ; Check for "options" state beq .end ; If so, exit ldy tryX ; Remove brick... lda doubleBrickTab,y ldy tryY and (blockPtr),y eor #%11111111 and (blockPtr),y sta (blockPtr),y lda #flashDuration sta stateTimer lda #-1 ; Ping! sta sfxEcho0 lda #sfxBrickFreq sta AUDF0 dec blockCount ; Decrease the block counter bne .addScore ; If not zero, then add score lda #state_clear sta gameState lda #clearDelay sta stateTimer lda #0 sta paddle0 sta paddle1 .addScore lda gameMode ; Check for scoring type and #mode_twoscores ; If it's two scores... bne .end ; Then don't update the score here lda #>brickScore ; Else add points to score sta temp lda #<brickScore jsr addScore ; THIS IS A THIRD LEVEL JSR jsr updateScore .end pla ; Pull the jsr return address off stack pla ; (Yep, it's two bytes) ;jsr updateScore ; THIS IS A THIRD LEVEL JSR sta CXCLR ; Clear collision register rts ; ; DEBUG_COLLISION stuff ; IF DEBUG_COLLISION ; Only assemble this subroutine if flag set displayCollision subroutine tax ; Transfer the updated left 2 score digits to X lda temp ; Was there a collision? beq .endDebug ; If not, then end stx score ; If so, update the left 2 score digits .endDebug rts ENDIF MAC DISPLAY_COLLISION IF DEBUG_COLLISION .collide SET {1}<<4 ; Shift the input value left 4 bits lda score ; Load left 2 score digits and #%00001111 ; Zero out leftmost score digit ora #.collide ; Overlay the input value jsr displayCollision ; Execute above routine ENDIF ENDM ; ; reflectAndRemove ; reflectAndRemove subroutine IF !NO_REMOVE lda #0 sta blockPtr+1 ; Set MSB of blockPtr to zero page jsr getBallLocation ; Retrieve block and offset positions lda CXP1FB ; Load player1/playfield collision register asl ; Shift the important bit out to alter carry flag bcs .removeScript ; If carry set (collision), then remove a brick rts ; Otherwise, we're done ; This remove algorithm refers to block locations ; as such, with 5 being the ball location: ; 1 2 3 1 ; 4(5)6 0 ; 7 8 9 -1 .removeScript ldx #0 ; X - Check for block underneath ball (position 5) ldy #0 ; Y jsr getBrickValue DISPLAY_COLLISION 5 ldx #3 ldy #3 jsr reflect jsr removeBrick .try4 lda offsetX ; Try to remove block 4 (see comment above) and so on... bne .try6 ldx #-1 ; X ldy #0 ; Y jsr getBrickValue DISPLAY_COLLISION 4 ldx #1 ; Reflection ldy #2 jsr reflect jsr removeBrick beq .try2 .try6 lda offsetX cmp #3 bne .try2 ldx #1 ; X ldy #0 ; Y jsr getBrickValue DISPLAY_COLLISION 6 ldx #-1 ; Reflection ldy #2 jsr reflect jsr removeBrick .try2 lda offsetY cmp #5 bmi .try8 ldx #0 ; X ldy #1 ; Y jsr getBrickValue DISPLAY_COLLISION 2 ldx #2 ldy #1 ; Reflection jsr reflect jsr removeBrick .try1 lda offsetX bne .try3 ldx #-1 ; X ldy #1 ; Y jsr getBrickValue DISPLAY_COLLISION 1 ldx #1 ; Reflection ldy #1 jsr reflect jsr removeBrick .try3 lda offsetX cmp #3 bne .dammit ldx #1 ; X ldy #1 ; Y jsr getBrickValue DISPLAY_COLLISION 3 ldx #-1 ; Reflection ldy #1 jsr reflect jsr removeBrick .try8 lda offsetY cmp #2 bpl .dammit ldx #0 ; X ldy #-1 ; Y jsr getBrickValue DISPLAY_COLLISION 8 ldx #2 ldy #-1 ; Reflection jsr reflect jsr removeBrick .try7 lda offsetX bne .try9 ldx #-1 ; X ldy #-1 ; Y jsr getBrickValue DISPLAY_COLLISION 7 ldx #1 ; Reflection ldy #-1 jsr reflect jsr removeBrick .try9 lda offsetX cmp #3 bne .dammit ldx #1 ; X ldy #-1 ; Y jsr getBrickValue DISPLAY_COLLISION 9 ldx #-1 ; Reflection ldy #-1 jsr reflect jsr removeBrick .dammit ; This should theoretically never get called .end ENDIF sta CXCLR ; Clear collision register rts ; ; ballEffects ; ;ballEffects subroutine ; lda stateTimer ; beq .noFlash ; dec stateTimer ; lda #%00000010 ; and stateTimer ; bne .flash2 ; lda #flashColor1 ; jmp .setColors ;.flash2 lda #flashColor2 ;.setColors sta COLUP0 ; sta COLUP1 ; rts ;.noFlash ; lda #ballHighlight ; sta COLUP0 ; lda #ballShadow ; sta COLUP1 ;.end rts ; ------------------------------------ ; Reset vector ; ------------------------------------ echo "ROM Bytes Used:",[*-program+4]d echo "ROM Bytes Free:",[$10000-*-4]d seg res reset org $FFFC dc.w startCart dc.w startCart
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Thread | Re: [stella] The Reflex!, Thomas Jentzsch | |
Date | Re: [stella] The Reflex!, Thomas Jentzsch | |
Month |