LIST OFF ; *** S U R R O U N D *** ; Copyright 1977 Atari, Inc. ; Designer: Alan Miller ; Analyzed, labeled and commented ; by Thomas Jentzsch (JTZ) ; Last Update: Aug 25, 2001 (v0.1) ; by Dennis Debro ; Last Update: Sept 10, 2004 processor 6502 ; ; NOTE: You must compile this with vcs.h version 105 or greater. ; TIA_BASE_READ_ADDRESS = $30 ; set the read address base so this runs on ; the real VCS and compiles to the exact ; ROM image include ..\..\..\vcs.h LIST ON ;=============================================================================== ; A S S E M B L E R - S W I T C H E S ;=============================================================================== NTSC = 0 PAL = 1 COMPILE_VERSION = NTSC ; change this to compile for different ; regions ;============================================================================ ; T I A - C O N S T A N T S ;============================================================================ HMOVE_L7 = $70 HMOVE_L6 = $60 HMOVE_L5 = $50 HMOVE_L4 = $40 HMOVE_L3 = $30 HMOVE_L2 = $20 HMOVE_L1 = $10 HMOVE_0 = $00 HMOVE_R1 = $F0 HMOVE_R2 = $E0 HMOVE_R3 = $D0 HMOVE_R4 = $C0 HMOVE_R5 = $B0 HMOVE_R6 = $A0 HMOVE_R7 = $90 HMOVE_R8 = $80 ; SWCHA joystick bits: PLAYER0_JOYSTICK_MASK = $F0 PLAYER1_JOYSTICK_MASK = $0F MOVE_RIGHT = %10000000 MOVE_LEFT = %01000000 MOVE_DOWN = %00100000 MOVE_UP = %00010000 NO_MOVE = %11111111 PLAYER0_NO_MOVE = NO_MOVE & PLAYER0_JOYSTICK_MASK PLAYER1_NO_MOVE = NO_MOVE & PLAYER1_JOYSTICK_MASK ; mask for SWCHB BW_MASK = %1000 ; black and white bit SELECT_MASK = %10 RESET_MASK = %01 ;============================================================================ ; U S E R - C O N S T A N T S ;============================================================================ ROM_BASE_ADDRESS = $F000 IF COMPILE_VERSION = NTSC H_KERNEL = 200 H_KERNEL_SECTION = 9 KERNEL_SKIPLINES = 3 BOTTOM_BORDER_START = 256-H_KERNEL+3 KERNEL_END = 232 ELSE H_KERNEL = 239 H_KERNEL_SECTION = 10 KERNEL_SKIPLINES = 14 BOTTOM_BORDER_START = 256-H_KERNEL+2 KERNEL_END = 213 ENDIF H_DIGIT = 5 VBLANK_TIME = 40 NUM_PLAYERS = 2 NUM_ROWS = 20 ; number of playfield rows (blocks) XMIN = 0 XMAX = 40 YMIN = 0 YMAX = 20 INIT_AMATEUR_MIN_BOUNDARY = 3 INIT_PRO_MIN_BOUNDARY = 6 PLAYER_START_Y = 10 PLAYER1_START_X = 10 PLAYER2_START_X = 30 MAX_SCORE = $10 ; BCD SELECT_DELAY = 63 FRAME_DELAY = 16 ; frame delay in gameSpeed upper nybbles MAX_GAME_SELECTION = 14 ;game state values SYSTEM_POWERUP = %00010000 GAME_RUNNING = %11111111 ;player state flags PLAYER1_COLL_MASK = %01000000 PLAYER2_COLL_MASK = %10000000 PLAYER_COLL_MASK = PLAYER1_COLL_MASK | PLAYER2_COLL_MASK ; game variation flags NOGRAFFITI = %10000000 SPEEDUP = %01000000 NOGRAFFITI2 = %00100000 ; always same as NOGRAFFITI SINGLEPLAYER = %00010000 WRAPAROUND = %00001000 ERASE = %00000100 DIAGONAL = %00000001 ;============================================================================ ; Z P - V A R I A B L E S ;============================================================================ SEG.U variables org $80 playfieldGraphics ds 100 ;-------------------------------------- pf0Graphics = playfieldGraphics leftPF1Graphics = playfieldGraphics+NUM_ROWS leftPF2Graphics = leftPF1Graphics+NUM_ROWS rightPF1Graphics = leftPF2Graphics+NUM_ROWS rightPF2Graphics = rightPF1Graphics+NUM_ROWS frameCount ds 1 nextKernelSection ds 1 ;-------------------------------------- playerJoystickValues = nextKernelSection ;-------------------------------------- playerMovingHorizontal = playerJoystickValues ;-------------------------------------- playfieldRAMPointer0 = playerMovingHorizontal workingJoystickValue ds 1 ;-------------------------------------- minBoundaryLookup = workingJoystickValue tempPlayfieldPattern ds 1 ;-------------------------------------- hueMask = tempPlayfieldPattern ;-------------------------------------- playfieldRAMPointer1 = tempPlayfieldPattern ;-------------------------------------- combinedMotionValues = playfieldRAMPointer1 ;-------------------------------------- scoreGraphic1 = combinedMotionValues tempJoystickValues ds 1 ;-------------------------------------- scoreGraphic2 = tempJoystickValues ;-------------------------------------- colorXOR = scoreGraphic2 playfieldLowerBoundary ds 1 ;-------------------------------------- playfieldLeftBoundary = playfieldLowerBoundary ;-------------------------------------- playfieldRightBoundary = playfieldLeftBoundary computerMotion ds 1 ;-------------------------------------- computerJoystickValue = computerMotion playfieldUpperBoundary ds 1 ;-------------------------------------- playfieldMinBoundary = playfieldUpperBoundary gameSpeed ds 1 playerVertPos ds 2 ;-------------------------------------- player1VertPos = playerVertPos player2VertPos = player1VertPos+1 playerHorizPos ds 2 ;-------------------------------------- player1HorizPos = playerHorizPos player2HorizPos = player1HorizPos+1 gameVariation ds 1 playerMotion ds 2 ;-------------------------------------- player1Motion = playerMotion player2Motion = player1Motion+1 colorCycleTimer ds 1 selectDebounce ds 1 playerScores ds 2 ;-------------------------------------- player1Score = playerScores player2Score = playerScores+1 gameState ds 1 gameSelection ds 1 unused ds 1 playerState ds 1 joystickValues ds 1 randomMotionSeed ds 1 ;============================================================================ ; R O M - C O D E ;============================================================================ SEG Bank0 org ROM_BASE_ADDRESS DisplayKernel SUBROUTINE .waitTime lda INTIM bne .waitTime sta WSYNC ; end last scan line sta VBLANK ; enable TIA (D1 = 0) lda #%00000010 ; set playfield to SCORE mode (i.e. sta CTRLPF ; player colors same as score) ldx #KERNEL_SKIPLINES .skipKernelLines sta WSYNC sta HMCLR dex bne .skipKernelLines ldx #5 lda #0 sta scoreGraphic1 sta scoreGraphic2 ScoreKernel sta WSYNC ;-------------------------------------- lda scoreGraphic1 ; 3 get the score graphic for display sta PF1 ; 3 = @06 lda player1Score ; 3 get player1's score and #$F0 ; 2 mask lower nybbles lsr ; 2 move upper nybbles to lower nybbles lsr ; 2 lsr ; 2 lsr ; 2 tay ; 2 lda DigitOfsTab,y ; 4 clc ; 2 adc DigitOfs2Tab,x ; 4 tay ; 2 lda NumberFonts,y ; 4 read the number fonts and #$F0 ; 2 mask the lower nybble sta scoreGraphic1 ; 3 save it in the score graphic lda scoreGraphic2 ; 3 get the score graphic for display sta PF1 ; 3 = @48 lda player1Score ; 3 get player1's score and #$0F ; 2 mask upper nybbles tay ; 2 lda DigitOfsTab,y ; 4 clc ; 2 adc DigitOfs2Tab,x ; 4 sta WSYNC ;-------------------------------------- tay ; 2 lda NumberFonts,y ; 4 read the number fonts and #$0F ; 2 mask the upper nybble ora scoreGraphic1 ; 3 or with score graphic to get LSB sta scoreGraphic1 ; 3 value sta PF1 ; 3 = @17 lda player2Score ; 3 get player2's score and #$F0 ; 2 mask lower nybbles lsr ; 2 move upper nybbles to lower nybbles lsr ; 2 lsr ; 2 lsr ; 2 tay ; 2 lda DigitOfsTab,y ; 4 ldy scoreGraphic2 ; 3 sty PF1 ; 3 = @44 clc ; 2 adc DigitOfs2Tab,x ; 4 tay ; 2 lda NumberFonts,y ; 4 read the number fonts and #$F0 ; 2 mask the lower nybble sta scoreGraphic2 ; 3 save it in the score graphic lda player2Score ; 3 get player2's score and #$0F ; 2 mask upper nybbles sta WSYNC ;-------------------------------------- tay ; 2 lda DigitOfsTab,y ; 4 ldy scoreGraphic1 ; 3 sty PF1 ; 3 = @12 clc ; 2 adc DigitOfs2Tab,x ; 4 tay ; 2 lda NumberFonts,y ; 4 read the number fonts and #$0F ; 2 and gameState ; 3 and gameState ; 3 ora scoreGraphic2 ; 3 or with score graphic to get LSB sta scoreGraphic2 ; 3 value sta PF1 ; 3 = @41 dex ; 2 bmi GameKernel ; 2³ jmp ScoreKernel ; 3 GameKernel sta WSYNC ;-------------------------------------- lda #0 ; 2 sta PF0 ; 3 = @05 clear the playfield graphics sta PF1 ; 3 = @08 sta PF2 ; 3 = @11 sta CTRLPF ; 3 = @14 non-reflective playfield ldy #256-H_KERNEL ; 2 lda #256-H_KERNEL+2 ; 2 sta nextKernelSection ; 3 ldx #-1 ; 2 .gameKernelLoop cpy #BOTTOM_BORDER_START ; 2 bcc .drawBorder ; 2³+1 cpy nextKernelSection ; 3 bcs .endKernelSection ; 2³ .kernelSectionLoop sta WSYNC ;-------------------------------------- lda pf0Graphics,x ; 4 get the PF0 data asl ; 2 shift the lower nybbles to the upper asl ; 2 nybbles for the left PF0 values asl ; 2 asl ; 2 sta PF0 ; 3 = @15 display left PF0 pattern lda leftPF1Graphics,x ; 4 get the left PF1 data sta PF1 ; 3 = @22 display left PF1 pattern lda leftPF2Graphics,x ; 4 sta PF2 ; 3 = @29 lda pf0Graphics,x ; 4 sta PF0 ; 3 = @36 only upper nybbles used for PF0 lda rightPF1Graphics,x ; 4 sta PF1 ; 3 = @43 iny ; 2 lda rightPF2Graphics,x ; 4 sta PF2 ; 3 = @52 cpy nextKernelSection ; 3 bcc .kernelSectionLoop ; 2³ .endKernelSection inx ; 2 lda #$0F ; 2 cpx player1VertPos ; 3 beq .drawPlayer1 ; 2³ lda #0 ; 2 .drawPlayer1 sta WSYNC ;-------------------------------------- sta GRP0 ; 3 = @03 lda #$0F ; 2 cpx player2VertPos ; 3 beq .drawPlayer2 ; 2³ lda #0 ; 2 .drawPlayer2 sta GRP1 ; 3 = @15 lda #0 ; 2 sta PF0 ; 3 = @20 sta PF1 ; 3 = @23 sta PF2 ; 3 = @26 tya ; 2 clc ; 2 adc #H_KERNEL_SECTION ; 2 sta nextKernelSection ; 3 .contKernel iny ; 2 beq .endGameKernel ; 2³ cpy #KERNEL_END ; 2 bcc .gameKernelLoop ; 2³+1 .drawBorder lda #$FF ; 2 sta WSYNC ;-------------------------------------- sta PF0 ; 3 = @03 sta PF1 ; 3 = @06 sta PF2 ; 3 = @09 lda #0 ; 2 sta GRP0 ; 3 = @14 sta GRP1 ; 3 = @17 beq .contKernel ; 3 unconditional branch .endGameKernel lda #0 ; 2 sta ENAM0 ; 3 = @45 disable missle graphics (missiles sta ENAM1 ; 3 = @48 aren't used in this game) rts ; 6 VerticalSync SUBROUTINE lda #42 sta HMCLR ; clear all horizontal motion sta WSYNC ; end last scan line sta VBLANK ; disable TIA (D1 = 1) sta VSYNC ; start vertical sync (D1 = 1) sta TIM8T ; set vertical sync wait time inc frameCount inc randomMotionSeed ; used to help decide computer direction .waitTime lda INTIM bne .waitTime sta WSYNC ; end last scan line sta VSYNC ; end vertical sync (D1 = 0) lda #VBLANK_TIME sta TIM64T ; set vertical blank wait time rts SinglePlayerGame lda #-1 sta playfieldLowerBoundary sta playfieldUpperBoundary lda #INIT_PRO_MIN_BOUNDARY bit SWCHB ; get P0 difficutly setting bvs DeterminePlayfieldUpperBoundary lda #INIT_AMATEUR_MIN_BOUNDARY DeterminePlayfieldUpperBoundary sta minBoundaryLookup lda #$00 sta playfieldRAMPointer1+1 ; set RAM pointer MSB to read ZP ldy player1HorizPos ; get the computer's horizontal position lda PFGraphicsPointerTable,y ; read the LSB for the playfield graphics sta playfieldRAMPointer1 ldx #MOVE_UP stx computerMotion ; set computer motion to up ldx player1HorizPos ; get the computer's horizontal position ldy player1VertPos ; get the computer's vertical position .computePFUpperBoundary inc playfieldUpperBoundary dey ; check next upper position bmi .foundPFUpperBoundary ; found limit if vertically out of range lda PlayfieldPatternTable,x ; get the playfield pattern and (playfieldRAMPointer1),y ; and with current playfield value beq .computePFUpperBoundary ; if empty then limit hasn't been found .foundPFUpperBoundary lda minBoundaryLookup ; get min lookup value cmp playfieldUpperBoundary ; if less than value found then bcs DeterminePlayfieldLowerBoundary sta playfieldUpperBoundary ; set it to PF upper boundary DeterminePlayfieldLowerBoundary ldy player1VertPos ; get the computer's vertical position ldx player1HorizPos ; get the computer's horizontal position .computePFLowerBoundary inc playfieldLowerBoundary iny ; check next lower position cpy #YMAX+1 bcs .foundPFLowerBoundary ; found limit if vertically out of range lda PlayfieldPatternTable,x ; get the playfield pattern and (playfieldRAMPointer1),y ; and with current playfield value beq .computePFLowerBoundary ; if empty then limit hasn't been found .foundPFLowerBoundary lda playfieldLowerBoundary ; get lower boundary cmp playfieldUpperBoundary ; compare with the upper boundary bcc DeterminePlayfieldLeftBoundary ldx playfieldUpperBoundary ; if greater use a random value to cpx minBoundaryLookup ; determine motion bcc .setComputerMotionToDown bit randomMotionSeed bvs DeterminePlayfieldLeftBoundary cmp minBoundaryLookup bcc .setComputerMotionToDown lda minBoundaryLookup .setComputerMotionToDown sta playfieldMinBoundary ldx #MOVE_DOWN stx computerMotion DeterminePlayfieldLeftBoundary ldy player1VertPos ; get the computer's vertical position ldx player1HorizPos ; get the computer's horizontal position lda #-1 sta playfieldLeftBoundary .computePFLeftBoundary inc playfieldLeftBoundary dex ; check next left position bmi .foundPFLeftBoundary ; found limit if horizontally out of range lda PFGraphicsPointerTable,x ; read the LSB for the playfield graphics sta playfieldRAMPointer1 lda (playfieldRAMPointer1),y ; get the current playfield value and PlayfieldPatternTable,x ; and with playfield pattern beq .computePFLeftBoundary ; if empty then limit hasn't been found .foundPFLeftBoundary lda playfieldLeftBoundary ; get left boundary cmp playfieldMinBoundary ; compare with minimum value bcc DeterminePlayfieldRightBoundary ldx playfieldMinBoundary ; if greater use a random value to cpx minBoundaryLookup ; determine motion bcc .setComputerMotionToLeft bit randomMotionSeed bvs DeterminePlayfieldRightBoundary cmp minBoundaryLookup bcc .setComputerMotionToLeft lda minBoundaryLookup .setComputerMotionToLeft sta playfieldMinBoundary ldx #MOVE_LEFT stx computerMotion DeterminePlayfieldRightBoundary ldy player1VertPos ; get the computer's vertical position ldx player1HorizPos ; get the computer's horizontal position lda #-1 sta playfieldRightBoundary .computePFRightBoundary inc playfieldRightBoundary inx ; check next right position cpx #XMAX+1 ; found limit if horizontally out of range beq .foundPFRightBoundary lda PFGraphicsPointerTable,x ; read the LSB for the playfield graphics sta playfieldRAMPointer1 lda (playfieldRAMPointer1),y ; get the current playfield value and PlayfieldPatternTable,x ; and with playfield pattern beq .computePFRightBoundary ; if empty then limit hasn't been found .foundPFRightBoundary lda playfieldRightBoundary ; get right boundary cmp playfieldMinBoundary ; compare with minimum value bcc .setComputerJoystickValue ldx playfieldMinBoundary ; if greater use a random value to cpx minBoundaryLookup ; determine motion bcc .setComputerMotionToRight bit randomMotionSeed bvs .setComputerJoystickValue cmp minBoundaryLookup bcc .setComputerMotionToRight lda minBoundaryLookup ; not needed as accumulator changes below .setComputerMotionToRight lda #MOVE_RIGHT sta computerMotion .setComputerJoystickValue lda computerMotion ; get computer's motion eor #$FF ; flip the bits to look like SWCHA and #PLAYER0_JOYSTICK_MASK sta computerJoystickValue ; set the computer's joystick value lda joystickValues ; get the joystick values and #PLAYER1_JOYSTICK_MASK ora computerJoystickValue sta joystickValues ; combine computer joystick value jmp DeterminePlayerMovements PerformGameLogic lda gameVariation ; get the current game variation and #SINGLEPLAYER ; mask for the single player option cmp #SINGLEPLAYER ; if this is not a single player game bne .performTwoPlayerGameLogic ; then process for 2 players jmp SinglePlayerGame .performTwoPlayerGameLogic lda gameVariation ; get the current game variation bpl .readJoystickForGraffiti ; do graffiti logic if a graffiti game DeterminePlayerMovements lda joystickValues ; get joystick values (looks like SWCHA) eor #$FF ; flip the bits sta tempJoystickValues ; save the values and #PLAYER0_JOYSTICK_MASK sta workingJoystickValue lda player2Motion ; get player2's motion lsr ; move it to the lower nybbles lsr lsr lsr ora player1Motion ; combine the player motion values sta combinedMotionValues ; save for later ldx #NUM_PLAYERS bit SWCHB ; read the difficulty switches bvs .nextPlayerDifficulty and #PLAYER0_JOYSTICK_MASK cmp #MOVE_UP beq .joystickMoved cmp #MOVE_DOWN beq .joystickMoved cmp #MOVE_LEFT beq .joystickMoved cmp #MOVE_RIGHT beq .joystickMoved .nextPlayerDifficulty dex .checkPlayer2Difficulty bit SWCHB ; read the difficulty switches bmi .nextPlayer lda tempJoystickValues ; get the temporary joystick values and #PLAYER1_JOYSTICK_MASK sta workingJoystickValue lda combinedMotionValues and #PLAYER1_JOYSTICK_MASK cmp #MOVE_UP>>4 beq .joystickMoved cmp #MOVE_DOWN>>4 beq .joystickMoved cmp #MOVE_LEFT>>4 beq .joystickMoved cmp #MOVE_RIGHT>>4 bne .nextPlayer .joystickMoved lda combinedMotionValues ; get the combined motions asl and workingJoystickValue and #%10101010 eor tempJoystickValues sta tempJoystickValues lda combinedMotionValues lsr and workingJoystickValue and #%01010101 eor tempJoystickValues sta tempJoystickValues .nextPlayer lda tempJoystickValues dex bne .checkPlayer2Difficulty eor #$FF ; flip bits to look like SWCHA values jmp CalculateMovements .readJoystickForGraffiti lda SWCHA CalculateMovements ldx #0 sta workingJoystickValue .calculateMovementLoop ldy #0 sty playerMovingHorizontal ; clear horizontal movement flag asl ; player 0 right motion value in carry bcs .checkP0LeftMovement lda #MOVE_RIGHT sta playerMotion,x jmp MovePlayerRight .checkP0LeftMovement asl ; player 0 left motion value in carry bcs .checkP0DownMovement lda #MOVE_LEFT sta playerMotion,x jmp MovePlayerLeft .checkP0DownMovement asl ; player 0 down motion value in carry bcs .checkP0UpMovement lda #MOVE_DOWN sta playerMotion,x jmp MovePlayerDown .checkP0UpMovement asl ; player 0 up motion value in carry bcs JoystickNotMoved lda #MOVE_UP sta playerMotion,x jmp MovePlayerUp JoystickNotMoved lda gameVariation ; get the current game variation asl ; move GRAFFITI mode to carry bcs .processOldDirections ; move based on directions if not GRAFFITI jmp .checkNextPlayersDirections .processOldDirections lda playerMotion,x ; get the player's direction asl ; right motion value in carry bcc .checkLeftMovement inc playerMovingHorizontal ; show player moving horizontally jmp MovePlayerRight .checkLeftMovement asl ; left motion value in carry bcc .checkDownMovement inc playerMovingHorizontal ; show player moving horizontally jmp MovePlayerLeft .checkDownMovement asl ; down motion value in carry bcc .checkUpMovement jmp MovePlayerDown .checkUpMovement asl ; up motion value in carry bcc .nextPlayerDirections jmp MovePlayerUp .nextPlayerDirections jmp .checkNextPlayersDirections MovePlayerDown lda playerVertPos,x ; get the player's vertical position clc adc #1 ; increment vertical position by 1 sbc #YMAX-1 bmi .wrapPlayerToTop lda #YMIN sta playerVertPos,x beq .checkNextPlayersDirections ; unconditional branch .wrapPlayerToTop adc #YMAX sta playerVertPos,x jmp .checkNextPlayersDirections MovePlayerUp lda playerVertPos,x ; get the player's vertical position sec sbc #1 ; reduce vertical position by 1 bpl .setPlayerVerticalPosition lda #YMAX-1 .setPlayerVerticalPosition sta playerVertPos,x jmp .checkNextPlayersDirections CheckToMoveDiagonally lda gameVariation ; get the current game variation ror ; diagonal flag no in carry bcc .checkNextPlayersDirections lda #1 cmp playerMovingHorizontal bne .playerNotMovingHoriz ldy #0 lda playerMotion,x ; get the player's direction asl ; shift left twice so down bit is in D7 asl jmp .checkDownMovement ; this will shift down bit into carry .playerNotMovingHoriz lda workingJoystickValue cpx #1 ; see if this is player 1 or 2 bcc .shiftDownBitToCarry asl ; we are processing player 2 so move asl ; player2's joystick values to upper asl ; nybbles asl .shiftDownBitToCarry asl asl asl bcs .checkDiagonalUpMovement lda #MOVE_DOWN ora playerMotion,x and #~MOVE_UP sta playerMotion,x jmp MovePlayerDown .checkDiagonalUpMovement asl ; shift up bit into carry bcs .checkNextPlayersDirections lda #MOVE_UP ora playerMotion,x and #~MOVE_DOWN sta playerMotion,x jmp MovePlayerUp .checkNextPlayersDirections inx cpx #NUM_PLAYERS bcs .delayPlayerMovement lda workingJoystickValue asl ; shift player2's joystick values to asl ; upper nybbles asl asl jmp .calculateMovementLoop MovePlayerRight lda #HMOVE_R4 sta HMP0,x ; move player 4 pixels to the right inc playerHorizPos,x ; increment player horizontal position lda #XMAX cmp playerHorizPos,x bne .diagonalMovementCheck lda #XMIN sta playerHorizPos,x jmp CheckToMoveDiagonally MovePlayerLeft lda #HMOVE_L4 sta HMP0,x ; move player 4 pixels to the left dec playerHorizPos,x ; reduce player horizontal position bpl .diagonalMovementCheck lda #XMAX-1 sta playerHorizPos,x .diagonalMovementCheck jmp CheckToMoveDiagonally .delayPlayerMovement lda #$F0 ora gameSpeed ; delay player movement for 15 frames sta gameSpeed rts Start sei cld ; clear decimal mode lda #SYSTEM_POWERUP sta gameState ; show system is in power up state jmp InitRound MainLoop jsr VerticalSync lda gameState ; get the current game state bmi ReadJoysticks ; read joysticks if game in progress jmp ReadGameSelectAndReset ReadJoysticks lda SWCHA ; get player joystick values and #PLAYER0_JOYSTICK_MASK ; mask player2's joystick values cmp #PLAYER0_NO_MOVE beq .checkPlayer2Joystick sta playerJoystickValues ; save joystick value temporarily lda joystickValues ; get current joystick values and #PLAYER1_JOYSTICK_MASK ; mask to isolate player 2's values ora playerJoystickValues ; combine player1's joystick value sta joystickValues .checkPlayer2Joystick: lda SWCHA ; get player joystick values and #PLAYER1_JOYSTICK_MASK ; mask to isolate player 2's values cmp #PLAYER1_NO_MOVE beq .skipPlayer2Joystick ; skip joystick logic if not moved sta playerJoystickValues ; save joystick value temporarily lda joystickValues ; get current joystick values and #PLAYER0_JOYSTICK_MASK ; mask to isolate player 1's values ora playerJoystickValues ; combine player2's joystick value sta joystickValues .skipPlayer2Joystick lda gameVariation ; get the current game variation and #NOGRAFFITI2 beq CheckForSpeedUp jsr CheckCollisions CheckForSpeedUp bit gameVariation ; check game variation for speed up bvc .skipSpeedIncrease ; skip speed increase if not SPEEDUP lda frameCount ; get the current frame count bne .skipSpeedIncrease ; skip speed increase if no roll over lda gameSpeed and #$0F cmp #4 bcc .skipSpeedIncrease dec gameSpeed dec gameSpeed dec gameSpeed .skipSpeedIncrease lda gameSpeed ; get the game speed and #$F0 ; keep the frame delay value cmp #32 bne .checkGameDelay bit playerState ; if either player was hit this round bmi .checkGameDelay ; then don't turn off the sound bvs .checkGameDelay lda #0 sta AUDV0 ; turn off sound channel 0 .checkGameDelay lda gameSpeed ; get the game speed and #$F0 ; keep the frame delay value cmp #0 ; skip player updates if not 0 bne ReadGameSelectAndReset jsr UpdatePlayfield jsr PerformGameLogic sta WSYNC ; wait for next scan line sta HMOVE ; move all objects horizontally lda gameSpeed and #$0F ; mask frame delay nybbles sta gameSpeed ; move lower nybbles to frame delay asl ; nybbles asl asl asl ora gameSpeed sta gameSpeed lda #8 bit gameVariation bmi .setVolume lda #0 .setVolume sta AUDV0 lda #$0C sta AUDC0 lda gameSpeed asl sta AUDF0 ReadGameSelectAndReset jsr CheckForGameSelectOrReset EnterDisplayKernel jsr DisplayKernel lda gameSpeed sec sbc #FRAME_DELAY ; reduce the frame delay value sta gameSpeed jmp MainLoop UpdatePlayfield SUBROUTINE ldx #0 stx playfieldRAMPointer0+1 .playfieldLoop ldy playerHorizPos,x ; get the player's horizontal position lda PFGraphicsPointerTable,y ; read the LSB for the playfield graphics sta playfieldRAMPointer0 lda PlayfieldPatternTable,y sta tempPlayfieldPattern ldy playerVertPos,x ; get the player's vertical position lda gameVariation ; get the current game variation lsr ; shift right 3 times so ERASE option lsr ; is in carry lsr bcc .skipErase lda INPT4,x ; read the joystick button bmi .skipErase ; skip erase if not pressed lda tempPlayfieldPattern eor #$FF ; flip the bits and (playfieldRAMPointer0),y sta (playfieldRAMPointer0),y jmp .nextPlayer .skipErase lda tempPlayfieldPattern ora (playfieldRAMPointer0),y sta (playfieldRAMPointer0),y .nextPlayer inx cpx #NUM_PLAYERS bcc .playfieldLoop rts InitRound ldx #$FF lda #0 sta PF0 ; clear playfield graphic registers sta PF1 sta PF2 txs ; set stack to the beginning jsr DisplayKernel jsr VerticalSync lda #0 ldx #NUM_ROWS*4 .clearPlayfieldLoop sta leftPF1Graphics-1,x dex bne .clearPlayfieldLoop lda gameVariation ; get the current game variation and #WRAPAROUND bne InitializeWrapAroundPF ldx #NUM_ROWS lda #%00000001 ; remember PF0 is displayed as D4 - D7 .setLeftPFColumnLoop ; and lower nybbles hold left PF0 values sta pf0Graphics-1,x ; set PF0 values to have column on left dex bne .setLeftPFColumnLoop ldx #NUM_ROWS lda #%10000000 ; remember PF2 is displayed as D0 - D7 .setRightPFColumnLoop sta rightPF2Graphics-1,x ; set PF2 values to have column on right dex bne .setRightPFColumnLoop lda #%11111111 sta pf0Graphics sta pf0Graphics+NUM_ROWS-1 sta leftPF1Graphics sta leftPF1Graphics+NUM_ROWS-1 sta leftPF2Graphics sta leftPF2Graphics+NUM_ROWS-1 sta rightPF1Graphics sta rightPF1Graphics+NUM_ROWS-1 sta rightPF2Graphics sta rightPF2Graphics+NUM_ROWS-1 jmp ClearGameTIARegisters InitializeWrapAroundPF lda #0 ldx #NUM_ROWS .clearPFBordersLoop sta pf0Graphics-1,x ; clear the left and right playfield sta rightPF2Graphics-1,x ; variables to have no left/right borders dex bne .clearPFBordersLoop ClearGameTIARegisters lda #0 ldx #CXCLR-RSYNC+1 .clearTIALoop sta NUSIZ0+63,x dex bne .clearTIALoop lda #PLAYER_START_Y sta player1VertPos sta player2VertPos sta player1HorizPos lda #PLAYER2_START_X sta player2HorizPos sta playerState sta WSYNC ; wait for next scan line ldy #5 .positionPlayer0Loop dey bpl .positionPlayer0Loop sta RESP0 sta WSYNC ; wait for next scan line ldy #10 .positionPlayer1Loop dey bpl .positionPlayer1Loop sta RESP1 lda #HMOVE_L3 sta HMP0 lda #HMOVE_R2 sta HMP1 sta WSYNC ; wait for next scan line sta HMOVE ; move all objects horizontally lda #NO_MOVE sta joystickValues sta gameSpeed ; set initial game speed sta AUDF0 lda gameSelection ; get current game selection and #7 asl ; multiply by 4 to get color table offset asl tay ldx #0 lda SWCHB ; read the console switches and #BW_MASK ; mask to get B/W switch value bne .setColorsLoop ldy #32 .setColorsLoop lda GameColorTable,y sta COLUP0,x iny inx cpx #4 bcc .setColorsLoop lda #0 sta AUDV0 ; turn off game sounds sta AUDV1 sta frameCount ; reset frame count lda gameVariation ; get the current game variation and #SINGLEPLAYER bne .setPlayer2InitMotion lda #MOVE_RIGHT sta player1Motion ; start player 1 moving right .setPlayer2InitMotion lda #MOVE_LEFT sta player2Motion ; start player 2 moving left lda #$0C sta AUDC0 jmp EnterDisplayKernel CheckForGameSelectOrReset SUBROUTINE lda gameState ; get the current game state cmp #SYSTEM_POWERUP ; read console switches if not powering up bne .checkForResetSwitch lda #-1 sta gameSelection bne ShowGameSelection .checkForResetSwitch lda SWCHB ; read the console switches ror ; RESET now in carry bcs .skipGameReset lda #GAME_RUNNING ; RESET pressed so show that game is sta gameState ; in progress lda #0 sta player1Score ; clear the player scores sta player2Score sta selectDebounce sta colorCycleTimer lda frameCount ; get the current frame count and #1 ; make the frame count between 0 and 1 sta frameCount lda #15 sta unused jmp InitRound .skipGameReset lda frameCount ; get current frame count and #SELECT_DELAY ; the select switch is checked ~ every 60 bne .checkGameSelectSwitch ; frames or ~ every second sta selectDebounce ; reset select debounce flag .checkGameSelectSwitch lda frameCount and #$FF bne .checkForSelectSwitch inc colorCycleTimer bne .checkForSelectSwitch lda #0 ; reset gameState so right score not shown sta gameState ; and color's could cycle for attract mode .checkForSelectSwitch lda SWCHB ; read the console switches and #SELECT_MASK ; mask to find SELECT value beq .selectSwitchPressed sta selectDebounce ; show SELECT not pressed this frame bne DetermineGameColors ; unconditional branch .selectSwitchPressed bit selectDebounce ; if SELECT held then skip SELECT button bmi DetermineGameColors ; logic lda #$FF sta selectDebounce ; show the SELECT button is held inc gameSelection ; increment game selection ShowGameSelection ldx #0 stx player1Score ; clear player1's score ldx gameSelection ; get current game selection .setGameSelectionDisplay sed lda player1Score ; the player's score is incremented by 1 clc ; to show the current game selection adc #1 sta player1Score cld dex bne .setGameSelectionDisplay ldx #0 stx player2Score ; clear player2's score stx unused stx gameState stx frameCount ; clear frame count lda gameSelection ; get current game selection cmp #MAX_GAME_SELECTION bcc .skipGameSelectionReset stx player1Score ; clear player's score stx gameSelection ; clear game selection .skipGameSelectionReset sed clc lda player1Score adc #1 sta player1Score cld ldx gameSelection ; get current game selection lda GameVariationTable,x ; set the game variation based on game sta gameVariation ; selection DetermineGameColors lda gameSelection ; get current game selection and #7 asl ; multiply by 4 to get color table offset asl tay ldx #0 lda gameState ; get the current game state and #$F0 ; keep upper nybbles to see if game active eor #$FF ; flip the bits and colorCycleTimer sta colorXOR lda #$FF sta hueMask ; assume color (i.e. keep color hues) lda SWCHB ; read the console switches and #BW_MASK ; mask to get B/W switch value bne .setColorsLoop lda #$0F sta hueMask ; mask hues for B/W option ldy #32 ; index for B/W values .setColorsLoop lda GameColorTable,y eor colorXOR and hueMask ; mask color hue bit gameState ; don't cycle colors if game in progress bvs .skipColorSet sta COLUP0,x lda #0 sta AUDV0 ; turn off channel 0 sound .skipColorSet iny inx cpx #4 bcc .setColorsLoop lda #0 sta PF0 ; clear playfield registers sta PF1 sta PF2 rts CheckCollisions lda CXP0FB ; read playfield collisions for player 0 bpl .checkPlayer1Collisions ; check player 1 if not collided with PF bit playerState bvs PlayCollisionSound lda #~PLAYER2_COLL_MASK sta playerState sed lda player2Score ; get player2's score clc ; player1 hit the playfield so adc #1 ; increment player2's score cld sta player2Score cmp #MAX_SCORE bne .checkPlayer1Collisions lda #$0F sta gameState .checkPlayer1Collisions lda CXP1FB ; read playfield collisions for player 1 bpl .donePlayerCollisionCheck bit playerState bmi PlayCollisionSound lda playerState ora #PLAYER2_COLL_MASK sta playerState sed lda player1Score ; get player1's score clc ; player2 hit the playfield so adc #1 ; increment player1's score cld sta player1Score cmp #MAX_SCORE bne .donePlayerCollisionCheck lda #$0F sta gameState .donePlayerCollisionCheck lda playerState and #PLAYER_COLL_MASK beq .leaveSubroutine jmp .incrementFrameDelay .leaveSubroutine rts PlayCollisionSound lda frameCount ; set collision volume and frequency and #3 ; every fourth frame bne .setCollisionVolume lda #1 sta AUDC0 ; set collision audio channel lda #6 sta AUDF0 ; set initial collision volume bne .checkToReduceCollisionVolume .setCollisionVolume lda playerState sta AUDV0 ; set game volume (only lower nybble used) lda #$0E sta AUDF0 .checkToReduceCollisionVolume lda frameCount and #$0F ; decrement collision volume every 15 bne .incrementFrameDelay ; frames dec playerState ; reduce collision volume lda playerState and #$0F ; check the collision volume values bne CheckToFlashPlayer ; if not done check for flashing player jmp InitRound CheckToFlashPlayer lda gameSelection ; get current game selection and #7 asl ; multiply by 4 to get color table offset asl tay lda SWCHB ; read the console switches and #BW_MASK ; mask to get B/W switch value bne .checkToFlashPlayer2 ldy #32 ; index for B/W values .checkToFlashPlayer2 bit playerState ; see if player2 hit player1 bpl .checkToFlashPlayer1 ; if not check to whether to flash player1 lda playerState ; get the player state and #%00100000 ; see if player2 was shown this frame bne .clearPlayer2 lda playerState ora #%00100000 ; set to show player2 shown this frame sta playerState iny ; increment y to point to player2 color lda GameColorTable,y ; read color table dey ; restore y to point to player1 color sta COLUP1 ; color player2 jmp .checkToFlashPlayer1 .clearPlayer2 lda playerState eor #%00100000 ; flip D5 to show player2 masked this frame sta playerState iny ; increment y to point to background color iny lda GameColorTable,y ; read color table dey ; restore y to point to player1 color dey sta COLUP1 ; color player2 .checkToFlashPlayer1 bit playerState ; see if player1 hit player2 bvc .incrementFrameDelay lda playerState and #%00010000 ; see if player1 was shown this frame bne .clearPlayer1 lda playerState ora #%00010000 sta playerState ; set to show player1 shown this frame lda GameColorTable,y ; read color table sta COLUP0 ; color player1 jmp .incrementFrameDelay .clearPlayer1 lda playerState eor #%00010000 ; flip D4 to show player1 masked this frame sta playerState iny ; increment y to point to background color iny lda GameColorTable,y ; read color table sta COLUP0 ; color player1 .incrementFrameDelay lda gameSpeed clc adc #FRAME_DELAY sta gameSpeed rts PFGraphicsPointerTable .byte pf0Graphics,pf0Graphics,pf0Graphics,pf0Graphics .byte leftPF1Graphics,leftPF1Graphics,leftPF1Graphics,leftPF1Graphics .byte leftPF1Graphics,leftPF1Graphics,leftPF1Graphics,leftPF1Graphics .byte leftPF2Graphics,leftPF2Graphics,leftPF2Graphics,leftPF2Graphics .byte leftPF2Graphics,leftPF2Graphics,leftPF2Graphics,leftPF2Graphics .byte pf0Graphics,pf0Graphics,pf0Graphics,pf0Graphics .byte rightPF1Graphics,rightPF1Graphics,rightPF1Graphics,rightPF1Graphics .byte rightPF1Graphics,rightPF1Graphics,rightPF1Graphics,rightPF1Graphics .byte rightPF2Graphics,rightPF2Graphics,rightPF2Graphics,rightPF2Graphics .byte rightPF2Graphics,rightPF2Graphics,rightPF2Graphics,rightPF2Graphics PlayfieldPatternTable .byte $01,$02,$04,$08 .byte $80,$40,$20,$10,$08,$04,$02,$01 .byte $01,$02,$04,$08,$10,$20,$40,$80 .byte $10,$20,$40,$80 .byte $80,$40,$20,$10,$08,$04,$02,$01 .byte $01,$02,$04,$08,$10,$20,$40,$80 NumberFonts zero .byte $0E ; |....XXX.| .byte $0A ; |....X.X.| .byte $0A ; |....X.X.| .byte $0A ; |....X.X.| .byte $0E ; |....XXX.| one .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| two .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| .byte $88 ; |X...X...| .byte $EE ; |XXX.XXX.| three .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $66 ; |.XX..XX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| four .byte $AA ; |X.X.X.X.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| five .byte $EE ; |XXX.XXX.| .byte $88 ; |X...X...| .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| six .byte $EE ; |XXX.XXX.| .byte $88 ; |X...X...| .byte $EE ; |XXX XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| seven .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| eight .byte $EE ; |XXX.XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| nine .byte $EE ; |XXX.XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| GameColorTable ; ; organized as... ; P0/P1/PF/BK ; IF COMPILE_VERSION = NTSC .byte $C8,$84,$3A,$44 .byte $46,$EA,$5A,$88 .byte $2A,$46,$B4,$E8 .byte $3A,$86,$E8,$46 .byte $2A,$56,$E8,$A4 .byte $36,$2C,$C8,$A2 .byte $C8,$2A,$83,$46 .byte $86,$2C,$48,$E8 ELSE .byte $CA,$84,$3A,$2E .byte $84,$2E,$CE,$E8 .byte $CE,$3A,$2E,$82 .byte $CE,$2E,$82,$3A .byte $82,$3A,$2E,$CE .byte $86,$2E,$3F,$32 .byte $2E,$3A,$CF,$C2 .byte $2E,$86,$EF,$3A ENDIF .byte $00,$0E,$04,$08 DigitOfsTab .byte H_DIGIT*0, H_DIGIT*1, H_DIGIT*2, H_DIGIT*3, H_DIGIT*4 .byte H_DIGIT*5, H_DIGIT*6, H_DIGIT*7, H_DIGIT*8, H_DIGIT*9 ; used to duplicate bottom row of score: DigitOfs2Tab: .byte 4, 4, 3, 2, 1, 0 GameVariationTable .byte NOGRAFFITI| NOGRAFFITI2 .byte NOGRAFFITI| NOGRAFFITI2|SINGLEPLAYER .byte NOGRAFFITI|SPEEDUP|NOGRAFFITI2 .byte NOGRAFFITI|SPEEDUP|NOGRAFFITI2|SINGLEPLAYER .byte NOGRAFFITI| NOGRAFFITI2| DIAGONAL .byte NOGRAFFITI|SPEEDUP|NOGRAFFITI2| DIAGONAL .byte NOGRAFFITI|SPEEDUP|NOGRAFFITI2| ERASE|DIAGONAL .byte NOGRAFFITI| NOGRAFFITI2| WRAPAROUND .byte NOGRAFFITI|SPEEDUP|NOGRAFFITI2| WRAPAROUND .byte NOGRAFFITI| NOGRAFFITI2| WRAPAROUND| DIAGONAL .byte NOGRAFFITI|SPEEDUP|NOGRAFFITI2| WRAPAROUND| DIAGONAL .byte NOGRAFFITI|SPEEDUP|NOGRAFFITI2| WRAPAROUND|ERASE|DIAGONAL .byte WRAPAROUND|ERASE .byte WRAPAROUND|ERASE|DIAGONAL .org ROM_BASE_ADDRESS + 2048 - 6, 234 * COMPILE_VERSION ; 2K ROM .word Start ; NMI vector .word Start ; RESET vector .word Start ; BRK vector