PROCESSOR 6502 INCLUDE "vcs.h" ;; Colors Constants WHITE = $0F BLUE = $9A ORANGE = $4A ;; ON/OFF Constants ON_BIT = %00000010 OFF_BIT = %0000000 ;; Hardware Constants INITIAL_SWABCNT = 0 INITIAL_CTRLPF = %00010000 INITIAL_NUSIZ01 = %00010000 ;; Input Constants J0_UP = %00010000 J0_DOWN = %00100000 J1_UP = %00000001 J1_DOWN = %00000010 ;; Movable Object Constants PLAYER0_OFFSET = 0 PLAYER1_OFFSET = 1 MISSILE0_OFFSET = 2 MISSILE1_OFFSET = 3 BALL_OFFSET = 4 ;; Common Constants COMMON_POS_Y = 100 ;; Paddle Constants MIN_PADDLE_POS_Y = 4 MAX_PADDLE_POS_Y = 180 PADDLE1_POS_X = 16 PADDLE2_POS_X = 144 PADDLE_FIRST_HIT_HEIGHT_OFFSET = 4 PADDLE_FIRST_HIT_DIR_X = 1 PADDLE_FIRST_HIT_DIR_Y = -2 PADDLE_SECOND_HIT_HEIGHT_OFFSET = 9 PADDLE_SECOND_HIT_DIR_X = 2 PADDLE_SECOND_HIT_DIR_Y = -2 PADDLE_THIRD_HIT_HEIGHT_OFFSET = 14 PADDLE_THIRD_HIT_DIR_X = 2 PADDLE_THIRD_HIT_DIR_Y = -1 PADDLE_FOURTH_HIT_HEIGHT_OFFSET = 19 PADDLE_FOURTH_HIT_DIR_X = 2 PADDLE_FOURTH_HIT_DIR_Y = 0 PADDLE_FIFTH_HIT_HEIGHT_OFFSET = 24 PADDLE_FIFTH_HIT_DIR_X = 2 PADDLE_FIFTH_HIT_DIR_Y = 1 PADDLE_SIXTH_HIT_HEIGHT_OFFSET = 29 PADDLE_SIXTH_HIT_DIR_X = 2 PADDLE_SIXTH_HIT_DIR_Y = 2 PADDLE_SEVENTH_HIT_DIR_X = 1 PADDLE_SEVENTH_HIT_DIR_Y = 2 ;; Ball Constants BALL_INITIAL_POS_X = 80 BALL_INITIAL_BALL_SPEED = 32 BALL_DIRECTION_COUNT = 3 ;; Score Constants MAX_SCORE = 9 SCORE1_POS_X = 50 SCORE2_POS_X = 100 ;; Time Constants VBLANK_TIME = 57 ; Formula = 48 scanlines * 76 clock cycles / 64 color clocks OVERSCAN_TIME = 42 ; Formula = 35 scanlines * 76 clock cycles / 64 color clocks ;; Scanline Constants VISIBLE_SCANLINES = 220 SCORE_SCANLINES = 8 PADDLES_HEIGHT_SCANLINE_DIFF = 224 ; Formula = 256 - 32 pixels height BALL_HEIGHT_SCANLINE_DIFF = 252 ; Formula = 256 - 4 pixels height ;; Sound Effect Constants WALL_COLLISION_FREQUENCY = $17 WALL_COLLISION_CONTROL = $0C WALL_COLLISION_VOLUME = $0C WALL_COLLISION_DURATION = 10 BALL_OUT_FREQUENCY = $02 BALL_OUT_CONTROL = $06 BALL_OUT_VOLUME = $0C BALL_OUT_DURATION = 15 PADDLE_HIT_FREQUENCY = $1F PADDLE_HIT_CONTROL = $0C PADDLE_HIT_VOLUME = $0C PADDLE_HIT_DURATION = 5 ;; RAM Constants PADDLE1_POS_Y = $0080 PADDLE2_POS_Y = $0081 BALL_POS_X = $0082 BALL_POS_Y = $0083 BALL_DIR_X = $0084 BALL_DIR_Y = $0085 BALL_SPEED = $0086 SPEED_COUNTER = $0087 SCORE1 = $0089 SCORE2 = $008A TEMP1 = $008B TEMP2 = $008C FRAME_COUNTER = $008D SOUND_DURATION = $008E ORG $F800 Start: SEI CLD LDX #$FF TXS LDA #$00 ClearRAM: STA 0,X DEX BNE ClearRAM InitRAM: LDA #COMMON_POS_Y STA PADDLE1_POS_Y STA PADDLE2_POS_Y STA BALL_POS_Y LDA #BALL_INITIAL_POS_X STA BALL_POS_X LDA #BALL_INITIAL_BALL_SPEED STA BALL_SPEED InitRegisters: LDA #INITIAL_SWABCNT STA SWACNT STA SWBCNT LDA #INITIAL_CTRLPF STA CTRLPF LDA #INITIAL_NUSIZ01 STA NUSIZ0 STA NUSIZ1 LDA #BLUE STA COLUBK LDA #WHITE STA COLUPF STA COLUP0 LDA #ORANGE STA COLUP1 STA HMCLR STA CXCLR EnableVSync: STA WSYNC LDA #ON_BIT STA VSYNC SkipVSyncScanlines STA WSYNC STA WSYNC STA WSYNC InitVBlankTimer: LDA #VBLANK_TIME STA TIM64T DisableVSync: LDA #OFF_BIT STA VSYNC UpdateMovableObjectPositions: LDA #SCORE1_POS_X LDX #PLAYER0_OFFSET JSR UpdatePositionX LDA #SCORE2_POS_X LDX #PLAYER1_OFFSET JSR UpdatePositionX LDA #PADDLE1_POS_X LDX #MISSILE0_OFFSET JSR UpdatePositionX LDA #PADDLE2_POS_X LDX #MISSILE1_OFFSET JSR UpdatePositionX LDA BALL_POS_X LDX #BALL_OFFSET JSR UpdatePositionX STA WSYNC STA HMOVE WaitVBlankTimer: LDA INTIM BNE WaitVBlankTimer STA WSYNC STA WSYNC LDA #OFF_BIT STA VBLANK LoadScores: LDA SCORE1 ASL ASL ASL STA TEMP1 LDA SCORE2 ASL ASL ASL STA TEMP2 LDY #SCORE_SCANLINES DrawScores: STA WSYNC LDX TEMP1 LDA NUMBERS_BITMAP,X STA GRP0 LDX TEMP2 LDA NUMBERS_BITMAP,X STA GRP1 INC TEMP1 INC TEMP2 DEY BNE DrawScores StartVisibleScanlines: LDA PADDLE1_POS_Y STA TEMP1 LDA PADDLE2_POS_Y STA TEMP2 LDX #VISIBLE_SCANLINES LDY BALL_POS_Y DecreaseVisibleScanlines: STA WSYNC DEC TEMP1 DEC TEMP2 DEY DrawPaddle1: LDA TEMP1 CMP #PADDLES_HEIGHT_SCANLINE_DIFF PHP PLA ASL STA ENAM0 DrawPaddle2: LDA TEMP2 CMP #PADDLES_HEIGHT_SCANLINE_DIFF PHP PLA ASL STA ENAM1 DrawBall: CPY #BALL_HEIGHT_SCANLINE_DIFF PHP PLA ASL STA ENABL CheckEndVisibleScanlines: DEX BNE DecreaseVisibleScanlines EnableVBlank: LDA #ON_BIT STA VBLANK InitOverscanTimer LDA #OVERSCAN_TIME STA TIM64T CheckJ0Up LDA SWCHA AND #J0_UP BNE CheckJ0Down LDA PADDLE1_POS_Y CMP #MIN_PADDLE_POS_Y BEQ CheckJ0Down DEC PADDLE1_POS_Y DEC PADDLE1_POS_Y CheckJ0Down: LDA SWCHA AND #J0_DOWN BNE CheckJ1Up LDA PADDLE1_POS_Y CMP #MAX_PADDLE_POS_Y BEQ CheckJ1Up INC PADDLE1_POS_Y INC PADDLE1_POS_Y CheckJ1Up: LDA SWCHA AND #J1_UP BNE CheckJ1Down LDA PADDLE2_POS_Y CMP #MIN_PADDLE_POS_Y BEQ CheckJ1Down DEC PADDLE2_POS_Y DEC PADDLE2_POS_Y CheckJ1Down: LDA SWCHA AND #J1_DOWN BNE CheckJ0Button LDA PADDLE2_POS_Y CMP #MAX_PADDLE_POS_Y BEQ CheckJ0Button INC PADDLE2_POS_Y INC PADDLE2_POS_Y CheckJ0Button: LDA INPT4 BPL CheckExistingBallDirection CheckJ1Button: LDA INPT5 BMI MoveBall CheckExistingBallDirection: LDA BALL_DIR_X BNE MoveBall SetBallDirectionX: LDA FRAME_COUNTER AND #BALL_DIRECTION_COUNT TAX LDA BALL_DIRECTIONS_TABLE,X STA BALL_DIR_X SetBallDirectionY: LDA FRAME_COUNTER LSR LSR AND #BALL_DIRECTION_COUNT TAX LDA BALL_DIRECTIONS_TABLE,X STA BALL_DIR_Y MoveBall: LDA SPEED_COUNTER CLC ADC BALL_SPEED STA SPEED_COUNTER CheckBallSpeed: LDA SPEED_COUNTER SEC SBC #$40 BCS UpdateBallSpeed JMP DecreaseSoundCounter UpdateBallSpeed: STA SPEED_COUNTER UpdateBallDirectionX: LDA BALL_POS_X CLC ADC BALL_DIR_X STA TEMP1 UpdateBallDirectionY: LDA BALL_POS_Y CLC ADC BALL_DIR_Y STA TEMP2 CheckPaddle1Collision: LDA TEMP1 CMP #PADDLE1_POS_X-1 BCC CheckPaddle2Collision CMP #PADDLE1_POS_X+1 BCS CheckPaddle2Collision LDA PADDLE1_POS_Y SEC SBC #4 CMP TEMP2 BCS CheckPaddle2Collision CLC ADC #35 CMP TEMP2 BCC CheckPaddle2Collision LDA TEMP2 SEC SBC PADDLE1_POS_Y JSR HitPaddle LDA #WHITE STA COLUPF JMP CheckBallSpeed CheckPaddle2Collision: LDA TEMP1 CMP #PADDLE2_POS_X-1 BCC CheckWallCollisionX CMP #PADDLE2_POS_X+4 BCS CheckWallCollisionX LDA PADDLE2_POS_Y SBC #4 CMP TEMP2 BCS CheckWallCollisionX CLC ADC #35 CMP TEMP2 BCC CheckWallCollisionX LDA TEMP2 SBC PADDLE2_POS_Y JSR HitPaddle LDA #0 SEC SBC BALL_DIR_X STA BALL_DIR_X LDA #ORANGE STA COLUPF JMP CheckBallSpeed CheckWallCollisionX: LDA TEMP2 CMP #2 BCC CheckWallCollisionY CMP #181 BCC CheckBallPosition CheckWallCollisionY: LDA #0 SEC SBC BALL_DIR_Y STA BALL_DIR_Y PlayWallCollisionSoundEffect: LDA #WALL_COLLISION_FREQUENCY STA AUDF0 LDA #WALL_COLLISION_CONTROL STA AUDC0 LDA #WALL_COLLISION_VOLUME STA AUDV0 LDA #WALL_COLLISION_DURATION STA SOUND_DURATION JMP CheckBallSpeed CheckBallPosition: LDA TEMP1 CMP #2 BCC CheckScore2 CMP #157 BCC UpdateBallPositionX CheckScore1: LDA SCORE1 CMP #MAX_SCORE BEQ RestartGame INC SCORE1 JMP RestartBallPosition CheckScore2: LDA SCORE2 CMP #MAX_SCORE BEQ RestartGame INC SCORE2 RestartBallPosition: LDA #BALL_INITIAL_POS_X STA TEMP1 LDA #COMMON_POS_Y STA TEMP2 LDA #OFF_BIT STA BALL_DIR_X STA BALL_DIR_Y LDA #BALL_INITIAL_BALL_SPEED STA BALL_SPEED PlayBallOutSoundEffect: LDA #BALL_OUT_FREQUENCY STA AUDF0 LDA #BALL_OUT_CONTROL STA AUDC0 LDA #BALL_OUT_VOLUME STA AUDV0 LDA #BALL_OUT_DURATION STA SOUND_DURATION UpdateBallPositionX: LDA TEMP1 STA BALL_POS_X UpdateBallPositionY: LDA TEMP2 STA BALL_POS_Y DecreaseSoundCounter: DEC SOUND_DURATION BNE DisableBall LDA #OFF_BIT STA AUDV0 DisableBall: LDA #OFF_BIT STA ENABL WaitOverscanTimer: LDA INTIM BNE WaitOverscanTimer STA WSYNC INC FRAME_COUNTER JMP EnableVSync RestartGame: JMP Start UpdatePositionX: STA WSYNC SEC LDY $80 .DivLoop: SBC #15 BCS .DivLoop .AdjustFinePosition: TAY LDA FINE_ADJUST_TABLE-$F1,Y STA HMP0,X STA RESP0,X RTS DetectPageCrossing: IF (>.DivLoop) != (>.AdjustFinePosition) ECHO "ERROR: PAGE CROSSING" ERR ENDIF ORG $FEF1 HitPaddle: PHA .PlayPaddleHitSoundEffect LDA #PADDLE_HIT_FREQUENCY STA AUDF0 LDA #PADDLE_HIT_CONTROL STA AUDC0 LDA #PADDLE_HIT_VOLUME STA AUDV0 LDA #PADDLE_HIT_DURATION STA SOUND_DURATION .IncreaseBallBALL_SPEED INC BALL_SPEED INC BALL_SPEED PLA .CheckFirstPaddleCollisionRange CMP #PADDLE_FIRST_HIT_HEIGHT_OFFSET BPL .CheckSecondPaddleCollisionRange LDA #PADDLE_FIRST_HIT_DIR_X STA BALL_DIR_X LDA #PADDLE_FIRST_HIT_DIR_Y STA BALL_DIR_Y RTS .CheckSecondPaddleCollisionRange: CMP #PADDLE_SECOND_HIT_HEIGHT_OFFSET BCS .CheckThirdPaddleCollisionRange LDA #PADDLE_SECOND_HIT_DIR_X STA BALL_DIR_X LDA #PADDLE_SECOND_HIT_DIR_Y STA BALL_DIR_Y RTS .CheckThirdPaddleCollisionRange: CMP #PADDLE_THIRD_HIT_HEIGHT_OFFSET BCS .CheckFourthPaddleCollisionRange LDA #PADDLE_THIRD_HIT_DIR_X STA BALL_DIR_X LDA #PADDLE_THIRD_HIT_DIR_Y STA BALL_DIR_Y RTS .CheckFourthPaddleCollisionRange: CMP #PADDLE_FOURTH_HIT_HEIGHT_OFFSET BCS .CheckFifthPaddleCollisionRange LDA #PADDLE_FOURTH_HIT_DIR_X STA BALL_DIR_X LDA #PADDLE_FOURTH_HIT_DIR_Y STA BALL_DIR_Y RTS .CheckFifthPaddleCollisionRange: CMP #PADDLE_FIFTH_HIT_HEIGHT_OFFSET BCS .CheckSixthPaddleCollisionRange LDA #PADDLE_FIFTH_HIT_DIR_X STA BALL_DIR_X LDA #PADDLE_FIFTH_HIT_DIR_Y STA BALL_DIR_Y RTS .CheckSixthPaddleCollisionRange: CMP #PADDLE_SIXTH_HIT_HEIGHT_OFFSET BCS .CheckSeventhCollisionRange LDA #PADDLE_SIXTH_HIT_DIR_X STA BALL_DIR_X LDA #PADDLE_SIXTH_HIT_DIR_Y STA BALL_DIR_Y RTS .CheckSeventhCollisionRange: LDA #PADDLE_SEVENTH_HIT_DIR_X STA BALL_DIR_X LDA #PADDLE_SEVENTH_HIT_DIR_Y STA BALL_DIR_Y RTS FINE_ADJUST_TABLE: .BYTE $70 ; 7 px to left .BYTE $60 ; 6 px to left .BYTE $50 ; 5 px to left .BYTE $40 ; 4 px to left .BYTE $30 ; 3 px to left .BYTE $20 ; 2 px to left .BYTE $10 ; 1 px to left .BYTE $00 ; No adjustment .BYTE $F0 ; 1 px to right .BYTE $E0 ; 2 px to right .BYTE $D0 ; 3 px to right .BYTE $C0 ; 4 px to right .BYTE $B0 ; 5 px to right .BYTE $A0 ; 6 px to right .BYTE $90 ; 7 px to right BALL_DIRECTIONS_TABLE: .BYTE $FE ; -2 .BYTE $FF ; -1 .BYTE $01 ; 0 .BYTE $02 ; 1 NUMBERS_BITMAP: .BYTE %11111111 ; 0 .BYTE %11000011 .BYTE %11000011 .BYTE %11000011 .BYTE %11000011 .BYTE %11000011 .BYTE %11111111 .BYTE %00000000 .BYTE %00010000 ; 1 .BYTE %00010000 .BYTE %00010000 .BYTE %00110000 .BYTE %00110000 .BYTE %00110000 .BYTE %00110000 .BYTE %00000000 .BYTE %11111110 ; 2 .BYTE %00000010 .BYTE %00000010 .BYTE %11111110 .BYTE %11000000 .BYTE %11000000 .BYTE %11111110 .BYTE %00000000 .BYTE %11111110 ; 3 .BYTE %00000010 .BYTE %00000010 .BYTE %11111110 .BYTE %00000110 .BYTE %00000110 .BYTE %11111110 .BYTE %00000000 .BYTE %10000010 ; 4 .BYTE %10000010 .BYTE %10000010 .BYTE %11111110 .BYTE %00000110 .BYTE %00000110 .BYTE %00000110 .BYTE %00000000 .BYTE %11111110 ; 5 .BYTE %10000000 .BYTE %10000000 .BYTE %11111110 .BYTE %00000110 .BYTE %00000110 .BYTE %11111110 .BYTE %00000000 .BYTE %11111110 ; 6 .BYTE %10000000 .BYTE %10000000 .BYTE %11111110 .BYTE %11000110 .BYTE %11000110 .BYTE %11111110 .BYTE %00000000 .BYTE %11111110 ; 7 .BYTE %00000010 .BYTE %00000010 .BYTE %00000010 .BYTE %00000110 .BYTE %00000110 .BYTE %00000110 .BYTE %00000000 .BYTE %11111110 ; 8 .BYTE %10000010 .BYTE %10000010 .BYTE %11111110 .BYTE %11000110 .BYTE %11000110 .BYTE %11111110 .BYTE %00000000 .BYTE %11111110 ; 9 .BYTE %10000010 .BYTE %10000010 .BYTE %11111110 .BYTE %00000110 .BYTE %00000110 .BYTE %11111110 .BYTE %00000000 ORG $FFFC .WORD Start .WORD Start