Skip to content

Instantly share code, notes, and snippets.

@Sorebit
Last active February 22, 2018 22:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sorebit/fc1731c1a8779591d8af4dad0b5a61af to your computer and use it in GitHub Desktop.
Save Sorebit/fc1731c1a8779591d8af4dad0b5a61af to your computer and use it in GitHub Desktop.
NES implementation of pong
.inesprg 1 ; 1x 16KB PRG code
.ineschr 1 ; 1x 8KB CHR data
.inesmap 0 ; mapper 0 = NROM, no bank swapping
.inesmir 1 ; background mirroring
;;;;;;;;;;;;;;;
;; DECLARE SOME VARIABLES HERE
.rsset $0000 ;;start variables at ram location 0
gamestate .rs 1 ; .rs 1 means reserve one byte of space
ballx .rs 1 ; ball horizontal position
bally .rs 1 ; ball vertical position
ballup .rs 1 ; 1 = ball moving up
balldown .rs 1 ; 1 = ball moving down
ballleft .rs 1 ; 1 = ball moving left
ballright .rs 1 ; 1 = ball moving right
ballspeedx .rs 1 ; ball horizontal speed per frame
ballspeedy .rs 1 ; ball vertical speed per frame
paddle1ytop .rs 1 ; player 1 paddle top vertical position
paddle1ybot .rs 1 ; player 2 paddle bottom vertical position
paddle2ytop .rs 1 ; player 1 paddle top vertical position
paddle2ybot .rs 1 ; player 2 paddle bottom vertical position
buttons1 .rs 1 ; player 1 gamepad buttons, one bit per button
buttons2 .rs 1 ; player 2 gamepad buttons, one bit per button
score1 .rs 1 ; player 1 score, 0-15
score2 .rs 1 ; player 2 score, 0-15
gameOverDrawn .rs 1
;; DECLARE SOME CONSTANTS HERE
STATETITLE = $00 ; displaying title screen
STATEPLAYING = $01 ; move paddles/ball, check for collisions
STATEGAMEOVER = $02 ; displaying game over screen
RIGHTWALL = $F7 ; when ball reaches one of these, do something
TOPWALL = $20
BOTTOMWALL = $D8
LEFTWALL = $02
PADDLE1X = $08 ; horizontal position for paddles, doesnt move
PADDLE2X = $F0
;;;;;;;;;;;;;;;;;;
.bank 0
.org $C000
RESET:
SEI ; disable IRQs
CLD ; disable decimal mode
LDX #$40
STX $4017 ; disable APU frame IRQ
LDX #$FF
TXS ; Set up stack
INX ; now X = 0
STX $2000 ; disable NMI
STX $2001 ; disable rendering
STX $4010 ; disable DMC IRQs
vblankwait1: ; First wait for vblank to make sure PPU is ready
BIT $2002
BPL vblankwait1
clrmem:
LDA #$00
STA $0000, x
STA $0100, x
STA $0300, x
STA $0400, x
STA $0500, x
STA $0600, x
STA $0700, x
LDA #$FE
STA $0200, x
INX
BNE clrmem
vblankwait2: ; Second wait for vblank, PPU is ready after this
BIT $2002
BPL vblankwait2
;;;Set some initial ball stats
LDA #$01
STA balldown
STA ballright
LDA #$00
STA ballup
STA ballleft
LDA #$50
STA bally
LDA #$80
STA ballx
LDA #$02
STA ballspeedx
STA ballspeedy
;;;Set initial paddle positions
LDA #$30
STA paddle1ytop
STA paddle2ytop
LDA #$4D
STA paddle1ybot
STA paddle2ybot
LoadBackground:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$20
STA $2006 ; write the high byte of $2000 address
LDA #$00
STA $2006 ; write the low byte of $2000 address
LDX #$04
LDY #$00
LoadBackgroundLoop:
LDA #$24
CPX #$04
BNE Definitely
CPY #$80
BCS Definitely
LDA #$24
CPY #$41 ;; Check if score 1 position
BNE CheckSecond
LDA score1
CheckSecond:
CPY #$5E ;; Check if score 2 position
BNE NotScore
LDA score2
NotScore: ;; Not score position, load sky
CPY #$60
BCC Definitely
CPY #$80
BCS Definitely
LDA #$45
Definitely:
CPX #$01
BNE NotDown
CPY #$80
BCC NotDown
LDA #$45
NotDown:
STA $2007
INY
BNE LoadBackgroundLoop
; Outer loop 4 times
DEX
BNE LoadBackgroundLoop
LoadPalettes:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$3F
STA $2006 ; write the high byte of $3F00 address
LDA #$00
STA $2006 ; write the low byte of $3F00 address
LDX #$00 ; start out at 0
LoadPalettesLoop:
LDA palette, x ; load data from address (palette + the value in x)
; 1st time through loop it will load palette+0
; 2nd time through loop it will load palette+1
; 3rd time through loop it will load palette+2
; etc
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$20 ; Compare X to hex $10, decimal 16 - copying 16 bytes = 4 sprites
BNE LoadPalettesLoop ; Branch to LoadPalettesLoop if compare was Not Equal to zero
; if compare was equal to 32, keep going down
LoadAttribute:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$23
STA $2006 ; write the high byte of $23C0 address
LDA #$C0
STA $2006 ; write the low byte of $23C0 address
LDX #$00 ; start out at 0
LoadAttributeLoop:
LDA #%10101010 ; load data from address (attribute + the value in x)
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$5A ; Compare X to hex $08, decimal 8 - copying 8 bytes
BNE LoadAttributeLoop ; Branch to LoadAttributeLoop if compare was Not Equal to zero
; if compare was equal to 128, keep going down
;;;Set starting game state
LDA #STATEPLAYING
STA gamestate
LDA #%10010000 ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
STA $2000
LDA #%00011110 ; enable sprites, enable background, no clipping on left side
STA $2001
Forever:
JMP Forever ;jump back to Forever, infinite loop, waiting for NMI
NMI:
LDA #$00
STA $2003 ; set the low byte (00) of the RAM address
LDA #$02
STA $4014 ; set the high byte (02) of the RAM address, start the transfer
;;JSR DrawScore
;;This is the PPU clean up section, so rendering the next frame starts properly.
LDA #%10010000 ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
STA $2000
LDA #%00011110 ; enable sprites, enable background, no clipping on left side
STA $2001
LDA #$00 ;;tell the ppu there is no background scrolling
STA $2005
STA $2005
;;;all graphics updates done by here, run game engine
JSR ReadController1 ;;get the current button data for player 1
JSR ReadController2 ;;get the current button data for player 2
GameEngine:
LDA gamestate
CMP #STATETITLE
BEQ EngineTitle ;;game is displaying title screen
LDA gamestate
CMP #STATEGAMEOVER
BEQ EngineGameOver ;;game is displaying ending screen
LDA gamestate
CMP #STATEPLAYING
BEQ EnginePlaying ;;game is playing
GameEngineDone:
JSR UpdateSprites ;;set ball/paddle sprites from positions
RTI ; return from interrupt
;;;;;;;;
EngineTitle:
;;if start button pressed
;; turn screen off
;; load game screen
;; set starting paddle/ball position
;; go to Playing State
;; turn screen on
JMP GameEngineDone
;;;;;;;;;
EngineGameOver:
;;if start button pressed
;; turn screen off
;; load title screen
;; go to Title State
;; turn screen on
LDA gameOverDrawn
CMP #$01
BEQ Almost
LDA #$00
STA $2000 ; disable NMI
STA $2001 ; disable rendering
LDA #$21 ; load the first part
STA $2006 ; store into input port of PPU
LDA #$EB ; load the second part
STA $2006 ; store again
LDX #$00
LoadGameOverLoop:
LDA gameoversign, x
STA $2007
INX
CPX #$0A
BNE LoadGameOverLoop
LDA #%10010000
STA $2000
LDA #%00011110 ; enable sprites, enable background, no clipping on left side
STA $2001
LDA #$01
STA gameOverDrawn
Almost:
JMP GameEngineDone
;;;;;;;;;;;
EnginePlaying:
MoveBallRight:
LDA ballright
BEQ MoveBallRightDone ;;if ballright=0, skip this section
LDA ballx
CLC
ADC ballspeedx ;;ballx position = ballx + ballspeedx
STA ballx
LDA ballx
CMP #RIGHTWALL
BCC MoveBallRightDone ;;if ball x < right wall, still on screen, skip next section
JSR ResetLeft
MoveBallRightDone:
MoveBallLeft:
LDA ballleft
BEQ MoveBallLeftDone ;;if ballleft=0, skip this section
LDA ballx
SEC
SBC ballspeedx ;;ballx position = ballx - ballspeedx
STA ballx
LDA ballx
CMP #LEFTWALL
BCS MoveBallLeftDone ;;if ball x > left wall, still on screen, skip next section
JSR ResetRight
MoveBallLeftDone:
MoveBallUp:
LDA ballup
BEQ MoveBallUpDone ;;if ballup=0, skip this section
LDA bally
SEC
SBC ballspeedy ;;bally position = bally - ballspeedy
STA bally
LDA bally
CMP #TOPWALL
BCS MoveBallUpDone ;;if ball y > top wall, still on screen, skip next section
LDA #$01
STA balldown
LDA #$00
STA ballup ;;bounce, ball now moving down
MoveBallUpDone:
MoveBallDown:
LDA balldown
BEQ MoveBallDownDone ;;if ballup=0, skip this section
LDA bally
CLC
ADC ballspeedy ;;bally position = bally + ballspeedy
STA bally
LDA bally
CMP #BOTTOMWALL
BCC MoveBallDownDone ;;if ball y < bottom wall, still on screen, skip next section
LDA #$00
STA balldown
LDA #$01
STA ballup ;;bounce, ball now moving down
MoveBallDownDone:
MovePaddleUp1:
LDA buttons1
AND #%00001000
BEQ MovePaddleUpDone1 ;; If up button pressed
;; if paddle top > top wall
LDA paddle1ytop
SEC
SBC #TOPWALL
BCC MovePaddleUpDone1
;; move paddle top and bottom up
LDA paddle1ytop ;; Move paddle top up
SEC
SBC #$03
STA paddle1ytop
LDA paddle1ybot ;; Move paddle bot up
SEC
SBC #$03
STA paddle1ybot
MovePaddleUpDone1:
MovePaddleUp2:
LDA buttons2
AND #%00001000
BEQ MovePaddleUpDone2 ;; If up button pressed
;; if paddle top > top wall
LDA paddle2ytop
SEC
SBC #TOPWALL
BCC MovePaddleUpDone2
;; move paddle top and bottom up
LDA paddle2ytop ;; Move paddle top up
SEC
SBC #$03
STA paddle2ytop
LDA paddle2ybot ;; Move paddle bot up
SEC
SBC #$03
STA paddle2ybot
MovePaddleUpDone2:
MovePaddleDown:
LDA buttons1
AND #%00000100
BEQ MovePaddleDownDone ;; If down button pressed
;; if paddle bottom < bottom wall
LDA #BOTTOMWALL
SEC
SBC paddle1ybot
BCC MovePaddleDownDone
;;if down button pressed
;; move paddle top and bottom down
LDA paddle1ytop ;; Move paddle top down
CLC
ADC #$03
STA paddle1ytop
LDA paddle1ybot ;; Move paddle bot down
CLC
ADC #$03
STA paddle1ybot
MovePaddleDownDone:
MovePaddleDown2:
LDA buttons2
AND #%00000100
BEQ MovePaddleDownDone2 ;; If down button pressed
;; if paddle bottom < bottom wall
LDA #BOTTOMWALL
SEC
SBC paddle2ybot
BCC MovePaddleDownDone2
;;if down button pressed
;; move paddle top and bottom down
LDA paddle2ytop ;; Move paddle top down
CLC
ADC #$03
STA paddle2ytop
LDA paddle2ybot ;; Move paddle bot down
CLC
ADC #$03
STA paddle2ybot
MovePaddleDownDone2:
CheckPaddleCollisionLeft:
;;if (ball x - 7) < paddle1x
LDA ballx
SEC
SBC #$07
SEC
SBC #PADDLE1X
BCS CheckPaddleCollisionLeftDone
;; if (ball paddle ytop - 7) < y
LDA paddle1ytop
SEC
SBC #$07
SEC
SBC bally
BCS CheckPaddleCollisionLeftDone
;; if ball y < paddle y bottom
LDA bally
SEC
SBC paddle1ybot
BCS CheckPaddleCollisionLeftDone
;; Bounce, ball now moving right
LDA #$00
STA ballleft
LDA #$01
STA ballright
CheckPaddleCollisionLeftDone:
CheckPaddleCollisionRight:
;; If (paddle2x - 7) < ball x
LDA #PADDLE2X
SEC
SBC #07
SEC
SBC ballx
BCS CheckPaddleCollisionRightDone
;; If (paddle2 ytop - 7) < bally y
LDA paddle2ytop
SEC
SBC #$07
SEC
SBC bally
BCS CheckPaddleCollisionRightDone
;; If bally < paddle2 ybottom
LDA bally
SEC
SBC paddle2ybot
BCS CheckPaddleCollisionRightDone
;; Bounce, ball now moving left
LDA #$01
STA ballleft
LDA #$00
STA ballright
CheckPaddleCollisionRightDone:
JMP GameEngineDone
UpdateSprites:
LDA bally ;; Update all ball sprite info
STA $0200
LDA #$75 ;; Sprite num
STA $0201
LDA #$00 ;; Pallette
STA $0202
LDA ballx
STA $0203
;; update paddle sprites
;; Paddle 1
LDX #$00
LDY paddle1ytop
LoopUpdatePaddle1:
TYA ;; Y positon
STA $0204, x
CLC
ADC #$08 ;; Increase paddle Y by 8?
TAY
LDA #$86 ;; Sprite num
STA $0205, x
LDA #$03 ;; Pallette
STA $0206, x
LDA #PADDLE1X ;; X position
STA $0207, x
TXA ;; Increase X by 4
CLC
ADC #$04
TAX
CPX #$10 ;; Check if we did 4 passes, if not loop
BNE LoopUpdatePaddle1
;; Paddle 2
LDX #$00
LDY paddle2ytop
LoopUpdatePaddle2:
TYA
STA $0214, x
CLC
ADC #$08
TAY
LDA #$86 ;; Sprite num
STA $0215, x
LDA #$03 ;; Pallette
STA $0216, x
LDA #PADDLE2X
STA $0217, x
TXA
CLC
ADC #$04
TAX
CPX #$10
BNE LoopUpdatePaddle2
RTS
DrawScore:
;;draw score on screen using background tiles`
LDA #$00
STA $2000 ; disable NMI
STA $2001 ; disable rendering
LDA #$20 ; load the first part of the location for the score
STA $2006 ; store into input port of PPU
LDA #$41 ; load the second part of the location for the score
STA $2006 ; store again
LDA score1
STA $2007
LDA #$20 ; load the first part of the location for the score
STA $2006 ; store into input port of PPU
LDA #$5E ; load the second part of the location for the score
STA $2006 ; store again
LDA score2
STA $2007
LDA #%10010000
STA $2000
;;or using many sprites
RTS
ReadController1:
LDA #$01
STA $4016
LDA #$00
STA $4016
LDX #$08
ReadController1Loop:
LDA $4016
LSR A ; bit0 -> Carry
ROL buttons1 ; bit0 <- Carry
DEX
BNE ReadController1Loop
RTS
ReadController2:
LDA #$01
STA $4016
LDA #$00
STA $4016
LDX #$08
ReadController2Loop:
LDA $4017
LSR A ; bit0 -> Carry
ROL buttons2 ; bit0 <- Carry
DEX
BNE ReadController2Loop
RTS
ResetRight:
;; Position ball at right paddle
LDA #PADDLE2X
STA ballx
LDA paddle2ytop
CLC
ADC #$14
STA bally
;; ball moving left
LDA #$01
STA ballleft
LDA #$00
STA ballright
;; Increase score 2
LDX score2
INX
STX score2
JSR DrawScore
LDX score2
CPX #$09
BNE Player2NotWinning
LDX #STATEGAMEOVER
STX gamestate
Player2NotWinning:
RTS
ResetLeft:
;; Position ball at left paddle
LDA #PADDLE1X
STA ballx
LDA paddle1ytop
CLC
ADC #$14
STA bally
;; ball moving right
LDA #$00
STA ballleft
LDA #$01
STA ballright
;; Increase left score
LDX score1
INX
STX score1
JSR DrawScore
LDX score1
CPX #$09
BNE Player1NotWinning
LDX #STATEGAMEOVER
STX gamestate
Player1NotWinning:
RTS
;;;;;;;;;;;;;;
.bank 1
.org $E000
palette:
.db $22,$29,$1A,$0F, $22,$36,$17,$0F, $22,$30,$21,$0F, $22,$27,$17,$0F ;;background palette
.db $22,$0F,$27,$30, $22,$02,$38,$3C, $22,$1C,$15,$14, $22,$0F,$36,$17 ;;sprite palette
sprites:
;vert tile attr horiz
.db $80, $32, $00, $80 ;sprite 0
.db $80, $33, $00, $88 ;sprite 1
.db $88, $34, $00, $80 ;sprite 2
.db $88, $35, $00, $88 ;sprite 3
gameoversign:
.db $10, $0A, $16, $0E, $24, $24, $18, $1F, $0E, $1B
.org $FFFA ;first of the three vectors starts here
.dw NMI ;when an NMI happens (once per frame if enab ed) the
;processor will jump to the label NMI:
.dw RESET ;when the processor first turns on or is reset, it will jump
;to the label RESET:
.dw 0 ;external interrupt IRQ is not used in this tutorial
;;;;;;;;;;;;;;
.bank 2
.org $0000
.incbin "mario.chr" ;includes 8KB graphics file from SMB1
NESASM3 pong.asm
pause
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment