Pong tutorial failure

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Pong tutorial failure
by on (#97412)
I tried to finish bunnyboy's tutorial on nintendoage, but I had some issues.
After a game over the game shortly plays and crashes. I can't get the collision date to work either.

http://pastebin.com/5X1Us01D

I can't figure out either; I've been playing around with the collision data for hours. Please help.
Re: Pong tutorial failure
by on (#97422)
Ok, I figured out the freezing problem you are having. During your "EngineTitle" and "EngineGameOver" you created a loop that never executes the RTI instruction until you hit START so the NMI is still firing which will fill up the stack. Then when it overflows and then RTI is executed you are not returned to where you are expected to and the game freezes.

Since your NMI is called from an infinite loop the stack doesn't matter when calling it. So first, put three PLA's after the NMI call.
That seta the stack to back before the NMI was called.
Code:
NMI:
  PLA                ;<--------------ADD THESE
  PLA
  PLA
  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
  JSR DrawScore2


This also means the RTI instruction needs to go since it is dependent on the stack. Just have it go back to the infinite loop.

Code:
  JSR UpdateSprites  ;;set ball/paddle sprites from positions
 
  JSR MovePaddle1  ;;get the current button data for player 1
  JSR MovePaddle2  ;;get the current button data for player 2 
  JSR CheckPaddleCollision

  RTI             ; return from interrupt <---------REMOVE THIS LINE
  JMP Forever ;<---------ADD THIS LINE




And the freezing problems should go away.
Re: Pong tutorial failure
by on (#97483)
Wow, thanks man. That fixed the freezing problem. Now I can't figure out why the collision won't work. The code works if I comment off parts ex. leave only the if greater than PADDLE1X part. If I leave in all the comparisons the code won't work :/.
Re: Pong tutorial failure
by on (#97568)
Ok I solved the collision problem.

Your second and third compares were reversed the idea is to skip the collision handling if NOT within the paddle's range (this is a VERY common error that I still find myself falling for).

Also, the value of paddle1y2 being only 8 higher that paddle1y makes the paddle collision area 9 pixels. Subtracting 8 from bally for the comparison fixes that but you should find a better way of doing it.
Code:
CheckPaddleCollision:
  LDA ballx
  CMP #PADDLE1X+8
  BCS CheckPaddleCollisionDone      ;;if ball x > left wall, still on screen, skip next section  <--BCC changed to BCS   
  LDA bally
  CMP paddle1y
  BCC CheckPaddleCollisionDone      ;;if ball y > top wall, still on screen, skip next section <--BCS changed to BCC   
  LDA bally

  sec                                  <-- Fixes the width of the paddle so the ball collides with the whole thing
  sbc #$08                          <-- otherwise it would pass through the bottom half (effectively the same as adding 8 to paddle1y2)

  CMP paddle1y2
  BCS CheckPaddleCollisionDone      ;;if ball y < bottom wall, still on screen, skip next section 


All in all, the collision should work.
Re: Pong tutorial failure
by on (#97590)
The ball acts spasticly and bounces below the paddle. Urgh...

Code:
CheckPaddleCollision:
  LDA ballx
  SEC
  SBC #$08 
  CMP #PADDLE1X
  BCS CheckPaddleCollisionDone      ;;if ball x > left wall, still on screen, skip next section  <--BCC changed to BCS   
  LDA bally 
  CLC
  ADC #$08
  CMP paddle1y
  BCC CheckPaddleCollisionDone      ;;if ball y > top wall, still on screen, skip next section <--BCS changed to BCC   
;  LDA bally

  sec                              ;    <-- Fixes the width of the paddle so the ball collides with the whole thing
  sbc #$08                      ;    <-- otherwise it would pass through the bottom half (effectively the same as adding 8 to paddle1y2)
;  STA bally

  CMP paddle1y2
  BCS CheckPaddleCollisionDone      ;;if ball y < bottom wall, still on screen, skip next section 
  LDA #$01
  STA ballright 
  LDA #$00
  STA ballleft         ;;bounce, ball now moving right
  JMP CheckPaddleCollisionDone
CheckPaddleCollisionDone:


This seems to work better, but I don't understand why. What was wrong with the hitboxes?

Finished:

http://pastebin.com/cZAUUTfH

Any tips for optimization?
Re: Pong tutorial failure
by on (#97607)
After looking through your code I figured that i should optimize it as much as possible. That way you can look through it and see what kind of optimizations can be done and compare it to your original code.

Code:
  .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
balldirection   .rs 1 ;direction the ball moves
ballspeedx .rs 1  ; ball horizontal speed per frame
ballspeedy .rs 1  ; ball vertical speed per frame
paddle1y      .rs 1  ; paddle vertical position
paddle2y      .rs 1  ; paddle vertical position
buttons1   .rs 1  ; player 1 gamepad buttons, one bit per button
buttons2   .rs 1  ; player 2 gamepad buttons, one bit per button
scoreOnes     .rs 1  ; byte for each digit in the decimal score
scoreTens     .rs 1
scoreHundreds .rs 1
scoreOnes2     .rs 1  ; byte for each digit in the decimal score
scoreTens2     .rs 1
scoreHundreds2 .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      = $F4  ; when ball reaches one of these, do something
TOPWALL        = $20
BOTTOMWALL     = $E0
LEFTWALL       = $04
 
PADDLE1X       = $08  ; horizontal position for paddles, doesnt move
PADDLE2X       = $F0
PADDLESPEED   = $04
PADDLELENGTH    = $10
INITALBALLSPEED = $02

A_BUTTON = %10000000
B_BUTTON = %01000000
SELECT_BUTTON = %00100000
START_BUTTON = %00010000
UP_BUTTON = %00001000
DOWN_BUTTON = %00000100
LEFT_BUTTON = %00000010
RIGHT_BUTTON = %00000001
;;;;;;;;;;;;;;;;;;

  .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

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

vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2


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

ClearNametable:      ;writes #$24 to the nametable 1024 times to make a clear screen
  LDX #$00      ;X is for the first loop
  LDY #$04      ;Y is for the second loop
  LDA $2002
  LDA #$20
  STA $2006
  LDA #$00
  STA $2006      ;set PPU to $2000
  LDA #$24
ClearNametableLoop:
  STA $2007      ;store the value
  INX
  BNE ClearNametableLoop ;this loop runs 256 times
  DEY
  BNE ClearNametableLoop ;this loop runs 4 times

  LDA #$50              ;Set some initial ball stats
  STA bally 
  LDA #$80
  STA ballx
 
  LDA #INITALBALLSPEED   ;initalize ball speed
  STA ballspeedx
  STA ballspeedy

  LDA #$50      ;initalize paddle positions
  STA paddle1y
  STA paddle2y
 
  LDA #STATETITLE   ;Set starting game state
  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 #$02
  STA $4014       ; set the high byte (02) of the RAM address, start the transfer

  JSR DrawScore
  JSR DrawScore2

  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 ReadController1  ;;get the current button data for player 1
  JSR ReadController2  ;;get the current button data for player 2
   
GameEngine:        ;Game state selection (later you might want to make each state a subroutine)
  LDA gamestate
  CMP #STATETITLE
  BEQ EngineTitle    ;;game is displaying title screen
  CMP #STATEPLAYING
  BEQ EnginePlaying   ;;game is playing   
  CMP #STATEGAMEOVER
  BEQ EngineGameOver  ;;game is displaying ending screen
GameEngineDone:
  JSR UpdateSprites    ;this is best done every frame
  RTI
 

EngineTitle:   
        LDA buttons1      ;checks if start button was pressed
        AND #START_BUTTON
        BEQ EngineTitleBranch
        lda #STATEPLAYING   ;sets new state if pressed
   sta gamestate
EngineTitleBranch:
   jmp GameEngineDone   ;goes back either way

EngineGameOver:         ;similar to engine title but will reset values after start is pressed
        LDA buttons1
        AND #START_BUTTON
        BEQ EngineGameOverBranch
        lda #STATEPLAYING
   sta gamestate
   lda #$00
   STA scoreOnes      ;clears scores
   STA scoreTens
   STA scoreHundreds
   STA scoreOnes2
   STA scoreTens2
   STA scoreHundreds2
   LDA #$50      ;resets paddles
   STA paddle1y
   STA paddle2y
EngineGameOverBranch:
   jmp GameEngineDone   ;have all of your game states go back to somewhere that the rti triggers

EnginePlaying:
   jsr MovePaddle1      ;the playing engine
   jsr MovePaddle2
   jsr MoveBall
   jsr CheckBallCollision
   jsr CheckPaddleCollision
     jmp GameEngineDone

MovePaddle1:         ;moves the paddle up or down
   lda buttons1
   and #UP_BUTTON
   beq CheckPaddle1Down   ;check if up button pressed otherwise check down pressed
   lda paddle1y      ;subtracts paddlespeed to move it up
   sec
   sbc #PADDLESPEED
   cmp #TOPWALL      ;if it passed through the top wall, fix it to the top wall, otherwise nevermind
   bcs Paddle1NotTop
   lda #TOPWALL
Paddle1NotTop:
   sta paddle1y
   rts
CheckPaddle1Down:
   lda buttons1      ;same check as above
   and #DOWN_BUTTON
   beq CheckPaddle1Done
   lda paddle1y      ;moves the paddle down
   clc
   adc #PADDLESPEED
   cmp #BOTTOMWALL-16   ;checks the bottom wall the same way (the -16 is due to the paddles coordinate being at the top of the paddle)
   bcc Paddle1NotBottom
   lda #BOTTOMWALL-16
Paddle1NotBottom:
   sta paddle1y
CheckPaddle1Done:   
   rts

MovePaddle2:         ;this is identical to above but with paddle 2 instead
   lda buttons2
   and #UP_BUTTON
   beq CheckPaddle2Down
   lda paddle2y
   sec
   sbc #PADDLESPEED
   cmp #TOPWALL
   bcs Paddle2NotTop
   lda #TOPWALL
Paddle2NotTop:
   sta paddle2y
   rts
CheckPaddle2Down:
   lda buttons2
   and #DOWN_BUTTON
   beq CheckPaddle2Done
   lda paddle2y
   clc
   adc #PADDLESPEED
   cmp #BOTTOMWALL-16
   bcc Paddle2NotBottom
   lda #BOTTOMWALL-16
Paddle2NotBottom:
   sta paddle2y
CheckPaddle2Done:   
   rts

MoveBall:         ;this routine simply moves that ball
   lda balldirection   ;ball direction is now 1 byte, bit 0 is the X direction, bit 1 is the y direction (way more efficent)
   and #%00000001      ;teste the x direction (0 for right, 1 for left)
   bne BallMoveRight
   lda ballx      ;moves the ball accordingly
   clc
   adc ballspeedx
   sta ballx
   jmp CheckBallY      ;afterwards ckeck the y direction
BallMoveRight:         ;same as above but in the other direction
   lda ballx
   sec
   sbc ballspeedx
   sta ballx
CheckBallY:         ;same as above but for Y
   lda balldirection   
   and #%00000010      ;checks the y direction (bit 0 for down, bit 1 for up)
   bne BallMoveUp
   lda bally
   clc
   adc ballspeedy
   sta bally
   jmp MoveBallDone
BallMoveUp:         
   lda bally
   sec
   sbc ballspeedy
   sta bally
MoveBallDone:   
   rts

CheckBallCollision:      ;this checks the four sides of the screen
   lda bally
   cmp #TOPWALL      ;checks the top wall (similar to the paddles)
   bcs CheckBottomWall
   lda #TOPWALL      ;fixes the ball's y position to the edge of the wall
   sta bally
   lda balldirection
   eor #%00000010      ;switches the ball's y direction by toggling the bit
   sta balldirection
   jmp CheckLeftWall
CheckBottomWall:
   cmp #BOTTOMWALL-8   ;same as top wall
   bcc CheckLeftWall
   lda #BOTTOMWALL-8
   sta bally
   lda balldirection
   eor #%00000010
   sta balldirection
CheckLeftWall:         ;checks the side wall
   lda ballx
   cmp #LEFTWALL
   bcs CheckRightWall
   jsr IncrementScore2   ;if it hit the wall, increase the score
   lda #$80      ;then rest the ball's position
   sta ballx
   sta bally
   lda balldirection
   eor #%00000001      ;and change it's direction
   sta balldirection
   rts
CheckRightWall:         ;same as left wall
   cmp #RIGHTWALL
   bcc CheckBallCollisionDone
   jsr IncrementScore1
   lda #$80
   sta ballx
   sta bally
   lda balldirection
   eor #%00000001
   sta balldirection
CheckBallCollisionDone:
   rts

CheckPaddleCollision:      ;this checks if the ball hit a paddle
   lda ballx
   cmp #PADDLE1X+8      ;test if the ballx is within the paddle area
   bcs CheckOtherPaddle
   lda paddle1y      ;if so, test if bally is within the paddle's top coordinate
   sec
   sbc #$08      ;the sub 8 is to ensure that the bottom of the ball collides with the top of the paddle
   cmp bally
   bcs CheckOtherPaddle   ;if this fails go test the other paddle
   lda paddle1y
   clc
   adc #PADDLELENGTH   ;this tests the bottom of the paddle (16 is added to ensure that the top of the ball collides with the bottom of the paddle
   cmp bally
   bcc CheckOtherPaddle
   lda balldirection   ;if the tests were sucessful then flip the x direction of the ball
   eor #%00000001
   sta balldirection
   lda #PADDLE1X+8      ;and fix ballx to the edge of the paddle (otherwise causes wierd glitches)
   sta ballx
CheckOtherPaddle:      ;same as paddle 1
   lda ballx
   cmp #PADDLE2X-8
   bcc SkipCollision
   lda paddle2y
   sec
   sbc #$08
   cmp bally
   bcs SkipCollision
   lda paddle2y
   clc
   adc #PADDLELENGTH
   cmp bally
   bcc SkipCollision
   lda balldirection
   eor #%00000001
   sta balldirection
   lda #PADDLE2X-8
   sta ballx
SkipCollision:   
   rts

UpdateSprites:   ;all sprite update should be done in one pass
  LDA bally  ;ball
  STA $0200 
  LDA #$75
  STA $0201 
  LDA #$00
  STA $0202 
  LDA ballx
  STA $0203 
  LDA paddle1y ;left paddle top tile
  STA $0204
  LDA #$7F
  STA $0205 
  LDA #$00
  STA $0206
  LDA #PADDLE1X
  STA $0207
  LDA paddle1y ;left paddle bottom tile
  CLC
  ADC #$08     ;Add 8 to put it beneath the other tile
  STA $0208
  LDA #$7F
  STA $0209
  LDA #$00
  STA $020A
  LDA #PADDLE1X
  STA $020B
  LDA paddle2y ;right paddle top tile
  STA $020C
  LDA #$7F
  STA $020D 
  LDA #$00
  STA $020E
  LDA #PADDLE2X
  STA $020F
  LDA paddle2y ;right paddle bottom tile
  CLC
  ADC #$08     ;Add 8 to put it beneath the other tile
  STA $0210
  LDA #$7F
  STA $0211
  LDA #$00
  STA $0212
  LDA #PADDLE2X
  STA $0213 
  RTS
 
DrawScore:
  LDA $2002
  LDA #$20
  STA $2006
  LDA #$21
  STA $2006          ; start drawing the score at PPU $2021
  LDA scoreHundreds
  STA $2007
  LDA scoreTens
  STA $2007
  LDA scoreOnes
  STA $2007
  RTS
 
DrawScore2:
  LDA $2002
  LDA #$20
  STA $2006
  LDA #$3C
  STA $2006          ; start drawing the score at PPU $203C
  LDA scoreHundreds2
  STA $2007
  LDA scoreTens2
  STA $2007
  LDA scoreOnes2
  STA $2007
  RTS
 
IncrementScore1:   ;increases the score by one and sets the gameover flag if it hits 100
   inc scoreOnes
   lda scoreOnes
   cmp #$0A
   bne IncrementScore1Done
   lda #$00
   sta scoreOnes
   inc scoreTens
   lda scoreTens
   cmp #$0A
   bne IncrementScore1Done
   lda #$00
   sta scoreTens
   inc scoreHundreds
   lda #STATEGAMEOVER
   sta gamestate   
IncrementScore1Done:
   rts
       
IncrementScore2:
   inc scoreOnes2
   lda scoreOnes2
   cmp #$0A
   bne IncrementScore2Done
   lda #$00
   sta scoreOnes2
   inc scoreTens2
   lda scoreTens2
   cmp #$0A
   bne IncrementScore2Done
   lda #$00
   sta scoreTens2
   inc scoreHundreds2
   lda #STATEGAMEOVER
   sta gamestate   
IncrementScore2Done:
   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   

palette:
  .db $22,$29,$1A,$0F,  $22,$36,$17,$0F,  $22,$30,$21,$0F,  $22,$27,$17,$0F   ;;background palette
  .db $22,$1C,$15,$14,  $22,$02,$38,$3C,  $22,$1C,$15,$14,  $22,$02,$38,$3C   ;;sprite palette

  .bank 1
  .org $E000
  .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 0          ;external interrupt IRQ is not used in this tutorial
  .bank 2
  .org $0000
  .incbin "mario.chr"   ;includes 8KB graphics file from SMB
Re: Pong tutorial failure
by on (#97615)
Code:
.org $E000
.org $FFFA


That does nothing.

And you missed the biggest coding optimization of them all. Take all the NMI out of the NMI so replacing different NMI engines of your game is actually easy.. If you need example program I can show you.
Re: Pong tutorial failure
by on (#97673)
Thanks lazigamer I noticed I had a bit of unnecessary code in the checking scoring system. This will be helpful for future reference. Please do post 3gengames.
Re: Pong tutorial failure
by on (#97720)
I'm not sure if bunnyboy didn't want to teach signed numbers for a reason, but you would be a lot better of using signed values for moving the ball. You could reduce the size and complexity of the code that changes the ball's position:

Code:

MoveBall:         ;this routine simply moves that ball
   lda balldirection   ;ball direction is now 1 byte, bit 0 is the X direction, bit 1 is the y direction (way more efficent)
   and #%00000001      ;teste the x direction (0 for right, 1 for left)
   bne BallMoveRight
   lda ballx      ;moves the ball accordingly
   clc
   adc ballspeedx
   sta ballx
   jmp CheckBallY      ;afterwards ckeck the y direction
BallMoveRight:         ;same as above but in the other direction
   lda ballx
   sec
   sbc ballspeedx
   sta ballx
CheckBallY:         ;same as above but for Y
   lda balldirection   
   and #%00000010      ;checks the y direction (bit 0 for down, bit 1 for up)
   bne BallMoveUp
   lda bally
   clc
   adc ballspeedy
   sta bally
   jmp MoveBallDone
BallMoveUp:         
   lda bally
   sec
   sbc ballspeedy
   sta bally
MoveBallDone:   
   rts

Could be:
Code:
MoveBall:         ;this routine simply moves that ball
   lda ballx      ;moves the ball accordingly
   clc
   adc ballspeedx
   sta ballx
   lda bally
   clc
   adc ballspeedy
   sta bally
   rts


But you would have to change the code that checks collisions:
Code:

CheckBallCollision:      ;this checks the four sides of the screen
   lda bally
   cmp #TOPWALL      ;checks the top wall (similar to the paddles)
   bcs CheckBottomWall
   lda #TOPWALL      ;fixes the ball's y position to the edge of the wall
   sta bally
   lda ballspeedy
   clc         ;negate ballspeed
   eor #$FF           ;Invert all the bits
   adc #1   
   sta ballspeedy
   jmp CheckLeftWall
CheckBottomWall:
   cmp #BOTTOMWALL-8   ;same as top wall
   bcc CheckLeftWall
   lda #BOTTOMWALL-8
   sta bally
   lda ballspeedy
   clc         ;negate ballspeed
   eor #$FF           ;Invert all the bits
   adc #1   
   sta ballspeedy
CheckLeftWall:         ;checks the side wall
   lda ballx
   cmp #LEFTWALL
   bcs CheckRightWall
   jsr IncrementScore2   ;if it hit the wall, increase the score
   lda #$80      ;then rest the ball's position
   sta ballx
   sta bally
   lda ballspeedx
   clc         ;negate ballspeed
   eor #$FF           ;Invert all the bits
   adc #1   
   sta ballspeedx
   rts
CheckRightWall:         ;same as left wall
   cmp #RIGHTWALL
   bcc CheckBallCollisionDone
   jsr IncrementScore1
   lda #$80
   sta ballx
   sta bally
   lda ballspeedx
   clc         ;negate ballspeed
   eor #$FF           ;Invert all the bits
   adc #1   
   sta ballspeedx
CheckBallCollisionDone:
   rts

CheckPaddleCollision:      ;this checks if the ball hit a paddle
   lda ballx
   cmp #PADDLE1X+8      ;test if the ballx is within the paddle area
   bcs CheckOtherPaddle
   lda paddle1y      ;if so, test if bally is within the paddle's top coordinate
   sec
   sbc #$08      ;the sub 8 is to ensure that the bottom of the ball collides with the top of the paddle
   cmp bally
   bcs CheckOtherPaddle   ;if this fails go test the other paddle
   lda paddle1y
   clc
   adc #PADDLELENGTH   ;this tests the bottom of the paddle (16 is added to ensure that the top of the ball collides with the bottom of the paddle
   cmp bally
   bcc CheckOtherPaddle
   lda ballspeedx
   clc         ;negate ballspeed
   eor #$FF           ;Invert all the bits
   adc #1   
   sta ballspeedx
   lda #PADDLE1X+8      ;and fix ballx to the edge of the paddle (otherwise causes wierd glitches)
   sta ballx
CheckOtherPaddle:      ;same as paddle 1
   lda ballx
   cmp #PADDLE2X-8
   bcc SkipCollision
   lda paddle2y
   sec
   sbc #$08
   cmp bally
   bcs SkipCollision
   lda paddle2y
   clc
   adc #PADDLELENGTH
   cmp bally
   bcc SkipCollision
   lda ballspeedx
   clc         ;negate ballspeed
   eor #$FF           ;Invert all the bits
   adc #1   
   sta ballspeedx
   lda #PADDLE2X-8
   sta ballx
SkipCollision:   
   rts


You no longer need balldirection either I don't think. Google "two's complement" if you don't understand the changes.
Re: Pong tutorial failure
by on (#97913)
Why do you have to add after you invert the bits?
Re: Pong tutorial failure
by on (#97917)
#%00000001=1

When you reverse the bits to make it -1...

#%00000001=FE=-2.

So you add 1 to correct it.
Re: Pong tutorial failure
by on (#97921)
When learning binary most people learn the weight of the bits in decimal:

Code:
01010101

128, 64,  32, 16, 8, 4, 2, 1
  0  1    0    1  0  1  0  1 = 64 + 16 + 4 + 1 = 85


With a signed byte you can think of the 7th bit (on the left end) as a value of -128

Code:
11010101

128, 64, 32, 16, 8, 4, 2, 1
  1   1   0  1  0   1  0  1 = -128 + 64 + 16 + 4 + 1 = -43



So -1 is 11111111 (-128 + 127 = -1)

edit: That was maybe a bit confusing, fixed
Re: Pong tutorial failure
by on (#97928)
Ok thanks. My brain wanted to subtract :/. Hey 3gengames can you post an example of nmi switching?
Re: Pong tutorial failure
by on (#97929)
By "NMI switching" do you refer to having multiple NMI handlers?
Re: Pong tutorial failure
by on (#97934)
NMI:
INC Frame
RTI:
RTI

WaitForNMI:
LDA Frame
.Loop:
CMP Frame
BEQ .Loop
RTS

....somewhere in your game
Engine:
JSR WaitForNMI
...other stuff...
JMP Engine.


And just make as many "engines" as needed but remember the PPU code and stuff has to go in each one and might not all be the same.
Re: Pong tutorial failure
by on (#97936)
The solution that 3gengames gave works, and I use it in my own games. But it'll run into flicker problems once you start doing raster effects, such as scrolling with a still status bar.
Re: Pong tutorial failure
by on (#97959)
That is indeed the most straightforward way to make use of NMIs, but like tepples said, it might screw up raster effects (i.e. mid-screen changes to the scroll, palettes, etc.) in case your frame logic takes longer than the time of a NES frame (in most games that happens only when lots of objects are active at the same time). If you are sure your frame calculations will never take too long to finish (usually the case of most puzzle games and games with little variation in the number of on-screen objects), that NMI solution is safe to use.
Re: Pong tutorial failure
by on (#97980)
This isn't hard to change is it? You could have the NMI routine take care of screen updates and split screen and exit and return to the selected engine with the same basic structure. I'm actually working on this right now. This may be a bit much for the OP (not really that complex) but I use something like this:

Code:
main_jump_vector:     .res 3 ; 3 bytes for jmp (lo)(hi)

.proc reset

  standard_init     ;video + sound is off
   
  set_new_engine_state state_titlescreen     ; initial gamecode pointer
   
  set_PPU_CTRL CR_NMI,1     ; Turn on NMI before MainLoop, never turn it off (video still off)
   
  Mainloop:

   : bit NMIhit      ; Wait for a 
   bpl :-         ; NMI to occur
   clrflag NMIhit    ; before proceeding
      
   jsr main_jump_vector
      
 jmp Mainloop ;}
   
   
.endproc

; -----------------------------------------------

.proc   state_titlescreen
   [do stuff, then:]
   set_new_engine_state state_readyscreen
   rts
.endproc

.proc   state_readyscreen
   [do stuff, then:]
   set_new_engine_state state_gameplay
   rts
.endproc


.proc   state_gameplay
   [do stuff, then:]   
   set_new_engine_state state_gameover   
   rts
.endproc



.proc   state_gameover
        [do stuff, then:]   
   set_new_engine_state state_titlescreen
   rts
.endproc

;----------------------


.macro set_new_engine_state engine_state
   
   lda #_RTS_
   sta main_jump_vector
   lda #<(engine_state)
   sta main_jump_vector+1
   lda #>(engine_state)
   sta main_jump_vector+2
   lda #_JMP_
   sta main_jump_vector
   
.endmacro


I am working on it now, but I have decided that every engine should expect rendering to be off when started and should turn it off before changing to a new state - but no changes are made directly - nothing happens to the PPU side of the NES until NMI - it updates all changes based on flags marking data that is ready.