Lab 03 - Breakout Game

This lab is quite special compared to previous ones, as it is more subjective and taps into our creativity. We were tasked with creating any program we want, as long as it meets these criteria:

  • The program must work in the 6502 Emulator
  • It must output to the character screen as well as the graphics (bitmapped) screen.
  • It must accept user input from the keyboard in some form.
  • It must use some arithmetic/math instructions (to add, subtract, do bitwise operations, or rotate/shift)

Some suggestions given by our professor were to make either a simple game or a simple calculator/converter. With that in mind, I decided to remake the 1976 arcade game Breakout.

What is Breakout?

Based on its Wikipedia description, Breakout is a 1967 arcade game developed and published by Atari, Inc. It involves some bricks, a paddle, and a bouncing ball. The goal is to destroy all bricks with a bouncing ball, using the paddle as a sort of tool to keep the ball in play. You lose when the ball misses the paddle and escapes.

In the original version of game, it uses multiple rows of bricks with each brick corresponding to a certain amount of points when broken. The deeper the brick, the more points you get when you break it. The original game also gives you three chances to keep the ball in play. When the ball escapes the third time, you lose.

For my version, I'm deciding to keep things really simple by using only one row of bricks and giving the player only one chance to break them all. I will also remove the concept of points so that you only win by either breaking all the bricks or lose by allowing the ball to escape. This allows us to minimize complexity yet check all the criteria required by this lab.

My First Steps in Development

I started my development by doing some research about the game and looking up on tutorials on how to make the game in other programming languages. I needed a sort of guideline to follow so that I know the major concepts and building blocks that make up the game.

Some useful resources I found:

Through my research, I have come up with the following list of steps in order to create this breakout game:

  1. Develop keyboard controllable paddle
  2. Draw bricks onto screen
  3. Develop bouncing ball
  4. Making the ball break bricks
  5. Determining game end state
  6. Adding messages regarding game state

Develop keyboard controllable paddle

In order to create a keyboard controllable paddle, we need to be able to draw a paddle and update the its location depending on whether the user presses the left or right arrow keys.

Here is the pseudocode in order to do this:

Get base row address in screen
Add paddle position offset to base address
Draw paddle starting from (base address + offset)

Read one key input
 
If key input is left arrow key:
  Check if current paddle position is max left
  If it is max left:
    Do nothing and return
  If it is not max left:
    Clear the paddle (for next re-render)
    Decrement paddle position (for next re-render)

If key input is right arrow key:
  Check if current paddle position is max right
  If it is max right:
    Do nothing and return
  If it is not max right:
    Clear the paddle (for next re-render)
    Increment paddle position (for next re-render)

If key input is neither left nor right:
  Do nothing and return

Here is the pseudocode implemented in 6502 Assembly:

; ROM ROUTINES 
define CHRIN $ffcf ; input character from keyboard

; Constant variables
define PADCOLOR $03    ; Cyan

; Zero-page variables for paddle
define PADWIDTH $20 ; How long is the paddle
define PADPOS $21     ; How many pixels from the left to start rendering paddle
define PADMAXPOS $22 ; Maximum value for PADPOS
define PADPTR $23   ; Screen address (low byte)
define PADPTRH $24  ; Screen address (high byte)


INITIALIZE:     ; Runs once every new game
  LDA #8        ; Set paddle width to 8 pixels
  STA PADWIDTH
  LDA #24       ; Set paddle max pos to x=24
  STA PADMAXPOS
  LDA #12       ; Set paddle start pos at x=12
  STA PADPOS

GAME_LOOP:
  JSR UPDATE_PADDLE
  JMP GAME_LOOP


; ===============================================
; UPDATE_PADDLE: 
;     Subroutine for updating paddle location
;     based on user input
; ===============================================
UPDATE_PADDLE:
  LDA #$A0                ; Setup paddle base-row address ($05C0)
  STA PADPTR
  LDA #$05
  STA PADPTRH
  CLC                     ; Add x-pos to base-row to get our start address for drawing
  LDA PADPOS
  ADC PADPTR
  STA PADPTR
  LDA #$00
  ADC PADPTRH
  STA PADPTRH
  LDY #$00                ; Draw paddle from start address
  LDA #PADCOLOR
  DRAW_PADDLE:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE DRAW_PADDLE
  JSR CHRIN               ; Read key input
  CMP #$83                ; ascii for "left arrow key"
  BEQ MOVE_PADDLE_LEFT
  CMP #$81                ; ascii for "right"
  BEQ MOVE_PADDLE_RIGHT
  RTS                     ; If neither "left" nor "right, do nothing (return)
  MOVE_PADDLE_LEFT:
    LDA #0                ; MAX left-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX left, don't move left (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    DEC PADPOS            ; decrement paddle position
    RTS
  MOVE_PADDLE_RIGHT:
    LDA PADMAXPOS         ; MAX right-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX right, don't move right (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    INC PADPOS            ; increment paddle position
  END_UPDATE_PADDLE:
    RTS


; ===============================================
; CLEAR_PADDLE: 
;     Subroutine used by UPDATE_PADDLE to clear
;     the old paddle before rendering a new one
; ===============================================
CLEAR_PADDLE:
  LDY #$00
  LDA #$00
  CLEAR_LOOP:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE CLEAR_LOOP
  RTS

And here is the program output:

Draw bricks onto screen

For the bricks, all we'll do is render a single row with each pixel in the row representing a single brick. We'll have 26 bricks in total to leave some gaps on the left and right side.

For this, we need to create new variables for the brick information and add a new subroutine to draw the bricks:

; .. OTHER CODE ..

; Constant variables
define BRICK_COLOR $07 ; Yellow

; .. OTHER CODE ..

; Zero-page variables for bricks
define BRICKNUM $40

; .. OTHER CODE ..

INITIALIZE:
  LDA #26         ; Set num bricks to 26
  STA BRICKNUM
  JSR DRAW_BRICKS ; Draw bricks onto screen
  
; .. OTHER CODE ..

; ===============================================
; DRAW_BRICKS: 
;     Subroutine to draw the bricks on the screen
; ===============================================
DRAW_BRICKS:
  LDY #0
  LDA #BRICK_COLOR
  BRICK_LOOP: 
    STA $0263,Y
    INY
    CPY BRICKNUM
    BNE BRICK_LOOP
RTS

; .. OTHER CODE ..

Here it is implemented into our overall codebase:

; ROM ROUTINES 
define CHRIN $ffcf ; input character from keyboard

; Constant variables
define PADCOLOR $03    ; Cyan
define BRICKCOLOR $07 ; Yellow

; Zero-page variables for paddle
define PADWIDTH $20  ; How long is the paddle
define PADPOS $21    ; How many pixels from the left to start rendering paddle
define PADMAXPOS $22 ; Maximum value for PADPOS
define PADPTR $23    ; Screen address (low byte)
define PADPTRH $24   ; Screen address (high byte)

; Zero-page variables for bricks
define BRICKNUM $40


INITIALIZE:       ; Runs once every new game
  LDA #8          ; Set paddle width to 8 pixels
  STA PADWIDTH
  LDA #24         ; Set paddle max pos to x=24
  STA PADMAXPOS
  LDA #12         ; Set paddle start pos at x=12
  STA PADPOS
  LDA #26         ; Set num bricks to 26
  STA BRICKNUM
  JSR DRAW_BRICKS ; Draw bricks onto screen

GAME_LOOP:
  JSR UPDATE_PADDLE
  JMP GAME_LOOP


; ===============================================
; DRAW_BRICKS: 
;     Subroutine to draw the bricks on the screen
; ===============================================
DRAW_BRICKS:
  LDY #0
  LDA #BRICKCOLOR
  BRICK_LOOP: 
    STA $0263,Y
    INY
    CPY BRICKNUM
    BNE BRICK_LOOP
RTS


; ===============================================
; UPDATE_PADDLE: 
;     Subroutine for updating paddle location
;     based on user input
; ===============================================
UPDATE_PADDLE:
  LDA #$A0                ; Setup paddle base-row address ($05C0)
  STA PADPTR
  LDA #$05
  STA PADPTRH
  CLC                     ; Add x-pos to base-row to get our start address for drawing
  LDA PADPOS
  ADC PADPTR
  STA PADPTR
  LDA #$00
  ADC PADPTRH
  STA PADPTRH
  LDY #$00                ; Draw paddle from start address
  LDA #PADCOLOR
  DRAW_PADDLE:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE DRAW_PADDLE
  JSR CHRIN               ; Read key input
  CMP #$83                ; ascii for "left arrow key"
  BEQ MOVE_PADDLE_LEFT
  CMP #$81                ; ascii for "right"
  BEQ MOVE_PADDLE_RIGHT
  RTS                     ; If neither "left" nor "right, do nothing (return)
  MOVE_PADDLE_LEFT:
    LDA #0                ; MAX left-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX left, don't move left (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    DEC PADPOS            ; decrement paddle position
    RTS
  MOVE_PADDLE_RIGHT:
    LDA PADMAXPOS         ; MAX right-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX right, don't move right (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    INC PADPOS            ; increment paddle position
  END_UPDATE_PADDLE:
    RTS


; ===============================================
; CLEAR_PADDLE: 
;     Subroutine used by UPDATE_PADDLE to clear
;     the old paddle before rendering a new one
; ===============================================
CLEAR_PADDLE:
  LDY #$00
  LDA #$00
  CLEAR_LOOP:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE CLEAR_LOOP
  RTS

And here is the program output:

Develop bouncing ball

This where things get a bit complicated. We need to be able to animate a moving ball as well as detect collision with other objects in order to readjust its trajectory accordingly. With that said, here is the pseudocode that allows us to do just that.

Calculate ball's next x position based on current x direction

Check new x if it is border (-1 or 32)
If border:
  Bad! we have reached the border
  Switch x direction
  Keep current position, and go back to loop
If not border:
  Get address of adjacent pixel in x direction (new x, current y)
  Check value in address
  If address empty:
    Good! nothing is blocking us in x direction
    Calculate and check y
  If address not empty:
    Bad! something is blocking us in x direction
    Switch x direction
    Keep current position, and go back to loop

Calculate ball's next y position based on current y direction
Check new y if it is border (-1 or 32)
If border:
  Bad! we have reached the border
  Switch y direction
  Keep current position, and go back to loop
If not border:
  Get address of adjacent pixel in y direction (current x, new y)
  Check value in address
  If address empty:
    Good! nothing is blocking us in y direction
    Get address using new x and y and check if its valid
  If address not empty:
    Bad! something is blocking us in y direction
    Switch y direction
    Keep current position, and go back to loop

Get address using new x and y
Check value in address:
If address is empty:
  Great! we are safe to move in xy direction
  Fill the address with ball color
  Set new postion as the current postion
  Ball is now in new location, go back to loop
If address is not empty:
  Bad! something is blocking us in xy direction (diagonal)
  Switch x direction
  Switch y direction
  Keep current position, and go back to loop

To implement this code, we need to declare and initialize these new variables

; Constant variables
define BALLCOLOR $08 ; Orange

; Zero-page variables for ball
define BALLX $00       ; Current ball x position
define BALLY $01       ; Current ball y position
define NEWBALLX $02    ; New ball x position
define NEWBALLY $03    ; New ball y position
define BALLXMOV $04    ; How to change x position (+1 or -1)
define BALLYMOV $05    ; How to change y position (+1 or -1)
define BALLPTR $06     ; Pointer to ball address in screen (low byte)
define BALLPTRH $07    ; Pointer to ball address in screen (high byte)

; ... OTHER CODE ...
INITIALIZE:               ; Runs once every new game 
  LDA #16                 ; Set ball to start at x=16,y=28
  STA BALLX
  LDA #28
  STA BALLY
  LDA #$01                ; Set ball to move right (+1)
  STA BALLXMOV 
  LDA #$ff                ; Set ball to move up (-1)
  STA BALLYMOV
  LDX BALLX               ; Get ball address from xy-pos
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY 
  LDA ADDRESSPTR 
  STA BALLPTR
  LDA ADDRESSPTRH
  STA BALLPTRH
  LDY #$00                ; Draw ball at ball address
  LDA #BALLCOLOR
  STA (BALLPTR),Y
  
; ... OTHER CODE ...

Additionally, it would be useful to create a subroutine that gives us the memory address of a pixel on a the screen using xy coordinates.

; ===============================================
; GET_ADDRESS_FROM_XY: 
;     General subroutine used by UPDATE_BALL to 
;     get an address in the bitmap screen based on
;     xy coordinates
;
; Entry conditions:
;     Xreg - horizontal location in bitmap screen (0-31)
;     Yreg - vertical location in bitmap screen (0-31)
;
; Returns:
;     Zero-page pointer to address in bitmap
;       $00A0 - low byte of address in bitmap
;       $00A1 - high byte of address in bitmap
; ===============================================
; Zero-page variables used by this subroutine
define ADDRESSPTR $A0
define ADDRESSPTRH $A1

GET_ADDRESS_FROM_XY:
  STY ADDRESSPTR ; Add y-pos 
  LDA #$00
  STA ADDRESSPTRH 
  LDY #$05       ; Do 5 left shifts to multiply y-pos by 32
MULT_ADDR:
  ASL ADDRESSPTR 
  ROL ADDRESSPTRH 
  DEY
  BNE MULT_ADDR
  CLC            ; Add x-pos   
  TXA
  ADC ADDRESSPTR 
  STA ADDRESSPTR 
  LDA #$00
  ADC ADDRESSPTRH 
  STA ADDRESSPTRH 
  INC ADDRESSPTRH ; Add screen base Address of $0200
  INC ADDRESSPTRH  
  RTS

With all these in place, we can begin to implement the bouncing ball pseudocode into a subroutine called UPDATE_BALL.

; ===============================================
; UPDATE_BALL: 
;     Subroutine to update ball movement and
;     location based on the pixels ahead of the
;     direction its moving in.
;
;     This subroutine also updates the bricks and
;     brick count when the ball hits a brick
; ===============================================
UPDATE_BALL:
  LDA BALLX       ; Calculate next x-pos
  CLC
  ADC BALLXMOV
  STA NEWBALLX
  LDA NEWBALLX    ; Check next x-pos if border (-1 or 32)
  BMI SWITCH_X    ; If next x-pos is a border (-1), switch x-direction
  CMP #32
  BEQ SWITCH_X    ; If next x-pos is a border (32), switch x-direction
  JMP CHECK_X_ADJ ; If next x-pos is not a border, check the adjacent address in x-direction

SWITCH_X:      ; Switch x-direction, then we are done updating
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  RTS          ; Done updating

CHECK_X_ADJ:              ; Check adjacent address in x-direction
  LDX NEWBALLX            ; Get adjacent address in x-direction of ball (new-x, cur-y)
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y
  BEQ CHECK_Y_BORDER      ; If address is empty, we are good to move in this direction. Go check y
  JMP SWITCH_X            ; If address is not empty, Switch x-direction

CHECK_Y_BORDER:   ; Check next y-pos if border (-1 or 32)
  LDA BALLY       ; Calculate next y-pos
  CLC
  ADC BALLYMOV
  STA NEWBALLY
  LDA NEWBALLY
  BMI SWITCH_Y    ; If next y-pos is a border (-1), switch y-direction
  CMP #32
  BEQ SWITCH_Y    ; If next y-pos is a border (32), switch y-direction
  JMP CHECK_Y_ADJ ; If next y-pos is not a border, check the adjacent address in y-direction

SWITCH_Y:      ; Switch y-direction, then we are done updating
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

CHECK_Y_ADJ:              ; Check adjacent address in x-direction
  LDX BALLX               ; Get adjacent address in y-direction of ball (cur-x, new-y)
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y 
  BEQ CHECK_NEW_POS       ; If address is empty, we are good to move in this direction. Go check if new xy-pos is valid
  JMP SWITCH_Y            ; If address is not empty, Switch y-direction

CHECK_NEW_POS:              ; Check address in new-pos
  LDX NEWBALLX              ; Get new-pos address based on new-x and new-y
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY   ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                  ; Check value in new-pos
  LDA (ADDRESSPTR),Y
  BEQ SET_NEW_POS           ; If address is empty, we are good to move in this direction. Set and color this new-pos
  JMP SWITCH_X_Y            ; If address is not empty, switch both x and y direction

SET_NEW_POS:      ; Set new-pos as new ball position
  LDY #$00        ; Clear old ball
  LDA #$00
  STA (BALLPTR),Y
  LDA NEWBALLX    ; Copy new xy-pos to current xy-pos
  STA BALLX
  LDA NEWBALLY
  STA BALLY
  LDA ADDRESSPTR  ; Copy new pointer to current pointer
  STA BALLPTR
  LDA ADDRESSPTRH 
  STA BALLPTRH
  LDY #$00        ; Color ball at new pointer
  LDA #BALLCOLOR
  STA (BALLPTR),Y
  RTS             ; Done updating

SWITCH_X_Y:    ; Switch both x and y direction
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

We can now update our game loop to update the ball after updating our paddle, like so.

GAME_LOOP:
  JSR UPDATE_PADDLE
  JSR UPDATE_BALL
  JMP GAME_LOOP

Here is our code after all these changes.

; ROM ROUTINES 
define CHRIN $ffcf ; input character from keyboard

; Constant variables
define BALLCOLOR $08 ; Orange
define PADCOLOR $03    ; Cyan
define BRICKCOLOR $07 ; Yellow

; Zero-page variables for ball
define BALLX $00       ; Current ball x position
define BALLY $01       ; Current ball y position
define NEWBALLX $02    ; New ball x position
define NEWBALLY $03    ; New ball y position
define BALLXMOV $04    ; How to change x position (+1 or -1)
define BALLYMOV $05    ; How to change y position (+1 or -1)
define BALLPTR $06     ; Pointer to ball address in screen (low byte)
define BALLPTRH $07    ; Pointer to ball address in screen (high byte)

; Zero-page variables for paddle
define PADWIDTH $20  ; How long is the paddle
define PADPOS $21    ; How many pixels from the left to start rendering paddle
define PADMAXPOS $22 ; Maximum value for PADPOS
define PADPTR $23    ; Screen address (low byte)
define PADPTRH $24   ; Screen address (high byte)

; Zero-page variables for bricks
define BRICKNUM $40


INITIALIZE:       ; Runs once every new game

  LDA #26         ; Set num bricks to 26
  STA BRICKNUM
  JSR DRAW_BRICKS ; Draw bricks onto screen

  LDA #8          ; Set paddle width to 8 pixels
  STA PADWIDTH
  LDA #24         ; Set paddle max pos to x=24
  STA PADMAXPOS
  LDA #12         ; Set paddle start pos at x=12
  STA PADPOS
  JSR UPDATE_PADDLE ; Draw paddle

  LDA #16         ; Set ball to start at x=16,y=28
  STA BALLX
  LDA #28
  STA BALLY
  LDA #$01 ; +1   ; Set ball to move right-up
  STA BALLXMOV 
  LDA #$ff ; -1
  STA BALLYMOV
  LDX BALLX  ; Get ball address from xy-pos
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY
  LDA ADDRESSPTR 
  STA BALLPTR
  LDA ADDRESSPTRH
  STA BALLPTRH
  LDY #$00         ; Draw ball at ball address
  LDA #BALLCOLOR
  STA (BALLPTR),Y


GAME_LOOP:
  JSR UPDATE_PADDLE
  JSR UPDATE_BALL
  JMP GAME_LOOP


; ===============================================
; DRAW_BRICKS: 
;     Subroutine to draw the bricks on the screen
; ===============================================
DRAW_BRICKS:
  LDY #0
  LDA #BRICKCOLOR
  BRICK_LOOP: 
    STA $0263,Y
    INY
    CPY BRICKNUM
    BNE BRICK_LOOP
RTS


; ===============================================
; UPDATE_PADDLE: 
;     Subroutine for updating paddle location
;     based on user input
; ===============================================
UPDATE_PADDLE:
  LDA #$A0                ; Setup paddle base-row address ($05C0)
  STA PADPTR
  LDA #$05
  STA PADPTRH
  CLC                     ; Add x-pos to base-row to get our start address for drawing
  LDA PADPOS
  ADC PADPTR
  STA PADPTR
  LDA #$00
  ADC PADPTRH
  STA PADPTRH
  LDY #$00                ; Draw paddle from start address
  LDA #PADCOLOR
  DRAW_PADDLE:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE DRAW_PADDLE
  JSR CHRIN               ; Read key input
  CMP #$83                ; ascii for "left arrow key"
  BEQ MOVE_PADDLE_LEFT
  CMP #$81                ; ascii for "right"
  BEQ MOVE_PADDLE_RIGHT
  RTS                     ; If neither "left" nor "right, do nothing (return)
  MOVE_PADDLE_LEFT:
    LDA #0                ; MAX left-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX left, don't move left (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    DEC PADPOS            ; decrement paddle position
    RTS
  MOVE_PADDLE_RIGHT:
    LDA PADMAXPOS         ; MAX right-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX right, don't move right (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    INC PADPOS            ; increment paddle position
  END_UPDATE_PADDLE:
    RTS


; ===============================================
; CLEAR_PADDLE: 
;     Subroutine used by UPDATE_PADDLE to clear
;     the old paddle before rendering a new one
; ===============================================
CLEAR_PADDLE:
  LDY #$00
  LDA #$00
  CLEAR_LOOP:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE CLEAR_LOOP
  RTS


; ===============================================
; UPDATE_BALL: 
;     Subroutine to update ball movement and
;     location based on the pixels ahead of the
;     direction its moving in.
;
;     This subroutine also updates the bricks and
;     brick count when the ball hits a brick
; ===============================================
UPDATE_BALL:

  LDA BALLX       ; Calculate next x-pos
  CLC
  ADC BALLXMOV
  STA NEWBALLX
  LDA NEWBALLX    ; Check next x-pos if border (-1 or 32)
  BMI SWITCH_X    ; If next x-pos is a border (-1), switch x-direction
  CMP #32
  BEQ SWITCH_X    ; If next x-pos is a border (32), switch x-direction
  JMP CHECK_X_ADJ ; If next x-pos is not a border, check the adjacent address in x-direction

SWITCH_X:      ; Switch x-direction, then we are done updating
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  RTS          ; Done updating

CHECK_X_ADJ:              ; Check adjacent address in x-direction
  LDX NEWBALLX            ; Get adjacent address in x-direction of ball (new-x, cur-y)
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y
  BEQ CHECK_Y_BORDER      ; If address is empty, we are good to move in this direction. Go check y
  JMP SWITCH_X            ; If address is not empty, Switch x-direction

CHECK_Y_BORDER:   ; Check next y-pos if border (-1 or 32)
  LDA BALLY       ; Calculate next y-pos
  CLC
  ADC BALLYMOV
  STA NEWBALLY
  LDA NEWBALLY
  BMI SWITCH_Y    ; If next y-pos is a border (-1), switch y-direction
  CMP #32
  BEQ SWITCH_Y    ; If next y-pos is a border (32), switch y-direction
  JMP CHECK_Y_ADJ ; If next y-pos is not a border, check the adjacent address in y-direction

SWITCH_Y:      ; Switch y-direction, then we are done updating
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

CHECK_Y_ADJ:              ; Check adjacent address in x-direction
  LDX BALLX               ; Get adjacent address in y-direction of ball (cur-x, new-y)
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y 
  BEQ CHECK_NEW_POS       ; If address is empty, we are good to move in this direction. Go check if new xy-pos is valid
  JMP SWITCH_Y            ; If address is not empty, Switch y-direction

CHECK_NEW_POS:              ; Check address in new-pos
  LDX NEWBALLX              ; Get new-pos address based on new-x and new-y
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY   ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                  ; Check value in new-pos
  LDA (ADDRESSPTR),Y
  BEQ SET_NEW_POS           ; If address is empty, we are good to move in this direction. Set and color this new-pos
  JMP SWITCH_X_Y            ; If address is not empty, switch both x and y direction

SET_NEW_POS:      ; Set new-pos as new ball position
  LDY #$00        ; Clear old ball
  LDA #$00
  STA (BALLPTR),Y
  LDA NEWBALLX    ; Copy new xy-pos to current xy-pos
  STA BALLX
  LDA NEWBALLY
  STA BALLY
  LDA ADDRESSPTR  ; Copy new pointer to current pointer
  STA BALLPTR
  LDA ADDRESSPTRH  
  STA BALLPTRH
  LDY #$00        ; Color ball at new pointer
  LDA #BALLCOLOR
  STA (BALLPTR),Y
  RTS             ; Done updating

SWITCH_X_Y:    ; Switch both x and y direction
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

  
; ===============================================
; GET_ADDRESS_FROM_XY: 
;     General subroutine used by UPDATE_BALL to 
;     get an address in the bitmap screen based on
;     xy coordinates
;
; Entry conditions:
;     Xreg - horizontal location in bitmap screen (0-31)
;     Yreg - vertical location in bitmap screen (0-31)
;
; Returns:
;     Zero-page pointer to address in bitmap
;       $00A0 - low byte of address in bitmap
;       $00A1 - high byte of address in bitmap
; ===============================================
; Zero-page variables used by this subroutine
define ADDRESSPTR $A0
define ADDRESSPTRH $A1

GET_ADDRESS_FROM_XY:
  STY ADDRESSPTR ; Add y-pos 
  LDA #$00
  STA ADDRESSPTRH 
  LDY #$05       ; Do 5 left shifts to multiply y-pos by 32
MULT_ADDR:
  ASL ADDRESSPTR 
  ROL ADDRESSPTRH 
  DEY
  BNE MULT_ADDR
  CLC            ; Add x-pos   
  TXA
  ADC ADDRESSPTR 
  STA ADDRESSPTR 
  LDA #$00
  ADC ADDRESSPTRH 
  STA ADDRESSPTRH 
  INC ADDRESSPTRH ; Add screen base Address of $0200
  INC ADDRESSPTRH  
  RTS

And here is the output of the program:

Making the animation run smoother

We currently have a couple issues. When running the program at max speed, the ball moves too fast causing our paddle to miss way more than it can deflect. If we run our program at a slower speed the ball becomes easier to track, but our paddle becomes very flickery when it moves.

In order to solve this issue, we can try to update our paddle more times than our ball. That way, when we set the emulator to max speed, the paddle will be able to run smoother and the ball a lot slower.

To do this we need to declare a few looping variables.

; Zero-page variables for loops
define REPEAT $60
define REPEAT2 $61

And update the game loop like so.

GAME_LOOP:
  LDA #$00          ; Update the paddle more times than the ball so that the ball moves more slowly
  STA REPEAT       
  LDA #$03
  STA REPEAT2
PADDLE_LOOP:        ; Effectively, this updates the paddle 259 times before updating the ball
  JSR UPDATE_PADDLE
  DEC DELAY_VAR
  BNE PADDLE_LOOP
  DEC DELAY_VAR2
  BNE PADDLE_LOOP

  JSR UPDATE_BALL
  JMP GAME_LOOP

Here is our code with these new changes.

; ROM ROUTINES 
define CHRIN $ffcf ; input character from keyboard

; Constant variables
define BALLCOLOR $08 ; Orange
define PADCOLOR $03    ; Cyan
define BRICKCOLOR $07 ; Yellow

; Zero-page variables for ball
define BALLX $00       ; Current ball x position
define BALLY $01       ; Current ball y position
define NEWBALLX $02    ; New ball x position
define NEWBALLY $03    ; New ball y position
define BALLXMOV $04    ; How to change x position (+1 or -1)
define BALLYMOV $05    ; How to change y position (+1 or -1)
define BALLPTR $06     ; Pointer to ball address in screen (low byte)
define BALLPTRH $07    ; Pointer to ball address in screen (high byte)

; Zero-page variables for paddle
define PADWIDTH $20  ; How long is the paddle
define PADPOS $21    ; How many pixels from the left to start rendering paddle
define PADMAXPOS $22 ; Maximum value for PADPOS
define PADPTR $23    ; Screen address (low byte)
define PADPTRH $24   ; Screen address (high byte)

; Zero-page variables for bricks
define BRICKNUM $40

; Zero-page variables for loops
define REPEAT $60
define REPEAT2 $61


INITIALIZE:       ; Runs once every new game
  LDA #26         ; Set num bricks to 26
  STA BRICKNUM
  JSR DRAW_BRICKS ; Draw bricks onto screen

  LDA #8          ; Set paddle width to 8 pixels
  STA PADWIDTH
  LDA #24         ; Set paddle max pos to x=24
  STA PADMAXPOS
  LDA #12         ; Set paddle start pos at x=12
  STA PADPOS
  JSR UPDATE_PADDLE ; Draw paddle

  LDA #16         ; Set ball to start at x=16,y=28
  STA BALLX
  LDA #28
  STA BALLY
  LDA #$01 ; +1   ; Set ball to move right-up
  STA BALLXMOV 
  LDA #$ff ; -1
  STA BALLYMOV
  LDX BALLX  ; Get ball address from xy-pos
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY
  LDA ADDRESSPTR 
  STA BALLPTR
  LDA ADDRESSPTRH
  STA BALLPTRH
  LDY #$00         ; Draw ball at ball address
  LDA #BALLCOLOR
  STA (BALLPTR),Y


GAME_LOOP:
  LDA #$00          ; Update the paddle more times than the ball so that the ball moves more slowly
  STA REPEAT       
  LDA #$03
  STA REPEAT2
PADDLE_LOOP:        ; Effectively this updates the paddle 259 times before updating the ball
  JSR UPDATE_PADDLE
  DEC REPEAT       
  BNE PADDLE_LOOP
  DEC REPEAT2
  BNE PADDLE_LOOP

  JSR UPDATE_BALL
  JMP GAME_LOOP


; ===============================================
; DRAW_BRICKS: 
;     Subroutine to draw the bricks on the screen
; ===============================================
DRAW_BRICKS:
  LDY #0
  LDA #BRICKCOLOR
  BRICK_LOOP: 
    STA $0263,Y
    INY
    CPY BRICKNUM
    BNE BRICK_LOOP
RTS


; ===============================================
; UPDATE_PADDLE: 
;     Subroutine for updating paddle location
;     based on user input
; ===============================================
UPDATE_PADDLE:
  LDA #$A0                ; Setup paddle base-row address ($05C0)
  STA PADPTR
  LDA #$05
  STA PADPTRH
  CLC                     ; Add x-pos to base-row to get our start address for drawing
  LDA PADPOS
  ADC PADPTR
  STA PADPTR
  LDA #$00
  ADC PADPTRH
  STA PADPTRH
  LDY #$00                ; Draw paddle from start address
  LDA #PADCOLOR
  DRAW_PADDLE:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE DRAW_PADDLE
  JSR CHRIN               ; Read key input
  CMP #$83                ; ascii for "left arrow key"
  BEQ MOVE_PADDLE_LEFT
  CMP #$81                ; ascii for "right"
  BEQ MOVE_PADDLE_RIGHT
  RTS                     ; If neither "left" nor "right, do nothing (return)
  MOVE_PADDLE_LEFT:
    LDA #0                ; MAX left-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX left, don't move left (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    DEC PADPOS            ; decrement paddle position
    RTS
  MOVE_PADDLE_RIGHT:
    LDA PADMAXPOS         ; MAX right-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX right, don't move right (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    INC PADPOS            ; increment paddle position
  END_UPDATE_PADDLE:
    RTS


; ===============================================
; CLEAR_PADDLE: 
;     Subroutine used by UPDATE_PADDLE to clear
;     the old paddle before rendering a new one
; ===============================================
CLEAR_PADDLE:
  LDY #$00
  LDA #$00
  CLEAR_LOOP:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE CLEAR_LOOP
  RTS


; ===============================================
; UPDATE_BALL: 
;     Subroutine to update ball movement and
;     location based on the pixels ahead of the
;     direction its moving in.
;
;     This subroutine also updates the bricks and
;     brick count when the ball hits a brick
; ===============================================
UPDATE_BALL:

  LDA BALLX       ; Calculate next x-pos
  CLC
  ADC BALLXMOV
  STA NEWBALLX
  LDA NEWBALLX    ; Check next x-pos if border (-1 or 32)
  BMI SWITCH_X    ; If next x-pos is a border (-1), switch x-direction
  CMP #32
  BEQ SWITCH_X    ; If next x-pos is a border (32), switch x-direction
  JMP CHECK_X_ADJ ; If next x-pos is not a border, check the adjacent address in x-direction

SWITCH_X:      ; Switch x-direction, then we are done updating
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  RTS          ; Done updating

CHECK_X_ADJ:              ; Check adjacent address in x-direction
  LDX NEWBALLX            ; Get adjacent address in x-direction of ball (new-x, cur-y)
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y
  BEQ CHECK_Y_BORDER      ; If address is empty, we are good to move in this direction. Go check y
  JMP SWITCH_X            ; If address is not empty, Switch x-direction

CHECK_Y_BORDER:   ; Check next y-pos if border (-1 or 32)
  LDA BALLY       ; Calculate next y-pos
  CLC
  ADC BALLYMOV
  STA NEWBALLY
  LDA NEWBALLY
  BMI SWITCH_Y    ; If next y-pos is a border (-1), switch y-direction
  CMP #32
  BEQ SWITCH_Y    ; If next y-pos is a border (32), switch y-direction
  JMP CHECK_Y_ADJ ; If next y-pos is not a border, check the adjacent address in y-direction

SWITCH_Y:      ; Switch y-direction, then we are done updating
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

CHECK_Y_ADJ:              ; Check adjacent address in x-direction
  LDX BALLX               ; Get adjacent address in y-direction of ball (cur-x, new-y)
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y 
  BEQ CHECK_NEW_POS       ; If address is empty, we are good to move in this direction. Go check if new xy-pos is valid
  JMP SWITCH_Y            ; If address is not empty, Switch y-direction

CHECK_NEW_POS:              ; Check address in new-pos
  LDX NEWBALLX              ; Get new-pos address based on new-x and new-y
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY   ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                  ; Check value in new-pos
  LDA (ADDRESSPTR),Y
  BEQ SET_NEW_POS           ; If address is empty, we are good to move in this direction. Set and color this new-pos
  JMP SWITCH_X_Y            ; If address is not empty, switch both x and y direction

SET_NEW_POS:      ; Set new-pos as new ball position
  LDY #$00        ; Clear old ball
  LDA #$00
  STA (BALLPTR),Y
  LDA NEWBALLX    ; Copy new xy-pos to current xy-pos
  STA BALLX
  LDA NEWBALLY
  STA BALLY
  LDA ADDRESSPTR  ; Copy new pointer to current pointer
  STA BALLPTR
  LDA ADDRESSPTRH  
  STA BALLPTRH
  LDY #$00        ; Color ball at new pointer
  LDA #BALLCOLOR
  STA (BALLPTR),Y
  RTS             ; Done updating

SWITCH_X_Y:    ; Switch both x and y direction
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

  
; ===============================================
; GET_ADDRESS_FROM_XY: 
;     General subroutine used by UPDATE_BALL to 
;     get an address in the bitmap screen based on
;     xy coordinates
;
; Entry conditions:
;     Xreg - horizontal location in bitmap screen (0-31)
;     Yreg - vertical location in bitmap screen (0-31)
;
; Returns:
;     Zero-page pointer to address in bitmap
;       $00A0 - low byte of address in bitmap
;       $00A1 - high byte of address in bitmap
; ===============================================
; Zero-page variables used by this subroutine
define ADDRESSPTR $A0
define ADDRESSPTRH $A1

GET_ADDRESS_FROM_XY:
  STY ADDRESSPTR ; Add y-pos 
  LDA #$00
  STA ADDRESSPTRH 
  LDY #$05       ; Do 5 left shifts to multiply y-pos by 32
MULT_ADDR:
  ASL ADDRESSPTR 
  ROL ADDRESSPTRH 
  DEY
  BNE MULT_ADDR
  CLC            ; Add x-pos   
  TXA
  ADC ADDRESSPTR 
  STA ADDRESSPTR 
  LDA #$00
  ADC ADDRESSPTRH 
  STA ADDRESSPTRH 
  INC ADDRESSPTRH ; Add screen base Address of $0200
  INC ADDRESSPTRH  
  RTS

And here is the output of the program now:

Making the ball break bricks

To make the ball erase or "break" a brick whenever it hits one, we must update our UPDATE_BALL code such that when it detects a brick color in its immediate surrounding pixels, it erases the brick from that address and decrements the number of bricks before switching the xy directions.

We need to update the code at the CHECK_X_ADJ, CHECK_Y_ADJ, and CHECK_NEW_POS labels to do just that.

Here are the changes we would make to the code.

; ... OTHER CODE ...

CHECK_X_ADJ:              ; Check adjacent address in x-direction
  LDX NEWBALLX            ; Get adjacent address in x-direction of ball (new-x, cur-y)
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y
  BEQ CHECK_Y_BORDER      ; If address is empty, we are good to move in this direction. Go check y
  CMP #BRICKCOLOR         ; If address is not empty, check if this address contains a brick
  BNE SWITCH_X            ; If it is not a brick, go ahead to switch x-direction
  LDA #$00
  STA (ADDRESSPTR),Y      ; If it is a brick, erase the brick
  DEC BRICKNUM            ; Decrement brick count
  JMP SWITCH_X            ; Switch x-direction

; ... OTHER CODE ...

CHECK_Y_ADJ:              ; Check adjacent address in x-direction
  LDX BALLX               ; Get adjacent address in y-direction of ball (cur-x, new-y)
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y 
  BEQ CHECK_NEW_POS       ; If address is empty, we are good to move in this direction. Go check if new xy-pos is valid
  CMP #BRICKCOLOR         ; If address is not empty, check if this address contains a brick
  BNE SWITCH_Y            ; If it is not a brick, go ahead to switch x-direction
  LDA #$00
  STA (ADDRESSPTR),Y      ; If it is a brick, erase the brick
  DEC BRICKNUM            ; Decrement brick count
  JMP SWITCH_Y            ; Switch x-direction

CHECK_NEW_POS:              ; Check address in new-pos
  LDX NEWBALLX              ; Get new-pos address based on new-x and new-y
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY   ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                  ; Check value in new-pos
  LDA (ADDRESSPTR),Y
  BEQ SET_NEW_POS           ; If address is empty, we are good to move in this direction. Set and color this new-pos
  CMP #BRICKCOLOR           ; If address is not empty, check if this address contains a brick
  BNE SWITCH_Y              ; If it is not a brick, go ahead to switch x-direction
  LDA #$00
  STA (ADDRESSPTR),Y        ; If it is a brick, erase the brick
  DEC BRICKNUM              ; Decrement brick count
  JMP SWITCH_X_Y            ; Switch both x and y direction

Here is our code incorporating these new changes.

; ROM ROUTINES 
define CHRIN $ffcf ; input character from keyboard

; Constant variables
define BALLCOLOR $08 ; Orange
define PADCOLOR $03    ; Cyan
define BRICKCOLOR $07 ; Yellow

; Zero-page variables for ball
define BALLX $00       ; Current ball x position
define BALLY $01       ; Current ball y position
define NEWBALLX $02    ; New ball x position
define NEWBALLY $03    ; New ball y position
define BALLXMOV $04    ; How to change x position (+1 or -1)
define BALLYMOV $05    ; How to change y position (+1 or -1)
define BALLPTR $06     ; Pointer to ball address in screen (low byte)
define BALLPTRH $07    ; Pointer to ball address in screen (high byte)

; Zero-page variables for paddle
define PADWIDTH $20  ; How long is the paddle
define PADPOS $21    ; How many pixels from the left to start rendering paddle
define PADMAXPOS $22 ; Maximum value for PADPOS
define PADPTR $23    ; Screen address (low byte)
define PADPTRH $24   ; Screen address (high byte)

; Zero-page variables for bricks
define BRICKNUM $40

; Zero-page variables for loops
define REPEAT $60
define REPEAT2 $61


INITIALIZE:       ; Runs once every new game
  LDA #26         ; Set num bricks to 26
  STA BRICKNUM
  JSR DRAW_BRICKS ; Draw bricks onto screen
  LDA #8          ; Set paddle width to 8 pixels
  STA PADWIDTH
  LDA #24         ; Set paddle max pos to x=24
  STA PADMAXPOS
  LDA #12         ; Set paddle start pos at x=12
  STA PADPOS
  JSR UPDATE_PADDLE ; Draw paddle
  LDA #16         ; Set ball to start at x=16,y=28
  STA BALLX
  LDA #28
  STA BALLY
  LDA #$01 ; +1   ; Set ball to move right-up
  STA BALLXMOV 
  LDA #$ff ; -1
  STA BALLYMOV
  LDX BALLX  ; Get ball address from xy-pos
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY
  LDA ADDRESSPTR 
  STA BALLPTR
  LDA ADDRESSPTRH
  STA BALLPTRH
  LDY #$00         ; Draw ball at ball address
  LDA #BALLCOLOR
  STA (BALLPTR),Y


GAME_LOOP:
  LDA #$00          ; Update the paddle more times than the ball so that the ball moves more slowly
  STA REPEAT       
  LDA #$03
  STA REPEAT2
PADDLE_LOOP:        ; Effectively this updates the paddle 259 times before updating the ball
  JSR UPDATE_PADDLE
  DEC REPEAT       
  BNE PADDLE_LOOP
  DEC REPEAT2
  BNE PADDLE_LOOP

  JSR UPDATE_BALL
  JMP GAME_LOOP


; ===============================================
; DRAW_BRICKS: 
;     Subroutine to draw the bricks on the screen
; ===============================================
DRAW_BRICKS:
  LDY #0
  LDA #BRICKCOLOR
  BRICK_LOOP: 
    STA $0263,Y
    INY
    CPY BRICKNUM
    BNE BRICK_LOOP
RTS


; ===============================================
; UPDATE_PADDLE: 
;     Subroutine for updating paddle location
;     based on user input
; ===============================================
UPDATE_PADDLE:
  LDA #$A0                ; Setup paddle base-row address ($05C0)
  STA PADPTR
  LDA #$05
  STA PADPTRH
  CLC                     ; Add x-pos to base-row to get our start address for drawing
  LDA PADPOS
  ADC PADPTR
  STA PADPTR
  LDA #$00
  ADC PADPTRH
  STA PADPTRH
  LDY #$00                ; Draw paddle from start address
  LDA #PADCOLOR
  DRAW_PADDLE:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE DRAW_PADDLE
  JSR CHRIN               ; Read key input
  CMP #$83                ; ascii for "left arrow key"
  BEQ MOVE_PADDLE_LEFT
  CMP #$81                ; ascii for "right"
  BEQ MOVE_PADDLE_RIGHT
  RTS                     ; If neither "left" nor "right, do nothing (return)
  MOVE_PADDLE_LEFT:
    LDA #0                ; MAX left-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX left, don't move left (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    DEC PADPOS            ; decrement paddle position
    RTS
  MOVE_PADDLE_RIGHT:
    LDA PADMAXPOS         ; MAX right-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX right, don't move right (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    INC PADPOS            ; increment paddle position
  END_UPDATE_PADDLE:
    RTS


; ===============================================
; CLEAR_PADDLE: 
;     Subroutine used by UPDATE_PADDLE to clear
;     the old paddle before rendering a new one
; ===============================================
CLEAR_PADDLE:
  LDY #$00
  LDA #$00
  CLEAR_LOOP:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE CLEAR_LOOP
  RTS


; ===============================================
; UPDATE_BALL: 
;     Subroutine to update ball movement and
;     location based on the pixels ahead of the
;     direction its moving in.
;
;     This subroutine also updates the bricks and
;     brick count when the ball hits a brick
; ===============================================
UPDATE_BALL:

  LDA BALLX       ; Calculate next x-pos
  CLC
  ADC BALLXMOV
  STA NEWBALLX
  LDA NEWBALLX    ; Check next x-pos if border (-1 or 32)
  BMI SWITCH_X    ; If next x-pos is a border (-1), switch x-direction
  CMP #32
  BEQ SWITCH_X    ; If next x-pos is a border (32), switch x-direction
  JMP CHECK_X_ADJ ; If next x-pos is not a border, check the adjacent address in x-direction

SWITCH_X:      ; Switch x-direction, then we are done updating
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  RTS          ; Done updating

CHECK_X_ADJ:              ; Check adjacent address in x-direction
  LDX NEWBALLX            ; Get adjacent address in x-direction of ball (new-x, cur-y)
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y
  BEQ CHECK_Y_BORDER      ; If address is empty, we are good to move in this direction. Go check y
  CMP #BRICKCOLOR         ; If address is not empty, check if this address contains a brick
  BNE SWITCH_X            ; If it is not a brick, go ahead to switch x-direction
  LDA #$00
  STA (ADDRESSPTR),Y      ; If it is a brick, erase the brick
  DEC BRICKNUM            ; Decrement brick count
  JMP SWITCH_X            ; Switch x-direction

CHECK_Y_BORDER:   ; Check next y-pos if border (-1 or 32)
  LDA BALLY       ; Calculate next y-pos
  CLC
  ADC BALLYMOV
  STA NEWBALLY
  LDA NEWBALLY
  BMI SWITCH_Y    ; If next y-pos is a border (-1), switch y-direction
  CMP #32
  BEQ SWITCH_Y    ; If next y-pos is a border (32), switch y-direction
  JMP CHECK_Y_ADJ ; If next y-pos is not a border, check the adjacent address in y-direction

SWITCH_Y:      ; Switch y-direction, then we are done updating
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

CHECK_Y_ADJ:              ; Check adjacent address in x-direction
  LDX BALLX               ; Get adjacent address in y-direction of ball (cur-x, new-y)
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y 
  BEQ CHECK_NEW_POS       ; If address is empty, we are good to move in this direction. Go check if new xy-pos is valid
  CMP #BRICKCOLOR         ; If address is not empty, check if this address contains a brick
  BNE SWITCH_Y            ; If it is not a brick, go ahead to switch x-direction
  LDA #$00
  STA (ADDRESSPTR),Y      ; If it is a brick, erase the brick
  DEC BRICKNUM            ; Decrement brick count
  JMP SWITCH_Y            ; Switch x-direction

CHECK_NEW_POS:              ; Check address in new-pos
  LDX NEWBALLX              ; Get new-pos address based on new-x and new-y
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY   ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                  ; Check value in new-pos
  LDA (ADDRESSPTR),Y
  BEQ SET_NEW_POS           ; If address is empty, we are good to move in this direction. Set and color this new-pos
  CMP #BRICKCOLOR           ; If address is not empty, check if this address contains a brick
  BNE SWITCH_Y              ; If it is not a brick, go ahead to switch x-direction
  LDA #$00
  STA (ADDRESSPTR),Y        ; If it is a brick, erase the brick
  DEC BRICKNUM              ; Decrement brick count
  JMP SWITCH_X_Y            ; Switch both x and y direction

SET_NEW_POS:      ; Set new-pos as new ball position
  LDY #$00        ; Clear old ball
  LDA #$00
  STA (BALLPTR),Y
  LDA NEWBALLX    ; Copy new xy-pos to current xy-pos
  STA BALLX
  LDA NEWBALLY
  STA BALLY
  LDA ADDRESSPTR  ; Copy new pointer to current pointer
  STA BALLPTR
  LDA ADDRESSPTRH  
  STA BALLPTRH
  LDY #$00        ; Color ball at new pointer
  LDA #BALLCOLOR
  STA (BALLPTR),Y
  RTS             ; Done updating

SWITCH_X_Y:    ; Switch both x and y direction
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

  
; ===============================================
; GET_ADDRESS_FROM_XY: 
;     General subroutine used by UPDATE_BALL to 
;     get an address in the bitmap screen based on
;     xy coordinates
;
; Entry conditions:
;     Xreg - horizontal location in bitmap screen (0-31)
;     Yreg - vertical location in bitmap screen (0-31)
;
; Returns:
;     Zero-page pointer to address in bitmap
;       $00A0 - low byte of address in bitmap
;       $00A1 - high byte of address in bitmap
; ===============================================
; Zero-page variables used by this subroutine
define ADDRESSPTR $A0
define ADDRESSPTRH $A1

GET_ADDRESS_FROM_XY:
  STY ADDRESSPTR ; Add y-pos 
  LDA #$00
  STA ADDRESSPTRH 
  LDY #$05       ; Do 5 left shifts to multiply y-pos by 32
MULT_ADDR:
  ASL ADDRESSPTR 
  ROL ADDRESSPTRH 
  DEY
  BNE MULT_ADDR
  CLC            ; Add x-pos   
  TXA
  ADC ADDRESSPTR 
  STA ADDRESSPTR 
  LDA #$00
  ADC ADDRESSPTRH 
  STA ADDRESSPTRH 
  INC ADDRESSPTRH ; Add screen base Address of $0200
  INC ADDRESSPTRH  
  RTS

And here is the output of the program now:

Determining game end state

After updating our ball, we need to check the current state of the game and decide whether we win or we lose. To check if we win, we need to check the current brick count. If the brick count is zero, then we win. To check if we lose, we need to check the y position of the ball. If the ball is at y=31 then the ball has missed the paddle and escaped, so we lose.

We need to make these checks after updating our ball, like so.

GAME_LOOP:
  LDA #$00          ; Update the paddle more times than the ball so that the ball moves more slowly
  STA REPEAT       
  LDA #$03
  STA REPEAT2
PADDLE_LOOP:        ; Effectively this updates the paddle 259 times before updating the ball
  JSR UPDATE_PADDLE
  DEC REPEAT       
  BNE PADDLE_LOOP
  DEC REPEAT2
  BNE PADDLE_LOOP

  JSR UPDATE_BALL
  LDA #31
  CMP BALLY         ; Check ball position
  BEQ GAME_END      ; If ball hits the bottom edge, you lose

  LDA BRICKNUM      ; Check status of bricks
  BEQ GAME_END      ; If there are no more bricks, you win
  JMP GAME_LOOP

GAME_END:
  BRK

Here is the program output with these changes:

Pressing ENTER to START the game

Right now our game immediately starts when we run program; however, it would be better if we give the player a chance to prepare themselves first before starting the game. Let's make it so that we wait for the user to press "enter" before starting the game.

To do that we just need to implement another loop in between INITIALIZE and GAME_LOOP that constantly waits for the user to press "enter".

; ... AFTER INITIALIZE ...

INITIAL_LOOP:      ; Loop to wait for user input before proceeding to game loop
  JSR CHRIN        ; Read key input
  CMP #$0d         ; ascii for "carriage return"
  BNE INITIAL_LOOP

; ... BEFORE GAME_LOOP ...

Here is the program with these changes:

Pressing ENTER to RESTART the game

It would also be good if we allow the user to easily restart the game after it has ended. Let's make it so that after the game ends we wait for the user to press "enter" before restarting the game.

To do this, we need to implement a loop after the GAME_LOOP that constantly waits for the user to press "enter". When a user presses "enter", we should clear the bitmap screen and jump back to the INITIALIZE label.

GAME_LOOP:
  LDA #$00           ; Update the paddle more times than the ball so that the ball moves more slowly
  STA REPEAT       
  LDA #$03
  STA REPEAT2
PADDLE_LOOP:         ; Effectively this updates the paddle 259 times before updating the ball
  JSR UPDATE_PADDLE
  DEC REPEAT       
  BNE PADDLE_LOOP
  DEC REPEAT2
  BNE PADDLE_LOOP

  JSR UPDATE_BALL
  LDA #31
  CMP BALLY          ; Check ball position
  BEQ POST_GAME_LOOP      ; If ball hits the bottom edge, you lose

  LDA BRICKNUM       ; Check status of bricks
  BEQ POST_GAME_LOOP      ; If there are no more bricks, you win
  JMP GAME_LOOP

POST_GAME_LOOP: ; Wait for user to press enter before restarting game
  JSR CHRIN     ; Read key input
  CMP #$0d      ; ascii for "carriage return"
  BNE POST_GAME_LOOP
  JMP INITIALIZE

We also need to make sure that we clear the bitmap screen when we restart the game. Let's implement this at the very start of INITIALIZE.

INITIALIZE:        ; Runs once every new game
  LDA #$00         ; Clear bitmap screen
  LDY #$00
CLEAR_BITMAP: 
  STA $0200,y
  STA $0300,y
  STA $0400,y
  STA $0500,y
  INY
  BNE CLEAR_BITMAP
  
; ... OTHER CODE ...

Here is our program with these changes:

Adding messages regarding game state

Our game is now practically complete, however a player with no context would be lost and have no idea how to play the game. Let's make use of the text screen in order to inform the player about how to play the game and whether or not they have won.

To print a message to the text screen, we can create a subroutine called PRINT_MSG and provide to it the start address of a contiguous block of text and continuously prints a character from that text to the screen until it hits the null character.

; ===============================================
; PRINT_MSG:
;     General subroutine to print a message to
;     the text screen
;
; Entry conditions:
;     $00B0 - low byte pointer to message
;     $00B1 - high byte pointer to message
; ===============================================
PRINT_MSG:
  JSR SCINIT      ; Clear screen
  LDY #$00
PRINT_LOOP:    
  LDA ($b0),y
  BEQ DONE_PRINT
  JSR CHROUT      ; put a character on the screen
  INY
  BNE PRINT_LOOP
DONE_PRINT:
  RTS

We can define our messages at the very bottom of our program instructions like so.

; ===============================================
; OTHER DATA
; ===============================================
WELCOME_MSG:
  DCB 13
  DCB 32,32,"A","S","S","E","M","B","L","Y",32,"B","R","E","A","K","O","U","T",13
  DCB 13
  DCB 32,32,"U","s","e",32,"t","h","e",32,"l","e","f","t",32,"a","n","d",32,"r","i","g","h","t",32,"a","r","r","o","w",32,"k","e","y","s",32,"t","o",13
  DCB 32,32,"m","o","v","e",32,"t","h","e",32,"p","a","d","d","l","e",".",13
  DCB 13
  DCB 32,32,"P","r","e","s","s",32,"e","n","t","e","r",32,"t","o",32,"s","t","a","r","t",".",00

PLAY_MSG:
  DCB 13
  DCB 32,32,"A","S","S","E","M","B","L","Y",32,"B","R","E","A","K","O","U","T",13
  DCB 13
  DCB 32,32,"G","a","m","e",32,"s","t","a","r","t","e","d",".",00

WIN_MSG:
  DCB 13
  DCB 32,32,"A","S","S","E","M","B","L","Y",32,"B","R","E","A","K","O","U","T",13
  DCB 13
  DCB 32,32,"Y","o","u",32,"W","i","n","!",13
  DCB 13
  DCB 32,32,"P","r","e","s","s",32,"e","n","t","e","r",32,"t","o",32,"p","l","a","y",32,"a","g","a","i","n",".",00

LOSE_MSG:
  DCB 13
  DCB 32,32,"A","S","S","E","M","B","L","Y",32,"B","R","E","A","K","O","U","T",13
  DCB 13
  DCB 32,32,"Y","o","u",32,"L","o","s","e",".",".",".",13
  DCB 13
  DCB 32,32,"P","r","e","s","s",32,"e","n","t","e","r",32,"t","o",32,"p","l","a","y",32,"a","g","a","i","n",".",00

And use the subroutine like so.

  ; Print welcome message
  LDA #<WELCOME_MSG
  STA $b0
  LDA #>WELCOME_MSG
  STA $b1
  JSR PRINT_MSG

Let's print the WELCOME_MSG after initializing the variables and before the INITIAL_LOOP; print the PLAY_MSG after the INITIAL_LOOP and before the GAME_LOOP; print the LOSE_MSG when a player loses; and print the WIN_MSG when a player wins the game.

Here is our final program code with all the messages added in.

; ROM ROUTINES 
define CHRIN $ffcf ; input character from keyboard
define SCINIT $ff81 ; initialize/clear screen
define CHROUT $ffd2 ; output character to screen

; Constant variables
define BALLCOLOR $08 ; Orange
define PADCOLOR $03    ; Cyan
define BRICKCOLOR $07 ; Yellow

; Zero-page variables for ball
define BALLX $00       ; Current ball x position
define BALLY $01       ; Current ball y position
define NEWBALLX $02    ; New ball x position
define NEWBALLY $03    ; New ball y position
define BALLXMOV $04    ; How to change x position (+1 or -1)
define BALLYMOV $05    ; How to change y position (+1 or -1)
define BALLPTR $06     ; Pointer to ball address in screen (low byte)
define BALLPTRH $07    ; Pointer to ball address in screen (high byte)

; Zero-page variables for paddle
define PADWIDTH $20  ; How long is the paddle
define PADPOS $21    ; How many pixels from the left to start rendering paddle
define PADMAXPOS $22 ; Maximum value for PADPOS
define PADPTR $23    ; Screen address (low byte)
define PADPTRH $24   ; Screen address (high byte)

; Zero-page variables for bricks
define BRICKNUM $40

; Zero-page variables for loops
define REPEAT $60
define REPEAT2 $61


INITIALIZE:        ; Runs once every new game
  LDA #$00         ; Clear bitmap screen
  LDY #$00
CLEAR_BITMAP: 
  STA $0200,y
  STA $0300,y
  STA $0400,y
  STA $0500,y
  INY
  BNE CLEAR_BITMAP
  LDA #26         ; Set num bricks to 26
  STA BRICKNUM
  JSR DRAW_BRICKS ; Draw bricks onto screen
  LDA #8          ; Set paddle width to 8 pixels
  STA PADWIDTH
  LDA #24         ; Set paddle max pos to x=24
  STA PADMAXPOS
  LDA #12         ; Set paddle start pos at x=12
  STA PADPOS
  JSR UPDATE_PADDLE ; Draw paddle
  LDA #16         ; Set ball to start at x=16,y=28
  STA BALLX
  LDA #28
  STA BALLY
  LDA #$01 ; +1   ; Set ball to move right-up
  STA BALLXMOV 
  LDA #$ff ; -1
  STA BALLYMOV
  LDX BALLX  ; Get ball address from xy-pos
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY
  LDA ADDRESSPTR 
  STA BALLPTR
  LDA ADDRESSPTRH
  STA BALLPTRH
  LDY #$00         ; Draw ball at ball address
  LDA #BALLCOLOR
  STA (BALLPTR),Y

  ; Print welcome message
  LDA #<WELCOME_MSG
  STA $b0
  LDA #>WELCOME_MSG
  STA $b1
  JSR PRINT_MSG

INITIAL_LOOP: ; Loop to wait for user input before proceeding to game loop
  JSR CHRIN ; Read key input
  CMP #$0d ; ascii for "carriage return"
  BNE INITIAL_LOOP

  ; Print play message
  LDA #<PLAY_MSG
  STA $b0
  LDA #>PLAY_MSG
  STA $b1
  JSR PRINT_MSG

GAME_LOOP:
  LDA #$00          ; Update the paddle more times than the ball so that the ball moves more slowly
  STA REPEAT       
  LDA #$03
  STA REPEAT2
PADDLE_LOOP:        ; Effectively this updates the paddle 259 times before updating the ball
  JSR UPDATE_PADDLE
  DEC REPEAT       
  BNE PADDLE_LOOP
  DEC REPEAT2
  BNE PADDLE_LOOP

  JSR UPDATE_BALL
  LDA #31
  CMP BALLY         ; Check ball position
  BEQ GAME_LOSE     ; If ball hits the bottom edge, you lose

  LDA BRICKNUM      ; Check status of bricks
  BEQ GAME_WIN      ; If there are no more bricks, you win
  JMP GAME_LOOP

GAME_LOSE: ; Print lose message
  LDA #<LOSE_MSG
  STA $b0
  LDA #>LOSE_MSG
  STA $b1
  JSR PRINT_MSG
  JMP POST_GAME_LOOP

GAME_WIN: ; Print win message
  LDA #<WIN_MSG
  STA $b0
  LDA #>WIN_MSG
  STA $b1
  JSR PRINT_MSG
  JMP POST_GAME_LOOP

POST_GAME_LOOP: ; Wait for user to press enter before restarting game
  JSR CHRIN     ; Read key input
  CMP #$0d      ; ascii for "carriage return"
  BNE POST_GAME_LOOP
  JMP INITIALIZE


; ===============================================
; DRAW_BRICKS: 
;     Subroutine to draw the bricks on the screen
; ===============================================
DRAW_BRICKS:
  LDY #0
  LDA #BRICKCOLOR
  BRICK_LOOP: 
    STA $0263,Y
    INY
    CPY BRICKNUM
    BNE BRICK_LOOP
RTS


; ===============================================
; UPDATE_PADDLE: 
;     Subroutine for updating paddle location
;     based on user input
; ===============================================
UPDATE_PADDLE:
  LDA #$A0                ; Setup paddle base-row address ($05C0)
  STA PADPTR
  LDA #$05
  STA PADPTRH
  CLC                     ; Add x-pos to base-row to get our start address for drawing
  LDA PADPOS
  ADC PADPTR
  STA PADPTR
  LDA #$00
  ADC PADPTRH
  STA PADPTRH
  LDY #$00                ; Draw paddle from start address
  LDA #PADCOLOR
  DRAW_PADDLE:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE DRAW_PADDLE
  JSR CHRIN               ; Read key input
  CMP #$83                ; ascii for "left arrow key"
  BEQ MOVE_PADDLE_LEFT
  CMP #$81                ; ascii for "right"
  BEQ MOVE_PADDLE_RIGHT
  RTS                     ; If neither "left" nor "right, do nothing (return)
  MOVE_PADDLE_LEFT:
    LDA #0                ; MAX left-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX left, don't move left (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    DEC PADPOS            ; decrement paddle position
    RTS
  MOVE_PADDLE_RIGHT:
    LDA PADMAXPOS         ; MAX right-pos
    CMP PADPOS
    BEQ END_UPDATE_PADDLE ; if PADPOS is MAX right, don't move right (return)
    JSR CLEAR_PADDLE      ; clear paddle for re-render
    INC PADPOS            ; increment paddle position
  END_UPDATE_PADDLE:
    RTS


; ===============================================
; CLEAR_PADDLE: 
;     Subroutine used by UPDATE_PADDLE to clear
;     the old paddle before rendering a new one
; ===============================================
CLEAR_PADDLE:
  LDY #$00
  LDA #$00
  CLEAR_LOOP:
    STA (PADPTR),Y
    INY
    CPY PADWIDTH
    BNE CLEAR_LOOP
  RTS


; ===============================================
; UPDATE_BALL: 
;     Subroutine to update ball movement and
;     location based on the pixels ahead of the
;     direction its moving in.
;
;     This subroutine also updates the bricks and
;     brick count when the ball hits a brick
; ===============================================
UPDATE_BALL:

  LDA BALLX       ; Calculate next x-pos
  CLC
  ADC BALLXMOV
  STA NEWBALLX
  LDA NEWBALLX    ; Check next x-pos if border (-1 or 32)
  BMI SWITCH_X    ; If next x-pos is a border (-1), switch x-direction
  CMP #32
  BEQ SWITCH_X    ; If next x-pos is a border (32), switch x-direction
  JMP CHECK_X_ADJ ; If next x-pos is not a border, check the adjacent address in x-direction

SWITCH_X:      ; Switch x-direction, then we are done updating
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  RTS          ; Done updating

CHECK_X_ADJ:              ; Check adjacent address in x-direction
  LDX NEWBALLX            ; Get adjacent address in x-direction of ball (new-x, cur-y)
  LDY BALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y
  BEQ CHECK_Y_BORDER      ; If address is empty, we are good to move in this direction. Go check y
  CMP #BRICKCOLOR         ; If address is not empty, check if this address contains a brick
  BNE SWITCH_X            ; If it is not a brick, go ahead to switch x-direction
  LDA #$00
  STA (ADDRESSPTR),Y      ; If it is a brick, erase the brick
  DEC BRICKNUM            ; Decrement brick count
  JMP SWITCH_X            ; Switch x-direction

CHECK_Y_BORDER:   ; Check next y-pos if border (-1 or 32)
  LDA BALLY       ; Calculate next y-pos
  CLC
  ADC BALLYMOV
  STA NEWBALLY
  LDA NEWBALLY
  BMI SWITCH_Y    ; If next y-pos is a border (-1), switch y-direction
  CMP #32
  BEQ SWITCH_Y    ; If next y-pos is a border (32), switch y-direction
  JMP CHECK_Y_ADJ ; If next y-pos is not a border, check the adjacent address in y-direction

SWITCH_Y:      ; Switch y-direction, then we are done updating
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

CHECK_Y_ADJ:              ; Check adjacent address in x-direction
  LDX BALLX               ; Get adjacent address in y-direction of ball (cur-x, new-y)
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                ; Check the value in address
  LDA (ADDRESSPTR),Y 
  BEQ CHECK_NEW_POS       ; If address is empty, we are good to move in this direction. Go check if new xy-pos is valid
  CMP #BRICKCOLOR         ; If address is not empty, check if this address contains a brick
  BNE SWITCH_Y            ; If it is not a brick, go ahead to switch x-direction
  LDA #$00
  STA (ADDRESSPTR),Y      ; If it is a brick, erase the brick
  DEC BRICKNUM            ; Decrement brick count
  JMP SWITCH_Y            ; Switch x-direction

CHECK_NEW_POS:              ; Check address in new-pos
  LDX NEWBALLX              ; Get new-pos address based on new-x and new-y
  LDY NEWBALLY
  JSR GET_ADDRESS_FROM_XY   ; Calculate address from xy and store it to ADDRESSPTR 
  LDY #$00                  ; Check value in new-pos
  LDA (ADDRESSPTR),Y
  BEQ SET_NEW_POS           ; If address is empty, we are good to move in this direction. Set and color this new-pos
  CMP #BRICKCOLOR           ; If address is not empty, check if this address contains a brick
  BNE SWITCH_Y              ; If it is not a brick, go ahead to switch x-direction
  LDA #$00
  STA (ADDRESSPTR),Y        ; If it is a brick, erase the brick
  DEC BRICKNUM              ; Decrement brick count
  JMP SWITCH_X_Y            ; Switch both x and y direction

SET_NEW_POS:      ; Set new-pos as new ball position
  LDY #$00        ; Clear old ball
  LDA #$00
  STA (BALLPTR),Y
  LDA NEWBALLX    ; Copy new xy-pos to current xy-pos
  STA BALLX
  LDA NEWBALLY
  STA BALLY
  LDA ADDRESSPTR  ; Copy new pointer to current pointer
  STA BALLPTR
  LDA ADDRESSPTRH  
  STA BALLPTRH
  LDY #$00        ; Color ball at new pointer
  LDA #BALLCOLOR
  STA (BALLPTR),Y
  RTS             ; Done updating

SWITCH_X_Y:    ; Switch both x and y direction
  LDA BALLXMOV
  EOR #$fe     ; This toggles x-direction between 1 and -1
  STA BALLXMOV ; Store result of toggle back to BALLXMOV
  LDA BALLYMOV
  EOR #$fe     ; This toggles y-direction between 1 and -1
  STA BALLYMOV ; Store result of toggle back to BALLYMOV
  RTS          ; Done updating

  
; ===============================================
; GET_ADDRESS_FROM_XY: 
;     General subroutine used by UPDATE_BALL to 
;     get an address in the bitmap screen based on
;     xy coordinates
;
; Entry conditions:
;     Xreg - horizontal location in bitmap screen (0-31)
;     Yreg - vertical location in bitmap screen (0-31)
;
; Returns:
;     Zero-page pointer to address in bitmap
;       $00A0 - low byte of address in bitmap
;       $00A1 - high byte of address in bitmap
; ===============================================
; Zero-page variables used by this subroutine
define ADDRESSPTR $A0
define ADDRESSPTRH $A1

GET_ADDRESS_FROM_XY:
  STY ADDRESSPTR ; Add y-pos 
  LDA #$00
  STA ADDRESSPTRH 
  LDY #$05       ; Do 5 left shifts to multiply y-pos by 32
MULT_ADDR:
  ASL ADDRESSPTR 
  ROL ADDRESSPTRH 
  DEY
  BNE MULT_ADDR
  CLC            ; Add x-pos   
  TXA
  ADC ADDRESSPTR 
  STA ADDRESSPTR 
  LDA #$00
  ADC ADDRESSPTRH 
  STA ADDRESSPTRH 
  INC ADDRESSPTRH ; Add screen base Address of $0200
  INC ADDRESSPTRH  
  RTS


; ===============================================
; PRINT_MSG:
;     General subroutine to print a message to
;     the text screen
;
; Entry conditions:
;     $00B0 - low byte pointer to message
;     $00B1 - high byte pointer to message
; ===============================================
PRINT_MSG:
  JSR SCINIT      ; Clear screen
  LDY #$00
PRINT_LOOP:    
  LDA ($b0),y
  BEQ DONE_PRINT
  JSR CHROUT      ; put a character on the screen
  INY
  BNE PRINT_LOOP
DONE_PRINT:
  RTS


; ===============================================
; OTHER DATA
; ===============================================
WELCOME_MSG:
  DCB 13
  DCB 32,32,"A","S","S","E","M","B","L","Y",32,"B","R","E","A","K","O","U","T",13
  DCB 13
  DCB 32,32,"U","s","e",32,"t","h","e",32,"l","e","f","t",32,"a","n","d",32,"r","i","g","h","t",32,"a","r","r","o","w",32,"k","e","y","s",32,"t","o",13
  DCB 32,32,"m","o","v","e",32,"t","h","e",32,"p","a","d","d","l","e",".",13
  DCB 13
  DCB 32,32,"P","r","e","s","s",32,"e","n","t","e","r",32,"t","o",32,"s","t","a","r","t",".",00

PLAY_MSG:
  DCB 13
  DCB 32,32,"A","S","S","E","M","B","L","Y",32,"B","R","E","A","K","O","U","T",13
  DCB 13
  DCB 32,32,"G","a","m","e",32,"s","t","a","r","t","e","d",".",00

WIN_MSG:
  DCB 13
  DCB 32,32,"A","S","S","E","M","B","L","Y",32,"B","R","E","A","K","O","U","T",13
  DCB 13
  DCB 32,32,"Y","o","u",32,"W","i","n","!",13
  DCB 13
  DCB 32,32,"P","r","e","s","s",32,"e","n","t","e","r",32,"t","o",32,"p","l","a","y",32,"a","g","a","i","n",".",00

LOSE_MSG:
  DCB 13
  DCB 32,32,"A","S","S","E","M","B","L","Y",32,"B","R","E","A","K","O","U","T",13
  DCB 13
  DCB 32,32,"Y","o","u",32,"L","o","s","e",".",".",".",13
  DCB 13
  DCB 32,32,"P","r","e","s","s",32,"e","n","t","e","r",32,"t","o",32,"p","l","a","y",32,"a","g","a","i","n",".",00

And here is our final game!

Thoughts

This has definitely been the most challenging lab out of three labs we've done so far but also the most rewarding. I knew I've always wanted to learn assembly but never really knew that I'd end up coding a game in it! Though the game can still be improved, like giving the player more chances to break all bricks or creating more brick rows, I believe what I have created is already enough and is pretty much a full functioning breakout game. Going back to the lab criteria, this program works in the 6502 Emulator; It outputs to the character screen by informing the user about the game states; It outputs to the bitmap screen by running the actual game; It accepts user input from the keyboard as it uses the "enter" key to start/restart the game and the "left" and "right" arrow keys to move the paddle; and it uses a lot of arithmetic functions like EOR to toggle ball direction, AOL and ROR for multiplying, DEC for subtracting the brick counts, etc.

Overall, I definitely learned a lot in the lab. I started from scratch and got to learn how to create my own subroutines, work with loops and conditions, and overall developed a method for structuring my data and naming my variables. This lab really drove me to learn how to think in assembly which I believe will be really useful as I progress through my career as a software engineer.

Comments

Popular posts from this blog

Building GCC

Lab 04 - Diving into 64-bit Assembly