A Simple Maze Game using 6502 Emulator Part 2
Published: 2022-02-14 03:03:53

## 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!)
2. A player must be able to use the keyboard to control.
3. A player must find a route to reach to the goal within the maze in order to win the game.
4. 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
```
![game_screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4dg3m5euhpoiecz5onk.png)
 
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.
![text_help_message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7elx3xsa2haiil4p4mlg.png)
 
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. 
![game_screen_2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uh2c0qtv6200gmme4o4u.png)

Once reaching to the goal, the screen will congratulate the player with the text message.
![game_screen_3n](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2l9b79xcx8z3q0h0zed2.png)
![text_game_completed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n06wiaz3hrwh8nqiyfp2.png)
 
## 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.