2600-pong/pong.asm

818 lines
11 KiB
NASM

PROCESSOR 6502
INCLUDE "vcs.h"
;; Colors Constants
BLACK = $00
WHITE = $0F
;; 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 $F000
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 #BLACK
STA COLUBK
LDA #WHITE
STA COLUPF
STA COLUP0
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+4
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
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
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 EndGame
INC SCORE1
JMP RestartBallPosition
CheckScore2:
LDA SCORE2
CMP #MAX_SCORE
BEQ EndGame
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
EndGame:
JMP EndGame
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