Introduction
In the last post, we created a simple 6502 assembly code that generates a maze for a player to explore. Today, we are going to build codes on top of it in order to implement the rest of objectives.
Objectives
For this game, we need to accomplish 3 more objectives as following:
1. The game must draw the maze in the bitmapped screen. (Done!)
- A player must be able to use the keyboard to control.
- A player must find a route to reach to the goal within the maze in order to win the game.
- A player cannot goes through the wall.
Code
; zero-page variables
define ROW $20 ; current row
define COL $21 ; current column
define DRAWN_ROW $22 ; number of drawn rows
define MAZE_L $14 ; a pointer that points to where the maze will
define MAZE_H $15 ; be drawn
define PLAYER_L $10 ; a pointer that points to the player in the
; screen
define PLAYER_H $11
define TARGET_L $12 ; a pointer that points to the target position
define TARGET_H $13 ; where the player wants to proceed
; constants
define PATH $03 ; path color
define PLAYER $0e ; player color
define HEIGHT 7 ; height of the maze
define WIDTH 7 ; width of the maze
; ROM routine
define SCINIT $ff81 ; initialize/clear screen
jsr printHelp
jsr drawMaze
jsr gameInit
jsr gameLoop
printHelp: ldy #$00 ; print instructions on the screen
pHelpLoop: lda help,y
beq done
sta $f000,y
iny
bne pHelpLoop
gameInit: lda #$01 ; initialize ROW, COL to make the player
sta ROW ; starting at $0221 of the screen
sta COL
rts
gameLoop: jsr updatePosition
jsr getkey
jsr checkCollision
ldx #$00 ; clear out the key buffer
stx $ff
jmp gameLoop
updatePosition: ldy ROW ; load PLAYER pointer with ROW
lda table_low,y
sta PLAYER_L
lda table_high,y
sta PLAYER_H
ldy COL ; place the player at (POINTER + COL)
lda #PLAYER
sta (PLAYER_L),y
rts
getkey: lda $ff ; get the input key
cmp #$80 ; allow arrow keys only
bmi getkey
cmp #$84
bpl getkey
pha ; save the accumulator
lda #PATH ; set color of the current position to PATH
sta (PLAYER_L),y
pla ; restore accumulator
cmp #$80 ; check key is up
bne checkRight
dec ROW ; ... if yes, decrement ROW
rts
checkRight: cmp #$81 ; check if key is right
bne checkDown
inc COL ; ... if yes, increment COL
rts
checkDown: cmp #$82 ; check if key is down
bne checkLeft
inc ROW ; ... if yes, increment ROW
rts
checkLeft: cmp #$83 ; check if key is left
bne done
dec COL ; ... if yes, decrement COL
rts
done: rts ; break out of a loop or subroutine
checkCollision: ldy ROW ; load TARGET pointer with ROW
lda table_low,y
sta TARGET_L
lda table_high,y
sta TARGET_H
ldy COL ; load the color from the target
lda (TARGET_L),y; at (POINTER + COL)
cmp #$01
beq done
cmp #$03
beq done
cmp #$0a
beq gameComplete
lda #$00
sta (TARGET_L),y
lda $ff
cmp #$80 ; if input key was up...
bne ifRight
inc ROW ; ... if yes, increment ROW
rts
ifRight: cmp #$81 ; if input key was right...
bne ifDown
dec COL ; ... if yes, decrement COL
rts
ifDown: cmp #$82 ; if input key was down...
bne ifLeft
dec ROW ; ... if yes, decrement ROW
rts
ifLeft: cmp #$83 ; if input key was left...
bne done
inc COL ; ... if yes, increment COL
rts
gameComplete: jsr SCINIT
ldy #$00 ; print game completion message on the screen
pGameComplete: lda complete,y
beq done
sta $f000,y
iny
bne pGameComplete
brk
drawMaze: lda #$21 ; a pointer pointing to the first pixel
sta MAZE_L ; of the screen
lda #$02
sta MAZE_H
lda #$00 ; number of drawn rows
sta DRAWN_ROW
ldx #$00 ; maze data index
ldy #$00 ; column index
draw: lda maze_data,x
sta (MAZE_L), y
inx
iny
cpy #WIDTH ; compare with the number of WIDTH
bne draw ; if not, keep drawing the column
inc DRAWN_ROW ; increment the number of row
lda #HEIGHT
cmp DRAWN_ROW ; compare with the number of HEIGHT
beq done
lda MAZE_L
clc
adc #$20 ; add 32(0x0020) to increment the row
sta MAZE_L ; of the pixel
lda MAZE_H
adc #$00
sta MAZE_H
ldy #$00 ; reset the column index for the new row
beq draw
; help text message
help:
dcb "P","l","a","y",32,"w","i","t","h",32,"a","r","r","o","w"
dcb 32,"k","e","y","s",32,"t","o",32,"c","o","n","t","r","o","l",10
dcb 00
; game complete message
complete:
dcb "Y","o","u",32,"b","e","a","t",32
dcb "t","h","e",32,"g","a","m","e","!"
dcb 00
; maze map data
maze_data:
dcb 01,00,01,00,01,01,01
dcb 01,01,01,00,00,00,01
dcb 00,00,01,00,01,00,01
dcb 01,00,01,00,01,01,01
dcb 01,00,01,00,01,00,01
dcb 01,00,01,00,01,00,01
dcb 01,01,01,01,01,00,10
; these two tables contain the high and low bytes
; of the addresses of the start of each row
table_high:
dcb $02,$02,$02,$02,$02,$02,$02,$02
dcb $03,$03,$03,$03,$03,$03,$03,$03
dcb $04,$04,$04,$04,$04,$04,$04,$04
dcb $05,$05,$05,$05,$05,$05,$05,$05
table_low:
dcb $00,$20,$40,$60,$80,$a0,$c0,$e0
dcb $00,$20,$40,$60,$80,$a0,$c0,$e0
dcb $00,$20,$40,$60,$80,$a0,$c0,$e0
dcb $00,$20,$40,$60,$80,$a0,$c0,$e0
Let's walk through the code together. First of all, we print out the helpful instruction on the text screen with printHelp
subroutine. Then, we draw a maze using the drawMaze
subroutine we created from the last post. Having a maze for a player to explore on the screen, we need to first set the game state with the initial player position on the screen $#0221
. After that, we call the subroutine called gameLoop
which constantly loops itself.
The gameLoop
itself consists of a number of subroutines. The first one is updatePosition
. This subroutine loads the player pointer with the given the row and column information so that we can place the player on the screen. Afterwards, we call the getKey
subroutine to receive the player input from the keyboard. We limit the keyboard input by only accepting arrow keystrokes. Once we receive a key input, we update the number of column and row accordingly. Then, we check if the position the player wants to move is a wall by using the checkCollision
subroutine. If the player hits by the wall, we simply retract the move.
Once reaching to the goal, the screen will congratulate the player with the text message.
Conclusion
Making a simple maze using the 6502 assembly language definitely is harder and more time-consuming as compared to other high-level languages. The game we explored together is also not polished and needs a lot of improvements as well (such as having an alert when the player wants to proceed into the wall). we could use. Yet, this experience gives us a very meaningful insight as to how the game really works under the hood.
Top comments (0)