LIST OFF ; *** K A B O O M ! *** ; Copyright 1981 Activision, Inc. ; Programmer: Larry Kaplan ; Graphics: David Crane ; Analyzed, labeled and commented ; by Dennis Debro ; Last Update: August 20, 2004 ; ; *** 128 BYTES OF RAM USED 0 BYTES FREE ; *** 2 BYTES OF ROM FREE ; ; ============================================================================== ; = THIS REVERSE-ENGINEERING PROJECT IS BEING SUPPLIED TO THE PUBLIC DOMAIN = ; = FOR EDUCATIONAL PURPOSES ONLY. THOUGH THE CODE WILL ASSEMBLE INTO THE = ; = EXACT GAME ROM, THE LABELS AND COMMENTS ARE THE INTERPRETATION OF MY OWN = ; = AND MAY NOT REPRESENT THE ORIGINAL VISION OF THE AUTHOR. = ; = = ; = THE ASSEMBLED CODE IS © 1981, ACTIVISION = ; = = ; ============================================================================== processor 6502 include "vcs.h" include "macro.h" include "tia_constants.h" ; ; Make sure we are using vcs.h version 1.05 or greater. ; IF VERSION_VCS < 105 echo "" echo "*** ERROR: vcs.h file *must* be version 1.05 or higher!" echo "*** Updates to this file, DASM, and associated tools are" echo "*** available at https://dasm-assembler.github.io/" echo "" err ENDIF ; ; Make sure we are using macro.h version 1.01 or greater. ; IF VERSION_MACRO < 101 echo "" echo "*** ERROR: macro.h file *must* be version 1.01 or higher!" echo "*** Updates to this file, DASM, and associated tools are" echo "*** available at https://dasm-assembler.github.io/" echo "" err ENDIF LIST ON ;=============================================================================== ; A S S E M B L E R - S W I T C H E S ;=============================================================================== NTSC = 0 PAL50 = 1 TRUE = 1 FALSE = 0 IFNCONST COMPILE_REGION COMPILE_REGION = NTSC ; change to compile for different regions ENDIF IF !(COMPILE_REGION = NTSC || COMPILE_REGION = PAL50) echo "" echo "*** ERROR: Invalid COMPILE_REGION value" echo "*** Valid values: NTSC = 0, PAL50 = 1" echo "" err ENDIF ;=============================================================================== ; F R A M E T I M I N G S ;=============================================================================== IF COMPILE_REGION = NTSC FPS = 60 ; ~60 frames per second VBLANK_TIME = 48 OVERSCAN_TIME = 33 ELSE FPS = 50 ; ~50 frames per second VBLANK_TIME = 79 OVERSCAN_TIME = 61 ENDIF ;=============================================================================== ; C O L O R C O N S T A N T S ;=============================================================================== BLACK = $00 WHITE = $0E IF COMPILE_REGION = NTSC YELLOW = $10 BRICK_RED = $30 RED = $40 BLUE = $80 GREEN = $D0 WATER_COLOR = BLUE + 8 LOGO_COLOR = WATER_COLOR ELSE YELLOW = $20 BRICK_RED = $40 GREEN = $50 RED = $60 BLUE = $B0 WATER_COLOR = BLUE + 6 LOGO_COLOR = WATER_COLOR ENDIF ;=============================================================================== ; U S E R - C O N S T A N T S ;=============================================================================== ROM_BASE = $F000 SELECT_DELAY = 30 PADDLE_MIN = 2 PADDLE_MAX = 254 ; kernel boundaries H_KERNEL = 85 XMIN = 5 XMAX = 118 MAX_BOMBS = 8 MAX_BUCKETS = 3 H_MAD_BOMBER = 32 H_BOMB = 16 H_BUCKETS = 16 H_SPLASH = 8 H_ACTIVISION_LOGO = 8 H_SCORE = 8 STARTING_EXPLODING_TIMER = 43 EXPLODING_FLASH_TIME = STARTING_EXPLODING_TIMER - 11 ; bomb group constants BOMB_GROUP_MAX = 8 MAX_BOMBS_GROUP1 = 10 MAX_BOMBS_GROUP2 = 20 MAX_BOMBS_GROUP3 = 30 MAX_BOMBS_GROUP4 = 40 MAX_BOMBS_GROUP5 = 50 MAX_BOMBS_GROUP6 = 75 MAX_BOMBS_GROUP7 = 100 MAX_BOMBS_GROUP8 = 150 DROPPING_FREQ = 18 BOMB_DROP_RATE = 1 ; Mad Bomber traveling values MAD_BOMBER_TRAVELING_LEFT = $FF MAD_BOMBER_TRAVELING_RIGHT = $00 ; game state constances LEVEL_DONE = $7F WAIT_LEVEL_START = $FF BONUS_BUCKET_FREQ = 63 CATCHING_BOMB_FREQ = 16 ;=============================================================================== ; Z P - V A R I A B L E S ;=============================================================================== SEG.U variables .org $80 gameSelection ds 1 frameCount ds 1 randomSeed ds 1 colorEOR ds 1 hueMask ds 1 bomberKernelBGColor ds 1 backgroundColor ds 1 loopCount ds 1 ;-------------------------------------- scanline = loopCount tempCharHolder ds 1 ;-------------------------------------- tempXHolder = tempCharHolder ;-------------------------------------- temp = tempXHolder ;-------------------------------------- scoreColor = loopCount player1ScoreColor = scoreColor player2ScoreColor = scoreColor + 1 ;-------------------------------------- bomberExpressionState = tempCharHolder ; only D7 is used for this bombColors ds H_BOMB - 1 bucketsFineCoarse ds 1 madBomberFineCoarse ds 1 madBomberHorizPosition ds 1 madBomberDirection ds 1 catchingBucketNumber ds 1 bucketHorizPosition ds 1 colorCycleMode ds 1 selectDebounce ds 1 playerNumber ds 1 ; ranges from 0 - 1 currentPlayerVariables ds 5 ;-------------------------------------- remainingBuckets = currentPlayerVariables bombGroup = remainingBuckets + 1 playerScore = bombGroup + 1 reservedPlayerVariables ds 5 ;-------------------------------------- reservedRemainingBuckets = reservedPlayerVariables numberBombsDropped ds 1 explodingBombNumber ds 1 gameState ds 1 bombExplodingTimer ds 1 catchingBombSoundFreq ds 1 bonusBucketSoundFreq ds 1 bombDropVelocity ds 1 bombFineCoarse ds 9 bombLSB ds 9 bucketGraphics ds H_BUCKETS * MAX_BUCKETS thirdRowBucketGraphics = bucketGraphics secondRowBucketGraphics = thirdRowBucketGraphics + H_BUCKETS firstRowBucketGraphics = secondRowBucketGraphics + H_BUCKETS digitPointer ds 12 ;-------------------------------------- paddleRangeMax = digitPointer + 1 bombNumber = digitPointer + 2 paddleValue = digitPointer + 3 bombIndexCaught = digitPointer + 4 tempBombGraphic = digitPointer + 5 ;-------------------------------------- tempCoarseValue = tempBombGraphic ;-------------------------------------- splashGraphicLoopCount = tempCoarseValue bombGraphicPointer = digitPointer + 6 echo "***",(* - $80)d, "BYTES OF RAM USED", ($100 - *)d, "BYTES FREE" ;=============================================================================== ; R O M - C O D E ;=============================================================================== SEG Bank0 .org ROM_BASE Start ; ; Set up everything so the power up state is known. ; sei ; disable interrupts cld ; clear decimal mode ldx #0 txa .clearLoop sta VSYNC,x txs inx bne .clearLoop jmp JumpIntoConsoleSwitchCheck MainLoop ldx #18 .loadColorsLoop lda GameColors,x ; read the game colors from ROM ldy bombExplodingTimer cpy #EXPLODING_FLASH_TIME bcc .doExplodingFlash ldy explodingBombNumber ; get the current exploding bomb bne .maskColorValues ; branch if no bomb exploding cpx #4 bcc .maskColorValues lda randomSeed .doExplodingFlash eor bombExplodingTimer .maskColorValues eor colorEOR and hueMask sta bomberKernelBGColor,x dex bpl .loadColorsLoop sta COLUBK ; color the Mad Bomber are background ldx playerNumber ; get the current player number lda scoreColor,x ; get the score color for the player sta COLUP0 ; color the player's score sta COLUP1 DisplayKernel SUBROUTINE .waitTime lda INTIM bne .waitTime sta WSYNC ;-------------------------------------- sta VBLANK ; 3 = @03 enable TIA sta COLUPF ; 3 = @06 color playfield black lda #MSBL_SIZE8 | PF_PRIORITY | PF_REFLECT; 2 sta CTRLPF ; 3 = @11 sta PF0 ; 3 = @14 used to black out HMOVE lines ldy #THREE_COPIES ; 2 sty NUSIZ0 ; 3 = @19 sty NUSIZ1 ; 3 = @22 ldy #H_SCORE - 1 ; 2 sty VDELP0 ; 3 = @27 VDEL player0 and player 1 (D0 = 1) sty VDELP1 ; 3 = @30 .scoreLoop sty loopCount ; 3 lda (digitPointer + 10),y ; 5 sta tempCharHolder ; 3 sta WSYNC ; 3 ;-------------------------------------- lda (digitPointer),y ; 5 sta GRP0 ; 3 = @08 lda (digitPointer + 2),y ; 5 sta GRP1 ; 3 = @16 lda (digitPointer + 4),y ; 5 sta GRP0 ; 3 = @24 lda (digitPointer + 8),y ; 5 tax ; 2 lda (digitPointer + 6),y ; 5 ldy tempCharHolder ; 3 sta GRP1 ; 3 = @42 stx GRP0 ; 3 = @45 sty GRP1 ; 3 = @48 sta GRP0 ; 3 = @48 ldy loopCount ; 3 dey ; 2 bpl .scoreLoop ; 2³ iny ; 2 = @57 y = 0 lda bombLSB ; 3 get the bomb LSB to store in sta bombGraphicPointer ; 3 the graphic pointer for animation lda bombFineCoarse ; 3 get the bomb's fine/coarse value sta WSYNC ; 3 = @69 ;-------------------------------------- sty VDELP0 ; 3 = @03 turn off the verical delay of the sty VDELP1 ; 3 = @06 players (D1 = 0) sty GRP0 ; 3 = @09 clear the player graphics sty GRP1 ; 3 = @12 sta REFP0 ; 3 = @15 set reflect state sta HMP0 ; 3 = @18 set the bomb's fine horiz position and #7 ; 2 mask to get the coarse horiz value tax ; 2 .coarseMoveBomb dex ; 2 bpl .coarseMoveBomb ; 2³ sta RESP0 ; 3 set bomb's coarse position sta WSYNC ;-------------------------------------- sty NUSIZ1 ; 3 = @03 single copy of player 1 (y = 0) sty tempBombGraphic ; 3 clear the temp bomb graphic sty NUSIZ0 ; 3 = @09 single copy of player 0 (y = 0) lda madBomberFineCoarse ; 3 get bomber's fine/coarse value sta HMP1 ; 3 = @15 set bomber's fine horiz position and #7 ; 2 mask to get the coarse horiz value tax ; 2 lda bombDropVelocity ; 3 subtraction must start @ cycle 22 .coarseMoveMadBomber dex ; 2 bpl .coarseMoveMadBomber ; 2³ sta RESP1 ; 3 set bomber's coarse position sta WSYNC ;-------------------------------------- sta HMOVE ; 3 = @03 ldx #H_MAD_BOMBER - 1 ; 2 ora #H_BOMB * 2 ; 2 tay ; 2 lda playerScore ; 3 get the player's score (MSB) cmp #1 ; 2 see if the player scored >= 100,000 ror bomberExpressionState ; 5 rotate carry into D7 bmi DrawMadBomber ; 2³ lda bombExplodingTimer ; 3 bne .colorMadBomberLoop ; 2³ lda remainingBuckets ; 3 beq .colorMadBomberLoop ; 2³ DrawMadBomber lda MadBomberColors,x ; 4 eor colorEOR ; 3 and hueMask ; 3 sta WSYNC ; 3 ;-------------------------------------- sta COLUP1 ; 3 = @03 lda MadBomber,x ; 4 cpx #22 ; 2 are we drawing Bomber's upper mouth bne .checkForLowerMouth ; 2³ if not check lower mouth lda #%01101100 ; 2 Mad Bomber frown graphic .checkForLowerMouth bcs .drawMadBomber ; 2³ lda #%01101100 ; 2 Mad Bomber surprise graphic bit bomberExpressionState ; 3 bmi .drawMadBomber ; 2³ lda #%01010100 ; 2 Mad Bomber smile graphic .drawMadBomber sta GRP1 ; 3 dey ; 2 dex ; 2 cpx #21 ; 2 bcs DrawMadBomber ; 2³ .colorMadBomberLoop lda MadBomberColors,x ; 4 eor colorEOR ; 3 and hueMask ; 3 cpx #3 ; 2 bne .colorMadBomber ; 2³ + 1 crosses page boundary lda backgroundColor ; 3 sta WSYNC ; 3 ;-------------------------------------- sta COLUBK ; 3 = @03 jmp .drawBombHeldByBomber ; 3 .colorMadBomber sta WSYNC ; 3 ;-------------------------------------- sta COLUP1 ; 3 = @03 lda MadBomber,x ; 4 sta GRP1 ; 3 = @10 .drawBombHeldByBomber lda bombColors - 1,y ; 4 sta COLUP0 ; 3 = @17 lda tempBombGraphic ; 3 sta GRP0 ; 3 = @23 dey ; 2 cpy #H_BOMB ; 2 bcs .skipDrawHeldBomb ; 2³ lda (bombGraphicPointer),y ; 5 sta tempBombGraphic ; 3 .skipDrawHeldBomb dex ; 2 bpl .colorMadBomberLoop ; 2³ + 1 crosses page boundary lda SWCHB ; 4 read the console switches ldx playerNumber ; 3 get the current player number bne .onePlayerGame ; 2³ get player 1 difficulty value asl ; 2 shift to get difficulty in carry .onePlayerGame asl ; 2 lda #ONE_COPY ; 2 sta bombNumber ; 3 reset bomb number at kernel start bcs .skipDoubleSizeBuckets ; 2³ lda #DOUBLE_SIZE ; 2 .skipDoubleSizeBuckets sta WSYNC ; 3 ;-------------------------------------- sta NUSIZ1 ; 3 set size of player buckets lda bombColors - 1,y ; 4 sta COLUP0 ; 3 = @10 lda tempBombGraphic ; 3 sta GRP0 ; 3 = @16 lda #0 ; 2 sta GRP1 ; 3 = @21 clear the Mad Bomber graphic sta CXCLR ; 3 = @24 clear all previous collisions bcc .calibratePaddle ; 2³ branch if difficulty set to AMATEUR lda #10 ; 2 .calibratePaddle clc ; 2 adc #108 ; 2 sta paddleRangeMax ; 3 adc #6 ; 2 sta paddleValue ; 3 lda #H_KERNEL ; 2 get the kernel height sta scanline ; 3 store it (decremented in kernel) dey ; 2 bmi StartBombKernel ; 2³ .topDroppingBombKernel cpy #H_BOMB ; 2 bcc .drawTopDroppingBomb ; 2³ sta WSYNC ; 3 ;-------------------------------------- dec scanline ; 5 dey ; 2 bne .topDroppingBombKernel ; 2³ .drawTopDroppingBomb lda (bombGraphicPointer),y ; 5 sta WSYNC ; 3 ;-------------------------------------- sta GRP0 ; 3 = @03 draw the bomb graphic lda bombColors - 1,y ; 4 get the bomb colors to sta COLUP0 ; 3 = @10 color the bomb dec scanline ; 5 reduce the scanline count lda INPT0,x ; 4 read the paddle controller bmi .skipReducePaddleValue ; 2³ check if capacitor charged dec paddleValue ; 5 .skipReducePaddleValue dey ; 2 bpl .drawTopDroppingBomb ; 2³ StartBombKernel SUBROUTINE sta WSYNC ; 3 ;-------------------------------------- inc bombNumber ; 5 increment bomb number ldy bombNumber ; 3 load y with bomb number for index lda bombFineCoarse,y ; 4 get the fine/coarse value for bomb sta HMP0 ; 3 = @15 set bomb's fine motion value sta REFP0 ; 3 = @18 set reflect state and #7 ; 2 mask the fine motion value tay ; 2 move to y for coarse movement .coarseMoveBomb dey ; 2 bpl .coarseMoveBomb ; 2³ sta RESP0 ; 3 set bomb's coarse position sta WSYNC ; 3 ;-------------------------------------- ldy bombNumber ; 3 load y with bomb number for index lda bombLSB,y ; 4 get the bomb's sprite LSB sta bombGraphicPointer ; 3 store it in graphic pointer SLEEP 2 ; 2 waste 2 cycles so coarse lda bucketsFineCoarse ; 3 get bucket's fine/coarse value sta HMP1 ; 3 = @18 set bucket's fine motion and #7 ; 2 mask the fine motion value tay ; 2 move to y for coarse movement .coarseMoveBuckets dey ; 2 bpl .coarseMoveBuckets ; 2³ sta RESP1 ; 3 set bucket's coarse position sta WSYNC ; 3 ;-------------------------------------- sta HMOVE ; 3 = @03 ldy #H_BOMB - 1 ; 2 lda (bombGraphicPointer),y ; 5 sta GRP0 ; 3 = @13 draw the bomb graphic lda bombColors - 1,y ; 4 get the bomb colors to sta COLUP0 ; 3 color the bomb lda INPT0,x ; 4 read the paddle controller bmi .skipReducePaddleValue ; 2³ check if capacitor charged dec paddleValue ; 5 reduce paddle value if not charged .skipReducePaddleValue dey ; 2 sta HMCLR ; 3 = @36 clear horizontal movements DroppingBombKernel SUBROUTINE .drawBomb lda bombNumber ; 3 get the current bomb number cmp explodingBombNumber ; 3 skip the explosion random colors bne .skipExplosionColor ; 2³ if this is not an exploding bomb lda randomSeed ; 3 get the random number sta bombColors - 1,y ; 5 set the color of the bomb explosion .skipExplosionColor lda (bombGraphicPointer),y ; 5 sta WSYNC ;-------------------------------------- sta GRP0 ; 3 = @03 draw the bomb lda bombColors - 1,y ; 4 get the color for the bomb sta COLUP0 ; 3 = @10 color the bomb .bombKernelLoop dec scanline ; 5 beq DrawBucketKernel ; 2³ + 1 crosses page boundary lda INPT0,x ; 4 read the paddle controller bmi .skipReducePaddleValue ; 2³ check if capacitor charged dec paddleValue ; 5 reduce paddle value if not charged .skipReducePaddleValue dey ; 2 bpl .drawBomb ; 2³ inc bombNumber ; 5 increment bomb number ldy bombNumber ; 3 load y with bomb number for index lda bombLSB,y ; 4 get the bomb's sprite LSB sta WSYNC ;-------------------------------------- SLEEP 2 ; 2 waste 2 cycles for coarse move sta bombGraphicPointer ; 3 store it in graphic pointer sty bombIndexCaught ; 3 assume bomb will be caught by bucket lda bombFineCoarse,y ; 4 get the fine/coarse value for bomb sta HMP0 ; 3 = @15 set bomb's fine motion value sta REFP0 ; 3 = @18 set reflect state and #7 ; 2 mask the fine motion value tay ; 2 move to y for coarse movement .coarseMoveBomb dey ; 2 bpl .coarseMoveBomb ; 2³ sta RESP0 ; 3 set bomb's coarse position sta WSYNC ;-------------------------------------- sta HMOVE ; 3 ldy #H_BOMB ; 2 dec scanline ; 5 beq JumpIntoBucketKernel ; 2³ + 1 crosses page boundary bne .bombKernelLoop ; 3 unconditional branch DrawBucketKernel SUBROUTINE ldx #H_BUCKETS * MAX_BUCKETS;2 dey ; 2 bmi .doNextBomb ; 2³ bpl .determineBombColor ; 3 unconditional branch JumpIntoBucketKernel dey ; 2 ldx #H_BUCKETS * MAX_BUCKETS - 1;2 .drawBucketKernelLoop stx tempXHolder ; 3 ldx playerNumber ; 3 get the current player number lda INPT0,x ; 4 read the paddle controller bmi .resetKernelScanline ; 2³ check if capacitor charged dec paddleValue ; 5 reduce paddle value if not charged .resetKernelScanline ldx tempXHolder ; 3 .determineBombColor lda bombNumber ; 3 get the current bomb number cmp explodingBombNumber ; 3 skip the explosion random colors bne .skipExplosionColor ; 2³ if this is not an exploding bomb lda BucketColors - 1,x ; 4 get the bucket colors and hueMask ; 3 sta WSYNC ;-------------------------------------- sta COLUP1 ; 3 = @03 lda.w randomSeed ; 4 get the random number sta COLUP0 ; 3 = @10 set the color of the bomb explosion jmp .drawBucket ; 3 .skipExplosionColor lda BucketColors - 1,x ; 4 eor colorEOR ; 3 and hueMask ; 3 sta WSYNC ;-------------------------------------- sta COLUP1 ; 3 = @03 lda bombColors - 1,y ; 4 sta COLUP0 ; 3 = @10 .drawBucket lda bucketGraphics - 1,x ; 4 sta GRP1 ; 3 = @20 lda (bombGraphicPointer),y ; 5 sta GRP0 ; 3 = @28 dex ; 2 beq CopyrightKernel ; 2³ dey ; 2 .nextBucket bpl .drawBucketKernelLoop ; 2³ .doNextBomb inc bombNumber ; 5 increment bomb number ldy bombNumber ; 3 load y with bomb number for index bit CXPPMM ; 3 check player to player collisions bmi .bombCaught ; 2³ branch if P0 and P1 collide sty bombIndexCaught ; 3 .bombCaught lda bombLSB,y ; 4 get the bomb's sprite LSB sta bombGraphicPointer ; 3 store it in graphic pointer lda bombFineCoarse,y ; 4 get the fine/coarse value for bomb sta HMP0 ; 3 = @66 set bomb's fine motion value sta REFP0 ; 3 = @69 set reflect state (wiggle wick) and #7 ; 2 mask the fine motion value sta WSYNC ;-------------------------------------- tay ; 2 move to y for coarse movement lda BucketColors - 1,x ; 4 eor colorEOR ; 3 and hueMask ; 3 sta COLUP1 ; 3 = @15 lda bucketGraphics - 1,x ; 4 sta GRP1 ; 3 = @22 .coarseMoveBomb_b dey ; 2 bpl .coarseMoveBomb_b ; 2³ sta RESP0 ; 3 lda BombColors + 13,x ; 4 eor colorEOR ; 3 sta WSYNC ;-------------------------------------- sta HMOVE ; 3 dex ; 2 beq .jmpIntoCopyrightKernel; 2³ and hueMask ; 3 sta COLUP1 ; 3 = @13 lda bucketGraphics - 1,x ; 4 sta GRP1 ; 3 = @20 ldy #H_BOMB - 1 ; 2 dex ; 2 bne .nextBucket ; 2³ CopyrightKernel sta WSYNC ;-------------------------------------- .jmpIntoCopyrightKernel stx GRP0 ; 3 clear the player graphics (x = 0) stx GRP1 ; 3 lda colorEOR ; 3 and hueMask ; 3 sta WSYNC ;-------------------------------------- sta.w COLUBK ; 4 = @04 eor #LOGO_COLOR ; 2 and hueMask ; 3 sta COLUP0 ; 3 = @12 sta COLUP1 ; 3 = @15 stx HMCLR ; 3 = @18 clear horizontal movements stx REFP0 ; 3 = @21 stx REFP1 ; 3 = @24 lda #HMOVE_L1 | TWO_COPIES ; 2 sta NUSIZ0 ; 3 = @29 sta NUSIZ1 ; 3 = @32 sta HMP1 ; 3 = @35 move player 1 left 1 pixel sta RESP0 ; 3 = @38 player 0 @ pixel 114 sta RESP1 ; 3 = @41 player 1 @ pixel 122 ldx #H_ACTIVISION_LOGO - 1 ; 2 .activisionLogoLoop sta WSYNC ;-------------------------------------- sta HMOVE ; 3 lda Copyright0,x ; 4 sta GRP0 ; 3 = @10 lda Copyright1,x ; 4 sta GRP1 ; 3 = @17 jsr Waste12Cycles ; 12 lda Copyright3,x ; 4 tay ; 2 lda Copyright2,x ; 4 sta GRP0 ; 3 = @42 sty GRP1 ; 3 = @45 sta HMCLR ; 3 = @48 dex ; 2 bpl .activisionLogoLoop ; 2³ Overscan SUBROUTINE lda #OVERSCAN_TIME sta TIM64T ; set timer for overscan ldx SWCHA ; read the paddle fire buttons inx beq CalculateBucketPosition ; branch if fire buttons not pressed sty colorCycleMode ; y = 0 from above copyright loop CalculateBucketPosition sec ; set carry for subtraction lda paddleValue ; get the paddle value sbc #5 ; subtract by 5 bpl .calcPaddleBucketPosDelta ; keep the value if not negative tya ; set accumulator to 0 .calcPaddleBucketPosDelta sec ; set carry for subtraction sbc bucketHorizPosition clc ; clear carry for rotation below bpl .compareToPaddleRange ; branch if subtraction is positive sec ; set carry for rotation .compareToPaddleRange ror ; rotate a right (carry now in D7) cmp #PADDLE_MIN bcc .skipAttractModeReset ; branch if less than 2 cmp #PADDLE_MAX bcs .skipAttractModeReset ; branch if greater than 254 sty colorCycleMode ; set to 0 .skipAttractModeReset clc ; clear carry for addition adc bucketHorizPosition ; 2 <= a <= 254 cmp paddleRangeMax ; if less than the max range then bcc .setBucketHorizontalPosition ; set bucket horizontal position lda paddleRangeMax ; set bucket position to max paddle range .setBucketHorizontalPosition sta bucketHorizPosition ; save for the bucket's horiz position jsr CalcPosX ; calculate fine/coarse value sta bucketsFineCoarse ldx #H_BUCKETS - 1 .storeBucketGraphicsLoop lda BucketGraphics,x sta thirdRowBucketGraphics,x sta secondRowBucketGraphics,x sta firstRowBucketGraphics,x ldy remainingBuckets beq .clearFirstRowBucket dey beq .clearSecondRowBucket dey beq .clearThirdRowBucket bne .nextBucketGraphicLine ; unconditional branch .clearFirstRowBucket sty firstRowBucketGraphics,x .clearSecondRowBucket sty secondRowBucketGraphics,x .clearThirdRowBucket sty thirdRowBucketGraphics,x .nextBucketGraphicLine dex bpl .storeBucketGraphicsLoop StoreSplashGraphics ldy catchingBucketNumber ; get number of bucket that caught bomb ldx WaterSplashOffset,y ; set x to it's RAM offset lda catchingBombSoundFreq ; get the cathing bomb sound frequency asl ; multiply the value by 2 and #%00011000 ; make sure value is less than 24 (i.e. ; 4 animations * H_SPLASH) tay lda #H_SPLASH - 1 sta splashGraphicLoopCount .storeBucketSplashGraphics lda SplashAnimations,y sta bucketGraphics + 8,x iny inx dec splashGraphicLoopCount bpl .storeBucketSplashGraphics VerticalSync SUBROUTINE .waitTime lda INTIM ; wait until overscan is done bne .waitTime ldy #DUMP_PORTS | DISABLE_TIA sty WSYNC ; wait for next scan line sty VBLANK ; disable TIA and discharge paddles sty VSYNC ; start vertical sync (D1 = 1) sty WSYNC sty WSYNC sty WSYNC sta VSYNC ; end vertical sync (D1 = 0) inc frameCount ; increment frame count each new frame bne ReadConsoleSwitches inc colorCycleMode ; increment every 256 frames bne ReadConsoleSwitches sec ; set carry bit ror colorCycleMode ; rotate carry into D7 for color cycling ReadConsoleSwitches ldy #$FF ; assume color mode lda SWCHB ; read the console switches and #BW_MASK ; get the B/W switch value bne .colorMode ; branch if set to color ldy #$0F .colorMode tya ldy #0 bit colorCycleMode ; check color cycling mode bpl .setColorHueMask ; branch if not in color cycling mode and #$F7 ; mask for VCS colors (i.e. D0 not used) ldy colorCycleMode .setColorHueMask sty colorEOR asl colorEOR sta hueMask lda #VBLANK_TIME sta WSYNC sta TIM64T ; set timer for vertical blank time ldy #0 sty temp lda SWCHB ; read the console switches lsr ; RESET now in carry bcs .skipGameReset jsr ClearGameRAM stx gameState ; x = #$ff asl gameSelection ; shift the game selection left sec ror gameSelection ; set D7 of gameSelection high lda #MAX_BUCKETS ; set the number of remaining buckets sta remainingBuckets ; also ready so we'll branch to sta reservedRemainingBuckets ; .resetSelectDebounce below :-) .skipGameReset lsr ; SELECT now in carry bcs .resetSelectDebounce lda selectDebounce ; get the select debounce delay beq .incrementGameSelection ; if it's zero -- increase game selection dec selectDebounce ; decrement select debounce bpl .skipGameSelect ; unconditional branch .incrementGameSelection inc gameSelection JumpIntoConsoleSwitchCheck jsr ClearGameRAM lda gameSelection ; get the game selection value and #1 ; alternate the value between 0 and 1 and sta gameSelection ; set D7 low to show RESET not pressed tay ; move game selection to y iny ; increment the value so it shows as sty playerScore + 2 ; 1 and/or 2 to the user :-) ldy #SELECT_DELAY .resetSelectDebounce sty selectDebounce ; reset select debounce rate .skipGameSelect bit gameSelection ; see if game was RESET this frame bpl .jumpToPlayGameSounds ; if not then play the game sounds lda remainingBuckets bne CheckForExplodingBombs lda frameCount and #$7F bne .jumpToPlayGameSounds beq SwitchToOtherPlayer ; unconditional branch CheckForExplodingBombs lda bombExplodingTimer ; get the exploding bomb timer beq DoneExplodingBombs cmp #EXPLODING_FLASH_TIME bcc CheckToReduceNumberOfBuckets beq DetermineNextExplodingBomb .setExplodingBombSprite lda bombExplodingTimer ; get the exploding bomb timer and #$0C asl ; multiply by 4 (i.e. 12 * 4 = asl ; NUM_EXPLODE_ANIM * H_BOMB = 48) adc #NumberFonts ; set MSB pointer to digits sta digitPointer + 1,x sta digitPointer + 3,x dey bpl .bcd2DigitLoop ldx #0 .suppressZeroLoop lda digitPointer,x ; get LSB pointer to digit eor #