Last active
February 22, 2018 22:53
-
-
Save Sorebit/fc1731c1a8779591d8af4dad0b5a61af to your computer and use it in GitHub Desktop.
NES implementation of pong
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
NESASM3 pong.asm | |
pause |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment