Help understanding code that changes individual background tiles during gameplay

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
justin-rwx
Posts: 14
Joined: Sun Jan 10, 2021 1:12 pm

Help understanding code that changes individual background tiles during gameplay

Post by justin-rwx »

I'm trying to understand how the following code changes specific background tiles during gameplay.

Code: Select all

DrawP1Winner:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (GameOverWinnerTiles - Nametable_GameOver))
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (GameOverWinnerTiles - Nametable_GameOver))
  STA $2006
  .
  .
  .
  DrawP1Pause:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing))
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing))
  STA $2006
.
.
.
etc...  
Can somebody explain how the Pong code above knows which background to access, and which tiles in the background to access? Why doesn't just saying LDA #high(PauseTiles) and LDA #low(PauseTiles) work? With some tinkering, I found that adjusting the #high and #low addresses changes the location of where text appears when the game is paused. Therefore, I am assuming that A000 is a specific location on the screen? Or is it the second 8kB page of PRG memory? Both?

This seems like a fundamental concept to understand regarding changing individual background tiles (Player's score, time tiles, text, etc.) during gameplay, unless there is a better way to do it.

Finally, the Pong writer uses NESASM to compile his code, hence the .org calls. I intend to use CA65 for compiling future ASM code I write. Does this method of nametable tile updates still apply to CA65?

The entirety of the Pong code (with some redactions due to character limits) below:

Code: Select all

;--------------------------------------------------;
;                   pong.asm                       ;
;        A simple Pong game for the NES            ;
;--------------------------------------------------;


;--------------------------------------------------;
;                   iNES Headers                   ;
;--------------------------------------------------;
  .inesprg 1   ; 1x 16KB PRG code
  .ineschr 1   ; 1x  8KB CHR data
  .inesmap 0   ; mapper 0 = NROM, no bank swapping
  .inesmir 1   ; background mirroring
  

;--------------------------------------------------;
;                    Constants                     ;
;--------------------------------------------------;
TRUE          = $01
FALSE         = $00
BUTTON_A      = %10000000
BUTTON_B      = %01000000
BUTTON_SELECT = %00100000
BUTTON_START  = %00010000
BUTTON_UP     = %00001000
BUTTON_DOWN   = %00000100
BUTTON_LEFT   = %00000010
BUTTON_RIGHT  = %00000001
SPRITE_SIZE   = $08

SPRITE_LOCATION   = $0200
GRAPHICS_LOCATION = $A000  

;Game-specific constants
PADDLE1X      = $0B
PADDLE2X      = $ED
RIGHTWALL     = $F8
LEFTWALL      = $01
BOTTOMWALL    = $C8
TOPWALL       = $38
BLANK_TILE_BG = $24

;Game States
STATE_LOADTITLE     = $00
STATE_TITLE         = $01
STATE_LOADPLAYING   = $02
STATE_PLAYING       = $03
STATE_P1PAUSE       = $04
STATE_P2PAUSE       = $05
STATE_LOADGAMEOVER  = $06
STATE_GAMEOVER      = $07

;;Variables removed due to maximum character allowance

;--------------------------------------------------;
;                     Macros                       ;
;--------------------------------------------------;

;Zero flag clear if button is currently held down
Test_Button .macro ;usage: Test_Button buttonsP1, BUTTON_RIGHT.  BEQ if not pressed.
  LDA \1
  AND #\2
  .endm

;Zero flag clear if button has just been pressed
Test_Button_Pressed .macro 
;usage: 
;Test_Button_Pressed ButtonsP1, BUTTON_RIGHT
  LDA \1+1
  AND #\2
  BNE .Test_Button_Pressed_PrevWasPressed 
  LDA \1 
  AND #\2
  JMP .Test_Button_Pressed_Done
.Test_Button_Pressed_PrevWasPressed
  LDX #$FF
  INX       ;force Zero flag to be set before returning
.Test_Button_Pressed_Done
  .endm

;Zero flag clear if button has just been released (prev = 1, curr = 0)
Test_Button_Released .macro
  LDA \1
  AND #\2
  BNE .Test_Button_Released_CurrWasReleased 
  LDA \1+1
  AND #\2
  JMP .Test_Button_Released_Done
.Test_Button_Released_CurrWasReleased
  LDX #$FF
  INX       ;force Zero flag to be set before returning
.Test_Button_Released_Done
  .endm

;--------------------------------------------------;
;                     Bank 0                       ;
;--------------------------------------------------;
  .bank 0
  .org $8000

;--------------------------------------------------;
;                     RESET                        ;
;--------------------------------------------------;
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

  JSR vblankwait
clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0200, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA SPRITE_LOCATION, x
  INX
  BNE clrmem
  JSR vblankwait

  ;Initialize variables
  LDA #FALSE
  STA backgroundNeedsRedrawn

  LDA #STATE_LOADTITLE
  STA gamestate

  ;Initialize graphics
  JSR LoadPalette
  JSR LoadSprites
  JSR LoadNametable
  JSR LoadAttribute

  ;Initialize PPU Settings (enable Sprites and background)
  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
 
;--------------------------------------------------;
;                      NMI                         ;
;--------------------------------------------------;
NMI:
  ;Transfer sprite data to PPU using DMA
  LDA #low(SPRITE_LOCATION)
  STA $2003       ; set the low byte (00) of the RAM address
  LDA #high(SPRITE_LOCATION)
  STA $4014       ; set the high byte (02) of the RAM address, start the transfer

  ;Check flag to redraw background 
  LDA backgroundNeedsRedrawn
  CMP #TRUE
  BNE .continue
  JSR RedrawBackgroundTiles 
  LDA #FALSE
  STA backgroundNeedsRedrawn
.continue

  ;;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

  JSR ReadControllerState

GameEngine:
  LDA gamestate         ;Load the current game state
  ASL A             ;x2 to index into the table correctly (<128 game states)
  TAY
  LDA GameEnginePointerTable, Y     ;Load the address from the table
  STA tempWord
  LDA GameEnginePointerTable+1, Y
  STA tempWord+1
  JMP [tempWord]        ;Jump to the proper engine state
GameEngineDone:

  JSR UpdateSprites
  RTI             ; return from interrupt
 
GameEnginePointerTable:
  .dw EngineLoadTitle     ;One for each game state
  .dw EngineTitle
  .dw EngineLoadPlaying
  .dw EnginePlaying
  .dw EngineP1Pause
  .dw EngineP2Pause
  .dw EngineLoadGameOver
  .dw EngineGameOver

;--------------------------------------------------;
;               Game Engine Blocks                 ;
;--------------------------------------------------;
EngineLoadTitle:
  
  JSR InitializeGameVariables

  LDA #%00000000        ;Turn the screen off
  STA $2000
  STA $2001
  JSR LoadNametable     ;Load the new Nametable, Attribute, and Palette
  JSR LoadAttribute
  JSR LoadPalette
  JSR LoadSprites
  LDA #%10001000        ;Turn the screen on
  STA $2000

  JSR HideAllSprites

  LDA #STATE_TITLE
  STA gamestate


  JMP GameEngineDone
EngineTitle:
  JSR CheckStart
  JMP GameEngineDone
EngineLoadPlaying:
  
  ;Redraw screen
  LDA #%00000000        ;Turn the screen off
  STA $2000
  STA $2001
  JSR LoadNametable     ;Load the new Nametable, Attribute, and Palette
  JSR LoadAttribute
  JSR LoadPalette
  JSR LoadSprites
  LDA #%10001000        ;Turn the screen on
  STA $2000
  LDA #STATE_PLAYING    ;Set the new gamestate;;;;;;;;;;;;;;;;;#STATE_PLAYING->STATE_TITLE
  STA gamestate

  LDA timer2
  STA myWord
  LDA #$00
  STA myWord+1
  JSR HexWordToUnsignedDecimal
  LDA myDec+3
  STA timerDec
  LDA myDec+4
  STA timerDec+1

  LDA #TRUE
  STA backgroundNeedsRedrawn
  STA timerNeedsRedrawn

  JMP GameEngineDone

EnginePlaying:
  ;reset redraw flags
  LDA #FALSE
  STA backgroundNeedsRedrawn
  STA scoresNeedRedrawn
  STA timerNeedsRedrawn
  JSR UpdateTimer
  JSR CheckPaddle1Collision
  JSR CheckPaddle2Collision
  JSR Player1ReleaseBall
  JSR Player2ReleaseBall
  JSR MoveBallUp
  JSR MoveBallDown
  JSR MoveBallLeft
  JSR MoveBallRight
  JSR MovePaddle1Up
  JSR MovePaddle2Up
  JSR MovePaddle1Down
  JSR MovePaddle2Down
  JSR CheckP1Pause
  JSR CheckP2Pause
  JSR CheckTimeUp

  JMP GameEngineDone

EngineP1Pause:
  JSR CheckP1Unpause 
  JMP GameEngineDone

EngineP2Pause:
  JSR CheckP2Unpause
  JMP GameEngineDone

EngineLoadGameOver:
  LDA #%00000000        ;Turn the screen off
  STA $2000
  STA $2001
  JSR LoadNametable     ;Load the new Nametable, Attribute, and Palette
  JSR LoadAttribute
  JSR LoadPalette
  JSR LoadSprites
  LDA #%10001000        ;Turn the screen on
  STA $2000
  JSR HideAllSprites
  LDA #TRUE
  STA backgroundNeedsRedrawn
  LDA #STATE_GAMEOVER    ;Set the new gamestate
  STA gamestate

  JMP GameEngineDone

EngineGameOver:
  JSR CheckReturnToTitle
  JMP GameEngineDone

;--------------------------------------------------;
;                  Subroutines                     ;
;--------------------------------------------------;
UpdateSprites:
  
  ;If we're in EngineLoadTitle, EngineTitle, EngineLoadGameOver, or EngineGameOver, don't update paddle/ball sprites:
  LDA gamestate
  CMP #STATE_LOADTITLE
  BEQ .UpdateGameComponentsDone
  CMP #STATE_TITLE
  BEQ .UpdateGameComponentsDone
  CMP #STATE_LOADGAMEOVER
  BEQ .UpdateGameComponentsDone
  CMP #STATE_GAMEOVER
  BEQ .UpdateGameComponentsDone

  ;Update ball coordinates
  LDA ballY
  STA SPRITE_LOCATION + (Sprite_Ball - Sprites)
  LDA ballX
  STA SPRITE_LOCATION + (Sprite_Ball - Sprites) + 3

  ;Update p1 paddle coordinates (only need to change Y currently)
  LDA paddle1YTop
  LDX paddle1Size
  LDY #$00

.loop1
  CPX #$00
  BEQ .done1 
  STA SPRITE_LOCATION + (Sprite_Paddle1 - Sprites), Y
  CLC
  ADC #$08
  INY
  INY
  INY
  INY
  DEX
  JMP .loop1
.done1 

  ;Update p2 paddle coordinates (only need to change Y currently)
  LDA paddle2YTop
  LDX paddle2Size
  LDY #$00
.loop2
  CPX #$00
  BEQ .done2
  STA SPRITE_LOCATION + (Sprite_Paddle2 - Sprites), Y
  CLC
  ADC #$08
  INY
  INY
  INY
  INY
  DEX
  JMP .loop2
.done2
.UpdateGameComponentsDone
  
  RTS

RedrawBackgroundTiles:
  ;Draw winner at GameOver, no other redraws 
  LDA gamestate
  CMP #STATE_GAMEOVER
  BNE .gameoverChecked
  LDA player1Score
  CMP player2Score
  BEQ .tie 
  BCC .p2Win 
  BCS .p1Win 
.tie 
  JSR DrawTie 
  RTS
.p1Win 
  JSR DrawP1Winner
  RTS
.p2Win 
  JSR DrawP2Winner
  RTS
.gameoverChecked

  ;Non-gameover redraws
  LDA scoresNeedRedrawn   ;Check if scores need updated
  CMP #TRUE
  BNE .scoresChecked
  JSR DrawP1Score
  JSR DrawP2Score
.scoresChecked
  LDA timerNeedsRedrawn
  CMP #TRUE
  BNE .timerChecked
  JSR DrawTimer
.timerChecked
  LDA gamestate ;check if paused 
  CMP #STATE_P1PAUSE
  BNE .p1PauseChecked
  JSR DrawP1Pause
  JMP .pauseChecked
.p1PauseChecked
  LDA gamestate
  CMP #STATE_P2PAUSE
  BNE .p2PauseChecked
  JSR DrawP2Pause
  JMP .pauseChecked
.p2PauseChecked
  ;not paused, draw blank tiles
  JSR RemovePause
.pauseChecked
  ;Additional background redraws

  RTS

InitializeGameVariables:
;Initialize variables
  LDA #$00
  STA player1Score
  STA player2Score 
  STA player1ScoreDec
  STA player1ScoreDec+1
  STA player1ScoreDec+2
  STA player1ScoreDec+3
  STA player1ScoreDec+4
  STA player2ScoreDec
  STA player2ScoreDec+1
  STA player2ScoreDec+2
  STA player2ScoreDec+3
  STA player2ScoreDec+4

  STA timerDec
  STA timerDec+1

  LDA #$01
  STA ballSpeedY
  LDA #$01
  STA ballGoingDown
  STA ballGoingRight
  LDA #$00
  STA ballGoingUp
  STA ballGoingLeft
  LDA #$03
  STA paddle1Speed
  STA paddle2Speed
  LDA #$02
  STA ballSpeedX
  LDA #$70
  STA paddle1YTop
  LDA #$70 + 8 * 4
  STA paddle1YBot
  LDA #$70
  STA paddle2YTop
  LDA #$70 + 8 * 4
  STA paddle2YBot
  LDA #$05
  STA paddle1Size
  STA paddle2Size
  LDA #FALSE
  STA ballInMotion
  LDA #TRUE
  STA ballInP1Control
  LDA paddle1YTop
  CLC
  ADC #SPRITE_SIZE * 2
  TAY
  LDA #PADDLE1X
  CLC
  ADC #SPRITE_SIZE
  TAX
  STY ballY
  STX ballX
  LDA #99
  STA timer2
  LDA #$00
  STA timer1
  RTS

CheckReturnToTitle:
  Test_Button_Pressed buttonsP1, BUTTON_START 
  BEQ .CheckReturnToTitleDone
  LDA #STATE_LOADTITLE
  STA gamestate
  LDA #$AA 
  STA debug
.CheckReturnToTitleDone
  RTS

CheckTimeUp:
  LDA timer2
  CMP #$00
  BNE .CheckWinDone
  LDA #STATE_LOADGAMEOVER
  STA gamestate
.CheckWinDone
  RTS

CheckP2Unpause:
  Test_Button_Pressed buttonsP2, BUTTON_START
  BEQ .checkP1UnpauseDone
  LDA #STATE_PLAYING
  STA gamestate
  LDA #TRUE
  STA backgroundNeedsRedrawn
.checkP1UnpauseDone
  RTS

CheckP1Unpause:
  Test_Button_Pressed buttonsP1, BUTTON_START
  BEQ .checkP1UnpauseDone
  LDA #STATE_PLAYING
  STA gamestate
  LDA #TRUE
  STA backgroundNeedsRedrawn
.checkP1UnpauseDone
  RTS

CheckP1Pause:
  Test_Button_Pressed buttonsP1, BUTTON_START
  BEQ .checkP1PauseDone
  LDA #STATE_P1PAUSE
  STA gamestate
  LDA #TRUE
  STA backgroundNeedsRedrawn
.checkP1PauseDone
  RTS

CheckP2Pause:
  Test_Button_Pressed buttonsP2, BUTTON_START
  BEQ .checkP2PauseDone
  LDA #STATE_P2PAUSE
  STA gamestate
  LDA #TRUE
  STA backgroundNeedsRedrawn
.checkP2PauseDone
  RTS

DrawP1Winner:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (GameOverWinnerTiles - Nametable_GameOver))
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (GameOverWinnerTiles - Nametable_GameOver))
  STA $2006
  LDA #$19
  STA $2007
  LDA #$15
  STA $2007
  LDA #$0A
  STA $2007
  LDA #$22
  STA $2007
  LDA #$0E
  STA $2007
  LDA #$1B
  STA $2007
  LDA #$24
  STA $2007
  LDA #$01
  STA $2007
  LDA #$24
  STA $2007
  LDA #$20
  STA $2007
  LDA #$12
  STA $2007
  LDA #$17
  STA $2007
  LDA #$1C
  STA $2007
  LDA #$2B
  STA $2007
  RTS

DrawP2Winner:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (GameOverWinnerTiles - Nametable_GameOver))
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (GameOverWinnerTiles - Nametable_GameOver))
  STA $2006
  LDA #$19
  STA $2007
  LDA #$15
  STA $2007
  LDA #$0A
  STA $2007
  LDA #$22
  STA $2007
  LDA #$0E
  STA $2007
  LDA #$1B
  STA $2007
  LDA #$24
  STA $2007
  LDA #$02
  STA $2007
  LDA #$24
  STA $2007
  LDA #$20
  STA $2007
  LDA #$12
  STA $2007
  LDA #$17
  STA $2007
  LDA #$1C
  STA $2007
  LDA #$2B
  STA $2007
  RTS

DrawTie:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (GameOverWinnerTiles - Nametable_GameOver))
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (GameOverWinnerTiles - Nametable_GameOver))
  STA $2006
  LDA #$24
  STA $2007
  LDA #$24
  STA $2007
  LDA #$12
  STA $2007
  LDA #$1D
  STA $2007
  LDA #$FA
  STA $2007
  LDA #$1C
  STA $2007
  LDA #$24
  STA $2007
  LDA #$0A
  STA $2007
  LDA #$24
  STA $2007
  LDA #$1D
  STA $2007
  LDA #$12
  STA $2007
  LDA #$0E
  STA $2007
  LDA #$2B
  STA $2007
  LDA #$24
  STA $2007
  RTS

DrawP1Pause:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing))	;Nametable_Playing->Nametable_Title
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing))	;Nametable_Playing->Nametable_Title
  STA $2006
  LDA #$19
  STA $2007
  LDA #$01
  STA $2007
  LDA #$24
  STA $2007
  LDA #$19
  STA $2007
  LDA #$0A
  STA $2007
  LDA #$1E
  STA $2007
  LDA #$1C
  STA $2007
  LDA #$0E
  STA $2007
  RTS

DrawP2Pause:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing))	;Nametable_Playing->Nametable_Title
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing))  ;Nametable_Playing->Nametable_Title
  STA $2006
  LDA #$19
  STA $2007
  LDA #$02
  STA $2007
  LDA #$24
  STA $2007
  LDA #$19
  STA $2007
  LDA #$0A
  STA $2007
  LDA #$1E
  STA $2007
  LDA #$1C
  STA $2007
  LDA #$0E
  STA $2007
  RTS

RemovePause:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing))
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing))
  STA $2006
  LDA #$24
  STA $2007
  STA $2007
  STA $2007
  STA $2007
  STA $2007
  STA $2007
  STA $2007
  STA $2007
  RTS

CheckStart:
  Test_Button_Pressed buttonsP1, BUTTON_START
  BEQ .CheckStartDone
  LDA #STATE_LOADPLAYING
  STA gamestate
.CheckStartDone
  RTS

UpdateTimer:
  LDA ballInMotion
  CMP #TRUE
  BNE .UpdateTimerDone

  LDA timer1
  CLC
  ADC #$01
  STA timer1
  CMP #$3F
  BEQ .decrementTimer
  JMP .UpdateTimerDone
.decrementTimer
  LDA #TRUE
  STA timerNeedsRedrawn
  STA backgroundNeedsRedrawn
  LDA #$00
  STA timer1
  LDA timer2
  SEC
  SBC #$01
  STA timer2

  STA myWord
  LDA #$00
  STA myWord+1
  JSR HexWordToUnsignedDecimal
  LDA myDec+3
  STA timerDec
  LDA myDec+4
  STA timerDec+1

  LDA timer2
  CMP #$00
  BNE .UpdateTimerDone
.UpdateTimerDone
  RTS

CheckPaddle1Collision: ;checks for collision coming from the right only
  LDA ballX 
  CMP #PADDLE1X + SPRITE_SIZE
  BCS .CheckPaddle1CollisionDone ;if ballX > paddle1 X, there's no collision with the paddle
  LDA ballY                     ;otherwise, check if it's actually on the paddle
  CMP paddle1YTop               ;if ballY < paddle1Ytop, it's above the paddle, so no collision
  BCC .CheckPaddle1CollisionDone
  CMP paddle1YBot               ;if ballY > paddle1Ybot, it's below the paddle, so no collision
  BCS .CheckPaddle1CollisionDone
  LDA #FALSE                      ;if we get here, it was a collision, so we switch direction
  STA ballGoingLeft
  LDA #TRUE
  STA ballGoingRight
.CheckPaddle1CollisionDone
  RTS

CheckPaddle2Collision:  ;checks for collision coming from the left only
  LDA ballX
  CMP #PADDLE2X - SPRITE_SIZE
  BCC .CheckPaddle2CollisionDone ;if ballX < paddle2 X, there's no collision with the paddle
  LDA ballY
  CMP paddle2YTop
  BCC .CheckPaddle2CollisionDone ;if ballY < paddle2YTop, it's above the paddle, so no collision
  CMP paddle2YBot             
  BCS .CheckPaddle2CollisionDone ;if ballY > paddle2YBot, it's below the paddle, so no collision
  LDA #FALSE
  STA ballGoingRight
  LDA #TRUE
  STA ballGoingLeft
.CheckPaddle2CollisionDone
  RTS

Player1ReleaseBall:
  Test_Button buttonsP1, BUTTON_A 
  BEQ .Player1ReleaseBallDone
  LDA ballInMotion
  CMP #FALSE
  BNE .Player1ReleaseBallDone
  LDA ballInP1Control
  CMP #TRUE
  BNE .Player1ReleaseBallDone
  LDA #TRUE
  STA ballGoingRight
  LDA #FALSE
  STA ballGoingLeft
  LDA #TRUE
  STA ballInMotion
.Player1ReleaseBallDone
  RTS

Player2ReleaseBall:
  Test_Button buttonsP2, BUTTON_A 
  BEQ .Player2ReleaseBallDone
  LDA ballInMotion
  CMP #FALSE
  BNE .Player2ReleaseBallDone
  LDA ballInP2Control
  CMP #TRUE
  BNE .Player2ReleaseBallDone
  LDA #TRUE
  STA ballGoingLeft
  LDA #FALSE
  STA ballGoingRight
  LDA #TRUE
  STA ballInMotion
.Player2ReleaseBallDone
  RTS


;First check for normal ball movement, bounds on wall
MoveBallRight:
  LDA ballInMotion
  CMP #FALSE
  BEQ .MoveBallRightDone
  LDA ballGoingRight
  BEQ .MoveBallRightDone 
  LDA ballX ;Add to the ball's X coord
  CLC
  ADC ballSpeedX
  BCS .hitTheWall ;make sure it didn't go to the left side of the screen
  ;if it didn't, now we can check for collision with right wall
  STA ballX
  CMP #RIGHTWALL
  BCC .MoveBallRightDone ;if it's still less than the right wall, we're done 
.hitTheWall ;otherwise, we hit the wall, let's change direction

  ;Reposition ball (give to P1)
  LDA paddle1YTop
  CLC
  ADC #SPRITE_SIZE * 2
  TAY
  LDA #PADDLE1X
  CLC
  ADC #SPRITE_SIZE
  TAX
  STY ballY
  STX ballX

  ;Set movement flags
  LDA #FALSE
  STA ballInMotion
  STA ballInP2Control
  LDA #TRUE
  STA ballInP1Control

  LDX #$0A ; 1000 points for ball
  JSR AddToP1Score

  ;Convert to decimal
  LDA player1Score
  STA myWord
  LDA player1Score+1
  STA myWord+1

  JSR HexWordToUnsignedDecimal

  LDA myDec
  STA player1ScoreDec
  LDA myDec+1
  STA player1ScoreDec+1
  LDA myDec+2
  STA player1ScoreDec+2
  LDA myDec+3
  STA player1ScoreDec+3
  LDA myDec+4
  STA player1ScoreDec+4

  LDA #TRUE
  STA backgroundNeedsRedrawn
  STA scoresNeedRedrawn
.MoveBallRightDone  
  RTS

MoveBallLeft:
  LDA ballInMotion
  CMP #FALSE
  BEQ .MoveBallLeftDone
  LDA ballGoingLeft
  BEQ .MoveBallLeftDone
  LDA ballX
  SEC 
  SBC ballSpeedX
  BCC .hitTheWall
  STA ballX
  CMP #LEFTWALL
  BCS .MoveBallLeftDone
.hitTheWall

  ;Reposition ball (give to P2)
  LDA paddle2YTop
  CLC
  ADC #SPRITE_SIZE * 2
  TAY
  LDA #PADDLE2X
  SEC
  SBC #SPRITE_SIZE
  TAX
  STY ballY
  STX ballX

  ;Set movement flags
  LDA #FALSE
  STA ballInMotion
  STA ballInP1Control
  LDA #TRUE
  STA ballInP2Control

  LDX #$0A ; 1000 points for ball
  JSR AddToP2Score

  ;Convert to decimal
  LDA player2Score
  STA myWord
  LDA player2Score+1
  STA myWord+1

  JSR HexWordToUnsignedDecimal

  LDA myDec
  STA player2ScoreDec
  LDA myDec+1
  STA player2ScoreDec+1
  LDA myDec+2
  STA player2ScoreDec+2
  LDA myDec+3
  STA player2ScoreDec+3
  LDA myDec+4
  STA player2ScoreDec+4


  LDA #TRUE
  STA backgroundNeedsRedrawn
  STA scoresNeedRedrawn

  ;award p2 points
.MoveBallLeftDone
  RTS

MoveBallUp:
  LDA ballInMotion
  CMP #FALSE
  BEQ .MoveBallUpDone
  LDA ballGoingUp
  BEQ .MoveBallUpDone

  LDA ballY
  SEC 
  SBC ballSpeedY
  BCC .hitTheCeiling
  STA ballY
  CMP #TOPWALL  ;if ballY > top wall, nothing changes
  BCS .MoveBallUpDone

.hitTheCeiling
  LDA #TOPWALL
  STA ballY
  LDA #TRUE    ;otherwise, reverse vertical direction
  STA ballGoingDown
  LDA #FALSE
  STA ballGoingUp
.MoveBallUpDone
  RTS

MoveBallDown:
  LDA ballInMotion
  CMP #FALSE
  BEQ .MoveBallDownDone
  LDA ballGoingDown
  BEQ .MoveBallDownDone

  LDA ballY
  CLC
  ADC ballSpeedY
  BCS .hitTheFloor

  STA ballY
  CMP #BOTTOMWALL ;if bally < bottom wall, nothing changes
  BCC .MoveBallDownDone

.hitTheFloor
  LDA #BOTTOMWALL
  STA ballY
  LDA #TRUE ;otherwise, reverse vertical direction
  STA ballGoingUp
  LDA #FALSE
  STA ballGoingDown
.MoveBallDownDone
  RTS

;Handle moving paddles
MovePaddle1Up:
  Test_Button buttonsP1, BUTTON_UP ;sets Z flag accordingly
  BEQ .MovePaddle1UpDone ;if button not pressed, skip section

  LDA paddle1YTop
  CMP #TOPWALL ;if paddle top <= topwall, can't move it up
  BEQ .moveBall
  BCC .readjust

  ;subtract from the paddle's y coord (top and bottom) to move it up
  LDA paddle1YTop
  SEC
  SBC paddle1Speed
  STA paddle1YTop
  LDA paddle1YBot
  SEC
  SBC paddle1Speed
  STA paddle1YBot
  LDA paddle1YTop
  CMP #TOPWALL
  BEQ .moveBall
  BCC .readjust
  JMP .moveBall
.readjust ;paddletop was < topwall, so move it back, first find the offset (topwall - paddletop)
  LDA #TOPWALL
  SEC 
  SBC paddle1YTop
  STA tempByte ;tempByte is now the offset, now add it to paddle1 top and bottom
  LDA paddle1YTop
  CLC
  ADC tempByte
  STA paddle1YTop
  LDA paddle1YBot
  CLC
  ADC tempByte
  STA paddle1YBot

  ;move the ball sprite if it's held by paddle 1
.moveBall
  LDA ballInMotion
  CMP #FALSE
  BNE .MovePaddle1UpDone
  LDA ballInP1Control
  CMP #TRUE
  BNE .MovePaddle1UpDone
  LDA paddle1YTop
  CLC
  ADC #SPRITE_SIZE * 2
  TAY
  LDA #PADDLE1X
  CLC
  ADC #SPRITE_SIZE
  TAX
  STY ballY
  STX ballX
.MovePaddle1UpDone
  RTS

MovePaddle1Down:
  Test_Button buttonsP1, BUTTON_DOWN
  BEQ .MovePaddle1DownDone ;if button wasn't pressed, skip
  LDA paddle1YBot
  CMP #BOTTOMWALL ;if paddle bottom >= bottom wall, we can't move it down
  BCS .readjust

  ;add to paddle's y coord 
  LDA paddle1YTop
  CLC
  ADC paddle1Speed
  STA paddle1YTop
  LDA paddle1YBot
  CLC
  ADC paddle1Speed
  STA paddle1YBot
  CMP #BOTTOMWALL
  BCS .readjust
  JMP .moveBall
.readjust ;paddle bottom > bottom wall, so move it back.  But by how much?
  LDA paddle1YBot
  SEC
  SBC #BOTTOMWALL
  STA tempByte      ;tempByte = paddle1YBot - BOTTOMWALL
  LDA paddle1YBot
  SEC
  SBC tempByte
  STA paddle1YBot
  LDA paddle1YTop
  SEC
  SBC tempByte
  STA paddle1YTop

  ;move the ball sprite if it's held by paddle 1
.moveBall
  LDA ballInMotion
  CMP #FALSE
  BNE .MovePaddle1DownDone
  LDA ballInP1Control
  CMP #TRUE
  BNE .MovePaddle1DownDone
  LDA paddle1YTop
  CLC
  ADC #SPRITE_SIZE * 2
  TAY
  LDA #PADDLE1X
  CLC
  ADC #SPRITE_SIZE
  TAX
  STY ballY
  STX ballX
.MovePaddle1DownDone
  RTS

MovePaddle2Up:
  Test_Button buttonsP2, BUTTON_UP
  BEQ .MovePaddle2UpDone ;if button not pressed, skip section
  LDA paddle2YTop
  CMP #TOPWALL ;if paddle top <= topwall, we can't move it up
  BEQ .moveBall
  BCC .readjust

  ;subtract from the paddle's y coord (top and bottom) to move it up
  LDA paddle2YTop
  SEC
  SBC paddle2Speed
  STA paddle2YTop
  LDA paddle2YBot
  SEC
  SBC paddle2Speed
  STA paddle2YBot
  LDA paddle2YTop
  CMP #TOPWALL
  BEQ .moveBall
  BCC .readjust
  JMP .moveBall
.readjust ;paddletop was < topwall, so move it back, first find the offset (topwall - paddletop)
  LDA #TOPWALL
  SEC 
  SBC paddle2YTop
  STA tempByte ;tempByte is now the offset, now add it to paddle1 top and bottom
  LDA paddle2YTop
  CLC
  ADC tempByte
  STA paddle2YTop
  LDA paddle2YBot
  CLC
  ADC tempByte
  STA paddle2YBot

;move the ball sprite if it's held by paddle 2
.moveBall
  LDA ballInMotion
  CMP #FALSE
  BNE .MovePaddle2UpDone
  LDA ballInP2Control
  CMP #TRUE
  BNE .MovePaddle2UpDone
  LDA paddle2YTop
  CLC
  ADC #SPRITE_SIZE * 2
  TAY
  LDA #PADDLE2X
  SEC
  SBC #SPRITE_SIZE
  TAX
  STY ballY
  STX ballX
.MovePaddle2UpDone
  RTS

MovePaddle2Down:
  Test_Button buttonsP2, BUTTON_DOWN
  BEQ .MovePaddle2DownDone ;if button wasn't pressed, skip
  LDA paddle2YBot
  CMP #BOTTOMWALL ;if paddle bottom >= bottom wall, we can't move it down
  BCS .readjust

  ;add to paddle's y coord 
  LDA paddle2YTop
  CLC
  ADC paddle2Speed
  STA paddle2YTop
  LDA paddle2YBot
  CLC
  ADC paddle2Speed
  STA paddle2YBot
  CMP #BOTTOMWALL
  BCS .readjust
  JMP .moveBall
.readjust ;paddle bottom > bottom wall, so move it back.  But by how much?
  LDA paddle2YBot
  SEC
  SBC #BOTTOMWALL
  STA tempByte      ;tempByte = paddle1YBot - BOTTOMWALL
  LDA paddle2YBot
  SEC
  SBC tempByte
  STA paddle2YBot
  LDA paddle2YTop
  SEC
  SBC tempByte
  STA paddle2YTop

;move the ball sprite if it's held by paddle 2
.moveBall
  LDA ballInMotion
  CMP #FALSE
  BNE .MovePaddle2DownDone
  LDA ballInP2Control
  CMP #TRUE
  BNE .MovePaddle2DownDone
  LDA paddle2YTop
  CLC
  ADC #SPRITE_SIZE * 2
  TAY
  LDA #PADDLE2X
  SEC
  SBC #SPRITE_SIZE
  TAX
  STY ballY
  STX ballX
.MovePaddle2DownDone
  RTS


;Add the byte in X register to p1Score
AddToP1Score:
  STX tempByte
  LDA player1Score
  CLC
  ADC tempByte
  STA player1Score
  BCC .done
  LDA player1Score+1
  CLC
  ADC #$01
  STA player1Score+1
.done
  LDA player1Score+1
  CMP #$27
  BCS .checkLower
  JMP .notMax
.checkLower
  LDA player1Score
  CMP #$0F
  BCS .setMax
  JMP .notMax
.setMax
  LDA #$0F
  STA player1Score
  LDA #$27
  STA player1Score+1
.notMax
  RTS

;Add the byte in X register to p2Score
AddToP2Score:
  STX tempByte
  LDA player2Score
  CLC
  ADC tempByte
  STA player2Score
  BCC .done
  LDA player2Score+1
  CLC
  ADC #$01
  STA player2Score+1
.done
  LDA player2Score+1
  CMP #$27
  BCS .checkLower
  JMP .notMax
.checkLower
  LDA player2Score
  CMP #$0F
  BCS .setMax
  JMP .notMax
.setMax
  LDA #$0F
  STA player2Score
  LDA #$27
  STA player2Score+1
.notMax
  RTS

DrawTimer:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (TimerTiles - Nametable_Playing))
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (TimerTiles - Nametable_Playing)) 
  STA $2006

  LDA timerDec
  STA $2007
  LDA timerDec+1
  STA $2007


DrawP1Score:
  ;Draw player1Score
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (Player1ScoreTiles - Nametable_Playing))
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (Player1ScoreTiles - Nametable_Playing))
  STA $2006

  LDA player1ScoreDec
  STA myDec
  LDA player1ScoreDec+1
  STA myDec+1
  LDA player1ScoreDec+2
  STA myDec+2
  LDA player1ScoreDec+3
  STA myDec+3
  LDA player1ScoreDec+4
  STA myDec+4

;replace leading 0's with blank spaces
  LDX #$00
.loop
  LDA myDec, X
  CMP #$00
  BNE .loopDone
  LDA #BLANK_TILE_BG
  STA myDec, X
  INX
  CPX #$05
  BNE .loop
.loopDone

  LDA myDec+1
  STA $2007
  LDA myDec+2
  STA $2007
  LDA myDec+3
  STA $2007
  LDA player1ScoreDec+4 ;always taking the last to get 3 0s
  STA $2007

  RTS

DrawP2Score:
  LDA $2002
  LDA #high(GRAPHICS_LOCATION + (Player2ScoreTiles - Nametable_Playing))
  STA $2006
  LDA #low(GRAPHICS_LOCATION + (Player2ScoreTiles - Nametable_Playing))
  STA $2006

  LDA player2ScoreDec
  STA myDec
  LDA player2ScoreDec+1
  STA myDec+1
  LDA player2ScoreDec+2
  STA myDec+2
  LDA player2ScoreDec+3
  STA myDec+3
  LDA player2ScoreDec+4
  STA myDec+4

;replace leading 0's with blank spaces
  LDX #$00
.loop
  LDA myDec, X
  CMP #$00
  BNE .loopDone
  LDA #BLANK_TILE_BG
  STA myDec, X
  INX
  CPX #$05
  BNE .loop
.loopDone

  LDA myDec+1
  STA $2007
  LDA myDec+2
  STA $2007
  LDA myDec+3
  STA $2007
  LDA player2ScoreDec+4 ;taking the last for three 0's
  STA $2007

  RTS


;--------------------------------------------------;
;                Common Routines                   ;
;--------------------------------------------------;
vblankwait:
  BIT $2002
  BPL vblankwait
  RTS

;Load the 0th palette according to the palettePtr
LoadPalette:
  LDA $2002               ;read PPU status to reset the high/low latch 
  LDA #$3F 
  STA $2006             
  LDA #$00 
  STA $2006             

;Set the palette
  LDA gamestate           
  ASL A                   ;multiply by 2 because the address is 2 bytes
  TAY
  LDA PalettePointerTable, Y    
  STA tempWord
  LDA PalettePointerTable+1, Y  
  STA tempWord+1  
  
;Load the palette
  LDY #$00         
.loadPaletteLoop: 
  LDA [tempWord], Y    
  STA $2007               ;write to PPU 
  INY
  CPY #$20               
  BNE .loadPaletteLoop 
  RTS

LoadSprites:
  LDX #$00              ; start at 0
.loop
  LDA Sprites, x        ; load data from address (Sprites +  x)
  STA SPRITE_LOCATION, x          ; store into RAM address ($0200 + x)
  INX                   ; X = X + 1
  CPX #(Sprites_end - Sprites)              ; load all of the Sprites
  BNE .loop   ; Branch to LoadSpritesLoop if compare was Not Equal to zero
                        ; if compare was equal to 16, keep going down
  RTS

HideAllSprites: ;move their vertical attribute to FE?
  LDA #(Sprites_end - Sprites)
  LSR A
  LSR A ;divide by 4
  STA tempByte ;tempByte = number of sprites 

  LDX #$00
  LDY #$00
  LDA #$FE
.loop
  CPX tempByte
  BEQ .done
  STA SPRITE_LOCATION, Y
  INY
  INY
  INY
  INY
  INX
  JMP .loop
.done


  RTS


;Load the 0th Nametable according to the nametablePtr
LoadNametable:
  LDA $2002     ;read PPU status to reset the high/low latch
  LDA #$20
  STA $2006
  LDA #$00
  STA $2006

;Set the nametable
  LDA gamestate
  ASL A
  TAY
  LDA NametablePointerTable, Y
  STA tempWord
  LDA NametablePointerTable+1, Y
  STA tempWord+1

  LDX #$04
  LDY #$00

;Load the nametable (change this, attribute is being set unnecessarily)
.loadNametableLoop:
  LDA [tempWord], Y              ;load nametable
  STA $2007                      ;draw tile
  INY                            
  BNE .loadNametableLoop
  INC tempWord+1
  DEX
  BNE .loadNametableLoop
  RTS

;Load the Attribute according to attributePtr
LoadAttribute:
  LDA $2002     ;read PPU status to reset the high/low latch
  LDA #$23 
  STA $2006
  LDA #$C0 
  STA $2006   ;put nametable (960 bytes) at 2000 in loadbackground
          ;put 64 bytes of attribute table at 2000 + 3C0 = 23C0

  LDA gamestate
  ASL A
  TAY
  LDA AttributePointerTable, Y
  STA tempWord
  LDA AttributePointerTable+1, Y
  STA tempWord+1

  LDY #$00 
.loadAttributeLoop:  ;
  LDA [tempWord], y
  STA $2007 
  INY
  CPY #$40 
  BNE .loadAttributeLoop
  RTS


LatchControllers:
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016       ; tell both the controllers to latch buttons
  RTS

ReadControllerState:
  JSR LatchControllers

  LDA buttonsP1
  STA buttonsP1+1
  ;Read Controller 1
  LDX #$08
.loop1
  LDA $4016
  LSR A
  ROL buttonsP1
  DEX
  BNE .loop1

  LDA buttonsP2
  STA buttonsP2+1
  ;Read Controller 2
  LDX #$08
.loop2
  LDA $4017
  LSR A
  ROL buttonsP2
  DEX
  BNE .loop2
  RTS


;converts the myWord variable into decimal, stores into myDec variable
;myWord is 2 bytes, little endian
;myDec has 5 bytes, one for each digit, big endian
HexWordToUnsignedDecimal: 
  LDX #FALSE      ;init carry to false
  STX tempCarry
  LDA myWord+1    ;Take the highest order hex digit (i.e. A in #$ABCD)
  AND #$F0        ;Mask the lower nybble and shift right 4 times
  LSR A
  LSR A
  LSR A
  LSR A
  STA tempByte    ;Store the value in tempByte - this is used as the table index

;Find the correct index into the FortyNinetySixDigit table
  LDA #$00        
  LDX #$00        
  CPX tempByte    ;If the hex digit is 0, we're done.  Otherwise iterate.
  BEQ .findFortyNinetySixIndexDone
.findFortyNinetySixIndex
  CLC
  ADC #$05
  INX
  CPX tempByte
  BNE .findFortyNinetySixIndex
.findFortyNinetySixIndexDone
  TAY             ;Now Y contains the index into the table.

;Initialize return value digits with FortyNinetySixDigit table values
  LDA FortyNinetySixDigit, Y  ;ones digit from table
  STA myDec+4                 ;store in the rightmost byte
  INY
  LDA FortyNinetySixDigit, Y  ;tens digit
  STA myDec+3
  INY
  LDA FortyNinetySixDigit, Y  ;hundreds digit
  STA myDec+2
  INY
  LDA FortyNinetySixDigit, Y  ;thousands digit
  STA myDec+1
  INY
  LDA FortyNinetySixDigit, Y  ;ten thousands digit
  STA myDec

  LDA myWord+1        ;Take the 2nd-highest order hex digit (i.e. B in #$ABCD)
  AND #$0F            ;Mask the upper nybble
  STA tempByte

;Find the correct index into the TwoFiftySixDigit table
  LDX #$00
  LDA #$00
  CPX tempByte
  BEQ .findTwoFiftySixIndexDone
.findTwoFiftySixIndex
  CLC
  ADC #$05
  INX
  CPX tempByte
  BNE .findTwoFiftySixIndex
.findTwoFiftySixIndexDone
  TAY

;Recalculate.  Add these digits to the ones previously calculated.
;Recalculate ones digit
  LDA TwoFiftySixDigit, Y   ;Load the ones digit from the 256 table
  CLC
  ADC tempCarry             ;Add carry (this could be removed)
  ADC myDec+4               ;Add the previous decimal digit value
  CMP #$0A                  ;If it's < 10, we don't need to carry
  BCC .onesNoCarry256
  LDX #TRUE                 ;Otherwise set carry to true
  STX tempCarry
  SEC
  SBC #$0A                  ;And subtract 10 from this digit
  JMP .onesDone256 
.onesNoCarry256
  LDX #FALSE
  STX tempCarry
.onesDone256
  STA myDec+4

  INY
;Recalculate tens digit
  LDA TwoFiftySixDigit, Y
  CLC
  ADC tempCarry
  ADC myDec+3
  CMP #$0A
  BCC .tensNoCarry256
  LDX #TRUE
  STX tempCarry
  SEC
  SBC #$0A
  JMP .tensDone256
.tensNoCarry256
  LDX #FALSE
  STX tempCarry
.tensDone256
  STA myDec+3

  INY
;Recalculate hundreds digit
  LDA TwoFiftySixDigit, Y
  CLC
  ADC tempCarry
  ADC myDec+2
  CMP #$0A
  BCC .hundredsNoCarry256
  LDX #TRUE
  STX tempCarry
  SBC #$0A
  JMP .hundredsDone256
.hundredsNoCarry256
  LDX #FALSE
  STX tempCarry
.hundredsDone256
  STA myDec+2

  INY
;Recalculate thousands
  LDA TwoFiftySixDigit, Y
  CLC
  ADC tempCarry
  ADC myDec+1
  CMP #$0A
  BCC .thousandsNoCarry256
  LDX #TRUE
  STX tempCarry
  SBC #$0A
  JMP .thousandsDone256
.thousandsNoCarry256
  LDX #FALSE
  STX tempCarry
.thousandsDone256
  STA myDec+1

  INY
;Recalculate tenthousands
  LDA TwoFiftySixDigit, Y
  CLC
  ADC tempCarry
  ADC myDec
  CMP #$0A                      ;This should never happen, so...
  BCC .tenthousandsNoCarry256   ;The check to set carry here could be removed
  LDX #TRUE                     ;And instead we could always set it to FALSE
  STX tempCarry
  SBC #$0A
  JMP .tenthousandsDone256
.tenthousandsNoCarry256
  LDX #FALSE
  STX tempCarry
.tenthousandsDone256
  STA myDec
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  LDA myWord        ;Take the 2nd-lowest order hex digit (i.e. C in #$ABCD)
  AND #$F0
  LSR A
  LSR A
  LSR A
  LSR A
  STA tempByte

;Find the correct index into the SixteensDigit table
  LDX #$00
  LDA #$00
  CPX tempByte
  BEQ .findSixteensIndexDone
.findSixteensIndex 
  CLC
  ADC #$05
  INX
  CPX tempByte
  BNE .findSixteensIndex
.findSixteensIndexDone
  TAY

;Recalculate.  Add these digits to the ones previously calculated.
;Recalculate ones digit
  LDX #FALSE
  STX tempCarry

  LDA SixteensDigit, Y
  CLC
  ADC tempCarry ; should be 0 though
  ADC myDec+4
  CMP #$0A
  BCC .onesNoCarry16
  LDX #TRUE
  STX tempCarry
  SEC
  SBC #$0A
  JMP .onesDone16
.onesNoCarry16
  LDX #FALSE
  STX tempCarry
.onesDone16
  STA myDec+4

  INY
;Recalculate tens digit
  LDA SixteensDigit, Y
  CLC
  ADC tempCarry
  ADC myDec+3
  CMP #$0A
  BCC .tensNoCarry16
  LDX #TRUE
  STX tempCarry
  SEC
  SBC #$0A
  JMP .tensDone16
.tensNoCarry16
  LDX #FALSE
  STX tempCarry
.tensDone16
  STA myDec+3

  INY
;Recalculate hundreds digit
  LDA SixteensDigit, Y
  CLC
  ADC tempCarry
  ADC myDec+2
  CMP #$0A
  BCC .hundredsNoCarry16
  LDX #TRUE
  STX tempCarry
  SBC #$0A
  JMP .hundredsDone16
.hundredsNoCarry16
  LDX #FALSE
  STX tempCarry
.hundredsDone16
  STA myDec+2

  INY
;Recalculate thousands
  LDA SixteensDigit, Y
  CLC
  ADC tempCarry
  ADC myDec+1
  CMP #$0A
  BCC .thousandsNoCarry16
  LDX #TRUE
  STX tempCarry
  SBC #$0A
  JMP .thousandsDone16
.thousandsNoCarry16
  LDX #FALSE
  STX tempCarry
.thousandsDone16
  STA myDec+1

  INY
;Recalculate tenthousands
  LDA SixteensDigit, Y
  CLC
  ADC tempCarry
  ADC myDec
  CMP #$0A
  BCC .tenthousandsNoCarry16    ;Similar situation here, should never happen
  LDX #TRUE                     ;So we could always set it to FALSE
  STX tempCarry
  SBC #$0A
  JMP .tenthousandsDone16
.tenthousandsNoCarry16
  LDX #FALSE
  STX tempCarry
.tenthousandsDone16
  STA myDec
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Take the lowest order hex digit (i.e. D in #$ABCD)
  LDA myWord
  AND #$0F
  STA tempByte

;Find the correct index into the OnesDigit table
  LDX #$00
  LDA #$00
  CPX tempByte
  BEQ .findOnesIndexDone
.findOnesIndex 
  CLC
  ADC #$05
  INX
  CPX tempByte
  BNE .findOnesIndex
.findOnesIndexDone
  TAY

;Recalculate.  Add these digits to the ones previously calculated.
;Recalculate ones digit
  LDX #FALSE
  STX tempCarry

  LDA OnesDigit, Y
  CLC
  ADC tempCarry ; should be 0 though
  ADC myDec+4
  CMP #$0A
  BCC .onesNoCarry1
  LDX #TRUE
  STX tempCarry
  SEC
  SBC #$0A
  JMP .onesDone1
.onesNoCarry1
  LDX #FALSE
  STX tempCarry
.onesDone1
  STA myDec+4

  INY
;Recalculate tens digit
  LDA OnesDigit, Y
  CLC
  ADC tempCarry
  ADC myDec+3
  CMP #$0A
  BCC .tensNoCarry1
  LDX #TRUE
  STX tempCarry
  SEC
  SBC #$0A
  JMP .tensDone1
.tensNoCarry1
  LDX #FALSE
  STX tempCarry
.tensDone1
  STA myDec+3

  INY
;Recalculate hundreds digit
  LDA OnesDigit, Y
  CLC
  ADC tempCarry
  ADC myDec+2
  CMP #$0A
  BCC .hundredsNoCarry1
  LDX #TRUE
  STX tempCarry
  SBC #$0A
  JMP .hundredsDone1
.hundredsNoCarry1
  LDX #FALSE
  STX tempCarry
.hundredsDone1
  STA myDec+2

  INY
;Recalculate thousands
  LDA OnesDigit, Y
  CLC
  ADC tempCarry
  ADC myDec+1
  CMP #$0A
  BCC .thousandsNoCarry1
  LDX #TRUE
  STX tempCarry
  SBC #$0A
  JMP .thousandsDone1
.thousandsNoCarry1
  LDX #FALSE
  STX tempCarry
.thousandsDone1
  STA myDec+1

  INY
;Recalculate tenthousands
  LDA OnesDigit, Y
  CLC
  ADC tempCarry
  ADC myDec
  CMP #$0A                    ;Could completely ignore the carry here
  BCC .tenthousandsNoCarry1   ;It won't happen and won't be used again.
  LDX #TRUE
  STX tempCarry
  SBC #$0A                      
  JMP .tenthousandsDone1
.tenthousandsNoCarry1
  LDX #FALSE
  STX tempCarry
.tenthousandsDone1
  STA myDec
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  RTS

OnesDigit:
  ;ones, tens, hundreds, thousands, tenthousands
  .db $00,$00,$00,$00,$00 ;#$0000 = 0
  .db $01,$00,$00,$00,$00 ;#$0001 = 1
  .db $02,$00,$00,$00,$00 ;#$0002 = 2
  .db $03,$00,$00,$00,$00 ;#$0003 = 3
  .db $04,$00,$00,$00,$00 ;#$0004 = 4
  .db $05,$00,$00,$00,$00 ;#$0005 = 5
  .db $06,$00,$00,$00,$00 ;#$0006 = 6
  .db $07,$00,$00,$00,$00 ;#$0007 = 7
  .db $08,$00,$00,$00,$00 ;#$0008 = 8
  .db $09,$00,$00,$00,$00 ;#$0009 = 9
  .db $00,$01,$00,$00,$00 ;#$000A = 10
  .db $01,$01,$00,$00,$00 ;#$000B = 11
  .db $02,$01,$00,$00,$00 ;#$000C = 12
  .db $03,$01,$00,$00,$00 ;#$000D = 13
  .db $04,$01,$00,$00,$00 ;#$000E = 14
  .db $05,$01,$00,$00,$00 ;#$000F = 15

SixteensDigit:
  ;ones, tens, hundreds, thousands, tenthousands
  .db $00,$00,$00,$00,$00 ;#$0000 = 0
  .db $06,$01,$00,$00,$00 ;#$0010 = 16
  .db $02,$03,$00,$00,$00 ;#$0020 = 32
  .db $08,$04,$00,$00,$00 ;#$0030 = 48
  .db $04,$06,$00,$00,$00 ;#$0040 = 64
  .db $00,$08,$00,$00,$00 ;#$0050 = 80
  .db $06,$09,$00,$00,$00 ;#$0060 = 96
  .db $02,$01,$01,$00,$00 ;#$0070 = 112
  .db $08,$02,$01,$00,$00 ;#$0080 = 128
  .db $04,$04,$01,$00,$00 ;#$0090 = 144
  .db $00,$06,$01,$00,$00 ;#$00A0 = 160
  .db $06,$07,$01,$00,$00 ;#$00B0 = 176
  .db $02,$09,$01,$00,$00 ;#$00C0 = 192
  .db $08,$00,$02,$00,$00 ;#$00D0 = 208
  .db $04,$02,$02,$00,$00 ;#$00E0 = 224
  .db $00,$04,$02,$00,$00 ;#$00F0 = 240

TwoFiftySixDigit:
  ;ones, tens, hundreds, thousands, tenthousands
  .db $00,$00,$00,$00,$00 ;#$0000 = 0
  .db $06,$05,$02,$00,$00 ;#$0100 = 256
  .db $02,$01,$05,$00,$00 ;#$0200 = 512
  .db $08,$06,$07,$00,$00 ;#$0300 = 768
  .db $04,$02,$00,$01,$00 ;#$0400 = 1024
  .db $00,$08,$02,$01,$00 ;#$0500 = 1280
  .db $06,$03,$05,$01,$00 ;#$0600 = 1536
  .db $02,$09,$07,$01,$00 ;#$0700 = 1792
  .db $08,$04,$00,$02,$00 ;#$0800 = 2048
  .db $04,$00,$03,$02,$00 ;#$0900 = 2304
  .db $00,$06,$05,$02,$00 ;#$0A00 = 2560
  .db $06,$01,$08,$02,$00 ;#$0B00 = 2816
  .db $02,$07,$00,$03,$00 ;#$0C00 = 3072
  .db $08,$02,$03,$03,$00 ;#$0D00 = 3328
  .db $04,$08,$05,$03,$00 ;#$0E00 = 3584
  .db $00,$04,$08,$03,$00 ;#$0F00 = 3840

FortyNinetySixDigit:
  ;ones, tens, hundreds, thousands, tenthousands
  .db $00,$00,$00,$00,$00 ;#$0000 = 0
  .db $06,$09,$00,$04,$00 ;#$1000 = 4096
  .db $02,$09,$01,$08,$00 ;#$2000 = 8192
  .db $08,$08,$02,$02,$01 ;#$3000 = 12288
  .db $04,$08,$03,$06,$01 ;#$4000 = 16384
  .db $00,$08,$04,$00,$02 ;#$5000 = 20480
  .db $06,$07,$05,$04,$02 ;#$6000 = 24576
  .db $02,$07,$06,$08,$02 ;#$7000 = 28672
  .db $08,$06,$07,$02,$03 ;#$8000 = 32768
  .db $04,$06,$08,$06,$03 ;#$9000 = 36864
  .db $00,$06,$09,$00,$04 ;#$A000 = 40960
  .db $06,$05,$00,$05,$04 ;#$B000 = 45056
  .db $02,$05,$01,$09,$04 ;#$C000 = 49152
  .db $08,$04,$02,$03,$05 ;#$D000 = 53248
  .db $04,$04,$03,$07,$05 ;#$E000 = 57344
  .db $00,$04,$04,$01,$06 ;#$F000 = 61440


;--------------------------------------------------;
;                     Bank 1                       ;
;--------------------------------------------------;
  .bank 1
  .org GRAPHICS_LOCATION

;--------------------------------------------------;
;                   Nametables                     ;
;--------------------------------------------------;
NametablePointerTable:
  .dw Nametable_Title     ;STATE_LOADTITLE
  .dw Nametable_Title     ;STATE_TITLE
  .dw Nametable_Playing   ;STATE_LOADPLAYING
  .dw Nametable_Playing   ;STATE_PLAYING
  .dw Nametable_Playing   ;STATE_P1PAUSE
  .dw Nametable_Playing   ;STATE_P2PAUSE
  .dw Nametable_GameOver  ;STATE_LOADGAMEOVER
  .dw Nametable_GameOver  ;STATE_GAMEOVER, qty 16
  ;...

Nametable_Title:	;qty 960
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$45,$45,$45,$45,$45,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$45,$24,$24,$45,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$45,$24,$24,$45,$24,$45,$45,$45,$45,$24,$24,$45,$45,$45,$24,$45,$45,$45,$24,$45,$45,$45,$45,$45,$24,$24,$24,$24
  .db $24,$24,$24,$24,$45,$24,$24,$45,$24,$45,$24,$45,$45,$24,$24,$45,$45,$45,$24,$24,$45,$24,$24,$45,$24,$24,$24,$45,$24,$24,$24,$24
  .db $24,$24,$24,$24,$45,$45,$45,$45,$24,$45,$24,$24,$45,$24,$24,$45,$24,$45,$24,$24,$45,$24,$45,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$45,$24,$24,$24,$24,$45,$24,$24,$45,$24,$24,$45,$24,$45,$45,$24,$45,$24,$45,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$45,$24,$24,$24,$24,$45,$24,$24,$45,$24,$24,$45,$24,$24,$45,$24,$45,$24,$45,$24,$24,$24,$45,$45,$45,$24,$24,$24
  .db $24,$24,$24,$24,$45,$24,$24,$24,$24,$45,$24,$24,$45,$24,$24,$45,$24,$24,$45,$45,$45,$24,$45,$45,$24,$24,$24,$45,$24,$24,$24,$24
  .db $24,$24,$24,$45,$45,$45,$24,$24,$24,$45,$45,$45,$45,$24,$45,$45,$45,$24,$45,$45,$45,$24,$24,$45,$45,$45,$45,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$02,$00,$01,$06,$24,$24,$24,$24	;24->25
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$19,$1B,$0E,$1C,$1C,$24,$1C,$1D,$0A,$1B,$1D,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $4B,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$50
  .db $4C,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$51 
  .db $24,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$24 
  .db $24,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$24 

Nametable_Playing: ;32 wide, 30 down, qty 66
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24
Player1TextTiles: .db $19,$15,$0A,$22,$0E,$1B,$24,$01	;qty 12
  .db $24,$24,$24,$24
TimerTextTiles: .db $1D,$12,$16,$0E	;qty 8
  .db $24,$24,$24,$24
Player2TextTiles: .db $19,$15,$0A,$22,$0E,$1B,$24,$02 ;qty 12
  .db $24,$24,$24,$24
Player1ScoreTiles: .db $24,$24,$24,$00,$00,$00 ;qty 13
  .db $24,$24,$24,$24,$24,$24,$24
TimerTiles: .db $09,$09 ;80, 81, qty 7
  .db $24,$24,$24,$24,$24
Player2ScoreTiles: .db $24,$24,$24,$00,$00,$00 ;qty 406
  .db $24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $6B,$2C,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6C,$6D,$6E,$6F
  .db $70,$2D,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$71,$72,$73,$74
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
PauseTiles: .db $24,$24,$24,$24,$24,$24,$24,$24 ;qty 436
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $4B,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$4D,$50
  .db $4C,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$4F,$51 
  .db $24,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$24 
  .db $24,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$52,$24 

Nametable_GameOver:
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
GameOverTiles: .db $10,$0A,$16,$0E,$24,$24,$18,$1F,$0E,$1B
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24
GameOverWinnerTiles: .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
  .db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24

;;;Attributes and Sprites removed due to maximum character limit

;--------------------------------------------------;
;                    Vectors                       ;
;--------------------------------------------------;
  .org $FFFA     ;first of the three vectors starts here
  .dw NMI        ;when an NMI happens (once per frame if enabled) 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 $0000          ;external interrupt IRQ is not used in this tutorial
  
;--------------------------------------------------;
;                     Bank 2                       ;
;--------------------------------------------------;
  .bank 2
  .org $0000
  .incbin "mario.chr"   ;includes 8KB graphics file from SMB1`

User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: Help understanding code that changes individual background tiles during gameplay

Post by Dwedit »

You have an address inside of video RAM (2000-2FFF)
Write it out in Binary.

You get something like this:
0010NNYYYYYXXXXX

Lowest 5 bits are a X coordinate (in tiles). The next 5 bits are a Y coordinate in tiles. The next 2 bits pick which nametable it goes to (0, 1, 2, 3). For vertical mirroring (horizontal arrangement) nametables 2 and 3 are the same as 0 and 1.

Also as a simple formula: VramAddress = TileX + TileY * 32 + NametableNumber * 0x400 + 0x2000

When writing to $2006, High address byte comes first rather than second.

The code has a lot of symbols and math done on them, but you should be looking at what number it actually evaluated into.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
justin-rwx
Posts: 14
Joined: Sun Jan 10, 2021 1:12 pm

Re: Help understanding code that changes individual background tiles during gameplay

Post by justin-rwx »

Dwedit wrote: Sat Sep 24, 2022 8:31 pm You have an address inside of video RAM (2000-2FFF)
Write it out in Binary.

You get something like this:
0010NNYYYYYXXXXX

Lowest 5 bits are a X coordinate (in tiles). The next 5 bits are a Y coordinate in tiles. The next 2 bits pick which nametable it goes to (0, 1, 2, 3). For vertical mirroring (horizontal arrangement) nametables 2 and 3 are the same as 0 and 1.

Also as a simple formula: VramAddress = TileX + TileY * 32 + NametableNumber * 0x400 + 0x2000

When writing to $2006, High address byte comes first rather than second.

The code has a lot of symbols and math done on them, but you should be looking at what number it actually evaluated into.
So GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing) represents an address in video RAM? Regarding that formula VramAddress = TileX + TileY * 32 + NametableNumber * 0x400 + 0x2000, I'm not sure I can relate that to the formula GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing), especially considering that GRAPHICS_LOCATION is $A000. When actually counting out the individual bytes in the nametables, GRAPHICS_LOCATION + (PauseTiles - Nametable_Playing) ends up being $A20C. I put this value directly into the code and it checks out, i.e. changes to the correct tiles are made in the background.

I'm just unable to unpack how exactly the code and emulator takes the address $A20C and knows the correct tiles to change on the screen.
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: Help understanding code that changes individual background tiles during gameplay

Post by Dwedit »

The topmost bit of a VRAM address is ignored, A20C is a mirror of 220C.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Post Reply