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:
- 2D breakout game using pure JavaScript [MDN Tutorial]
- I made the same game in Assembly, C and C++ [YouTube Video]
- Alternative 6502 assembler and emulator [The brickout.asm project]
- Place a Message on the Character Display
Through my research, I have come up with the following list of steps in order to create this breakout game:
- Develop keyboard controllable paddle
- Draw bricks onto screen
- Develop bouncing ball
- Making the ball break bricks
- Determining game end state
- 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
Post a Comment