6502 Maze Game

Introduction

After learning a lot about assembly, getting familiar with the instructions for the 6502 8bit processor, and experimenting with different exercises – it is time to create something of my own. For this lab, I chose to create a maze game. The process of figuring out how to make everything work, in the little time I had, was complicated. I used snippets from the examples provided by Professor Chris Tyler, and what I’ve learned from the Software Portability & Optimization course so far, to come up with a considerably complex implementation of the popular game. In this blog, I will go over the steps I’ve taken to implement this game, and I will discuss how it can be further improved in the future.

Requirements

The instructions are to create a program that:

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

The maze game neatly fits these requirements – involving character output for instructions, character input to record movements, and arithmetic/math when calculating the position of the player after every move. To make the program interesting, I also planned to implement these rules:

  1. The player will lose, meaning the game would stop, if they “bumps” into a maze wall/border. They always have to move within the given path. This can make the game harder, as the player needs to focus to not make the wrong move.
  2. Player must be able to choose between a hard/easy mode, for variety and allowing easy testing. Adds more complexity to the game.
  3. Player should be able to restart the game at any point, and continue playing smoothly without restarting the program – after a win, or a lose.

Examples & Tools

After analyzing the resources available about 6502 tools, I found a few examples of code that proved very useful in the development of the maze game:

ROM Routines

Our 6502 Emulator has a few useful ROM Routines, that mimic some of the built-in firmware found in the physical systems. I recognized that for my implementation, I would use:

  • SCINIT $ff81 – Initialize and clear the character display
  • CHRIN $ffcf – Input one character from keyboard (returns A (that is, the return value will be placed in the Accumulator register))
  • CHROUT $ffdw – Outputs one character (A) to the screen at the current cursor position. Screen will wrap/scroll appropriately. Printable characters and cursor codes ($80/$81/$82/$83 for up/right/down/left) as well as RETURN ($0d) are accepted. Printable ASCII codes with the high bit set will be printed in reverse video.

Note: these descriptions were pulled, verbatim, from the website.

Etch-a-Sketch™

To start my ideation phase, I loaded the “Etch-a-Sketch™ Style Drawing” program, provided in the course examples. I immediately saw some similarities between the functionalities of this program, and some of the requirements for my maze game:

  1. A cursor, or “dot”, moving around the screen, leaving a trail on every “visited” pixel.
  2. The cursor is controller by user keyboard input.
  3. Help/instructions message printed to the console.
; This code is Copyright©2020-2024 Seneca College of Applied Arts and 
; Technology. Each of these programs is free software; you can 
; redistribute them and/or modify them under the terms of the 
; 'GNU|General Public License as published by the Free Software 
; Foundation; either version 2 of the License, or (at your option) 
; any later version.

; zero-page variable locations
 define	ROW		$20	; current row
 define	COL		$21	; current column
 define	POINTER		$10	; ptr: start of row
 define	POINTER_H	$11
 ; constants
 define	DOT		$01	; dot colour
 define	CURSOR		$04	; black colour

 	 	ldy #$00	; put help text on screen
 print:		lda help,y
 		beq setup
 		sta $f000,y
 		iny
 		bne print
 setup:		lda #$0f	; set initial ROW,COL
 		sta ROW
 		sta COL
 draw:		lda ROW		; ensure ROW is in range 0:31
 		and #$1f
 		sta ROW
 		lda COL		; ensure COL is in range 0:31
 		and #$1f
 		sta COL
 		ldy ROW		; load POINTER with start-of-row
 		lda table_low,y
 		sta POINTER
 		lda table_high,y
 		sta POINTER_H
 		ldy COL		; store CURSOR at POINTER plus COL
 		lda #CURSOR
 		sta (POINTER),y
 getkey:		lda $ff		; get a keystroke
 		beq getkey
 		ldx #$00	; clear out the key buffer
 		stx $ff
 		cmp #$43	; handle C or c
 		beq clear
 		cmp #$63
 		beq clear
 		cmp #$80	; if not a cursor key, ignore
 		bmi getkey
 		cmp #$84
 		bpl getkey
 		pha		; save A
 		lda #DOT	; set current position to DOT
 		sta (POINTER),y
 		pla		; restore A
 		cmp #$80	; check key == up
 		bne check1
 		dec ROW		; ... if yes, decrement ROW
 		jmp done
 check1:		cmp #$81	; check key == right
 		bne check2
 		inc COL		; ... if yes, increment COL
 		jmp done
 check2:		cmp #$82	; check if key == down
 		bne check3
 		inc ROW		; ... if yes, increment ROW
 		jmp done
 check3:		cmp #$83	; check if key == left
 		bne done
 		dec COL		; ... if yes, decrement COL
 		clc
 		bcc done
 clear:		lda table_low	; clear the screen
 		sta POINTER
 		lda table_high
 		sta POINTER_H
 		ldy #$00
 		tya
 c_loop:		sta (POINTER),y
 		iny
 		bne c_loop
 		inc POINTER_H
 		ldx POINTER_H
 		cpx #$06
 		bne c_loop
 done:		clc		; repeat
 		bcc draw

 ; 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

 ; help message for the character screen
 help:
   dcb "A","r","r","o","w",32,"k","e","y","s"
   dcb 32,"d","r","a","w",32,"/",32,"'","C","'"
   dcb 32,"k","e","y",32,"c","l","e","a","r","s"
   dcb 00

Other than the already-implemented functionalities, I saw a lot of versatility in the POINTER, ROW & COL implementation, using the table_high & table_low to store the high & low bytes of the addresses of every row start. Using this existing implementation will definitely move me forward in my progress.

Print Subroutine

I also found an implementation of the Print Subroutine, included in the “Color Selector” example on the course github.

; Colour selector - live updates
; (C)2020 Chris Tyler - Seneca College
; Licensed under the GPLv2+ - see LICENSE file
....
; --------------------------------------------------------
; Print a message
; 
; Prints the message in memory immediately after the 
; JSR PRINT. The message must be null-terminated and
; 255 characters maximum in length.

PRINT:		pla
		clc
		adc #$01
		sta PRINT_PTR
		pla
		sta PRINT_PTR_H

		tya
		pha

		ldy #$00
print_next:	lda (PRINT_PTR),y
		beq print_done
		
		jsr CHROUT
		iny
		jmp print_next

print_done:	tya
		clc
		adc PRINT_PTR
		sta PRINT_PTR

		lda PRINT_PTR_H
		adc #$00
		sta PRINT_PTR_H

		pla
		tay

		lda PRINT_PTR_H
		pha
		lda PRINT_PTR
		pha

		rts

Initial Implementation

Working off-of the Etch-a-Sketch™ example, I wanted to adjust it to first fill the screen with a specific background, and have the user trace the image.

Creating the Background/Maze

I used the PiskelApp website to draw the 32×32 pixel background, which would be used for my simple implementation of the game. I exported the spirtes I created as “c file”, which gave me a format that was easy to convert to DCB. I divided the image into 4 parts, for each page, to make it faster drawing.

As you can see from the illustration, once the player “hit” the blue background, the game was over. I achieved this by loading the color of the “next” pixel on the accumulator, and comparing it with the background color.

define BKG $10
define FRG $11
define BRD $12
define SCORE $13 ; score address

 ; zero-page variable locations
 define	ROW		$20	; current row
 define	COL		$21	; current column
 define	POINTER		$22	; ptr: start of row
 define	POINTER_H	$23
 
; constants
 define	DOT		$01	; dot colour
 define	CURSOR		$04	; black colour


ldy #$00          ; set y index

lda #$06
sta BKG

lda #$01
sta FRG

lda #$05
sta BRD

loop:
    ldx circimg_1 , y
    lda $0000, x
    sta $0200,y   ; color pixel on page

    ldx circimg_2 , y
    lda $0000, x
    sta $0300,y      
 
    ldx circimg_3 , y
    lda $0000, x 
    sta $0400,y    

    ldx circimg_4 , y
    lda $0000, x
    sta $0500,y    
    iny           ; increment Y
    bne loop
		
 	 	ldy #$00	; put help text on screen

 print:		lda help,y
 		beq setup
 		sta $f000,y
 		iny
 		bne print
 setup:		
    lda #$07	; set initial ROW,COL
 		sta ROW
    lda #$10
 		sta COL
    lda #$00
    tay
    tax

 draw:		
    lda ROW		; ensure ROW is in range 0:31
    and #$1f
    sta ROW
    lda COL		; ensure COL is in range 0:31
    and #$1f
    sta COL
 		
    ldy ROW		; load POINTER with start-of-row
    lda table_low,y
    sta POINTER
    lda table_high,y
    sta POINTER_H

    ldy COL		; store CURSOR at POINTER plus COL
    lda (POINTER), y    ; load the curr screen color at dot position
    cmp BKG             ; check if it's BKG (stored @ $0010)
    beq game_lost       ; if it's $10, lost the game.

    inc SCORE           ; score = number of "good" moves.
    
    ; store CURSOR at POINTER plus COL
    lda #CURSOR
    sta (POINTER),y

 
  
    
 getkey:		lda $ff		; get a keystroke
 		beq getkey
 		ldx #$00	; clear out the key buffer
 		stx $ff
 		cmp #$43	; handle C or c
 		beq clear
 		cmp #$63
 		beq clear
 		pha		; save A
 		lda #DOT	; set current position to DOT
 		sta (POINTER),y
 		pla		; restore A
 		cmp #$77	; check key == up
 		bne check1
 		dec ROW		; ... if yes, decrement ROW
 		jmp done
 check1:		cmp #$64	; check key == right
 		bne check2
 		inc COL		; ... if yes, increment COL
 		jmp done
 check2:		cmp #$73	; check if key == down
 		bne check3
 		inc ROW		; ... if yes, increment ROW
 		jmp done
 check3:		cmp #$61	; check if key == left
 		bne done
 		dec COL		; ... if yes, decrement COL
 		clc
 		bcc done
 clear:		lda table_low	; clear the screen
 		sta POINTER
 		lda table_high
 		sta POINTER_H
 		ldy #$00
 		tya
 c_loop:		sta (POINTER),y
 		iny
 		bne c_loop
 		inc POINTER_H
 		ldx POINTER_H
 		cpx #$06
 		bne c_loop
 done:		clc		; repeat
 		bcc draw

game_lost:
   ldy #$00
   lda #$00
   print_game_over:
    sta $0200,y   ; color pixel on page
    sta $0300,y      
    sta $0400,y    
    sta $0500,y    
    iny           ; increment Y
    bne print_game_over


 ; 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

 ; help message for the character screen
 help:
   dcb "A","r","r","o","w",32,"k","e","y","s"
   dcb 32,"d","r","a","w",32,"/",32,"'","C","'"
   dcb 32,"k","e","y",32,"c","l","e","a","r","s"
   dcb 00

circimg_1:
DCB $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12
DCB $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12
DCB $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $11, $11, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12

circimg_2:
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $11, $10, $10, $10, $10, $11, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $10, $10, $10, $10, $12, $12, $12

circimg_3:
DCB $12, $12, $12, $10, $10, $10, $10, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $11, $10, $10, $10, $10, $11, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12

circimg_4:
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $11, $11, $11, $11, $11, $11, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $12, $12, $12
DCB $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12
DCB $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12
DCB $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12, $12

Improvements & Maze Implementation

Now that my “circle” demo was working, I could move on to the maze implementation. Initially, I wanted to load a couple maze games for the user to choose from, but eventually opted for simplicity, and chose 2 maze maps – in 2 modes, easy and hard.

Generating the Maze

I used a maze generator which has the option to dictate the dimensions. There was a small issue with getting the 32×32 maze to work, since it was only producing odd-number dimensions, but using Piskel I was able to fix the maze, and convert it to DCB format.

Selecting Difficulty Level

To implement selecting the difficulty level, I first printed the instructions – to type in 1, or 2. If the key typed is neither, I wait for the input to be valid, and then I display the cooresponding maze on the screen. Then I continue to the actual play fo the game.

setup:	
  lda #$00	
  sta COL  ; set initial COL, ROW will depend on mode
  tay
  tax

  lda #$04
  fill_screen:
    sta $0200,y   
    sta $0300,y   
    sta $0400,y   
    sta $0500,y   
    iny           
    bne fill_screen

start_menu:
  jsr SCINIT ; Erase the screen on setup
  jsr PRINT  ; Usage instructions, prompt for difficulty level
  dcb "M","a","z","e",32,"G","a","m","e","!",$0D
  dcb "*",32,32,"G","e","t",32,"t","o",32,"t","h","e",
      32,"g","r","e","e","n",32,"f","i","n","i","s","h",$0D
  dcb "*",32,32,"D","o",32,"n","o","t",32,"t","o","u","c","h"
      ,32,"t","h","e",32,"m","a","z","e",32,"w","a","l","l","s",$0D
  dcb "*",32,32,"U","s","e",32,"W","A","S","D",32,"t","o",
      32,"m","o","v","e",$0D
  dcb "S","e","l","e","c","t",32,"d","i","f","f","i","c","u",
      "l","t","y",32,"l","e","v","e","l",":",$0D
  dcb "1",32,"-",32,"E","a","s","y",$0D 
  dcb "2",32,"-",32,"H","a","r","d",$0D
  dcb "E","n","t","e","r",32,"t","h","e",32,
      "n","u","m","b","e","r",":",00

get_input: 	
  JSR CHRIN ; Use ROM routine to get single character

check_easy: ; Check if easy mode selected, display easy maze
  cmp #$31 	; key == 1
  bne check_hard
  
  jsr PRINT ; Print selected mode
  dcb $0D,"N","o","w",32,"p","l","a","y","i","n","g",32,
      "E","A","S","Y",32,"l","e","v","e","l",46,32
  dcb "P","r","e","s","s",32,"X",32,"t","o",32,"r","e","s",
      "t","a","r","t",46,00
	
  lda #$02 	; Set starting row to 2
  sta ROW
  clc
  ldy #$00
	
  draw_easy_maze: ; Draw easy maze on bitmap screen 
    lda MAZE_EASY_1, y
    sta $0200,y   
    lda MAZE_EASY_2, y
    sta $0300,y   
    lda MAZE_EASY_3, y
    sta $0400,y   
    lda MAZE_EASY_4, y
    sta $0500,y   
    iny           
    bne draw_easy_maze

  beq play
  
check_hard: ; Check if hard mode selected, display hard maze
  cmp #$32	; key == 2
  bne get_input ; get input again, if not right.
	
  jsr PRINT
  dcb $0D,"N","o","w",32,"p","l","a","y","i","n","g",
      32,"H","A","R","D",32,"l","e","v","e","l",32
  dcb "P","r","e","s","s",32,"X",32,"t","o",32,"r","e","s","t",
      "a","r","t",46,00
	
  lda #$01	; Set starting row to 1
  sta ROW
  clc
  ldy #$00
  
  draw_hard_maze: ; Draw hard maze on bitmap screen 
    lda MAZE_HARD_1, y
    sta $0200,y   
    lda MAZE_HARD_2, y
    sta $0300,y   
    lda MAZE_HARD_3, y
    sta $0400,y   
    lda MAZE_HARD_4, y
    sta $0500,y   
    iny           
    bne draw_hard_maze

  beq play

Registering Player Moves

For my game, I decided to improve upon the Etch-a-Sketch™ code, to have the player move with WASD keys. The arrow keys worked great, but hitting the “down” arrow sometimes caused the screen to scroll down, which became very annoying. I also decided to use “X” to restart the game, in which case the program would jump back to “setup”, initializing the screen and reprompting the level.

getkey:		
  lda $ff		; get a keystroke
  beq getkey
  ldx #$00	; clear out the key buffer  
  stx $ff
		
  cmp #$58	; handles X or x
  beq restart
  cmp #$78
  beq restart
		
  pha		; save A
 		
  lda #PATH_CLR					; color the "path"
  sta (POINTER),y
 	
  pla		; restore A

  cmp #$77       ; 'W' or 'w'
  beq move_up
  cmp #$57
  beq move_up

  cmp #$64       ; 'D' or 'd'
  beq move_right
  cmp #$44
  beq move_right

  cmp #$73       ; 'S' or 's'
  beq move_down
  cmp #$53
  beq move_down

  cmp #$61       ; 'A' or 'a'
  beq move_left
  cmp #$41
  beq move_left
  jmp done       ; If no movement, do nothing

move_up:
    dec ROW
    jmp done

move_right:
    inc COL
    jmp done

move_down:
    inc ROW
    jmp done

move_left:
    dec COL
    jmp done


restart:		
    jsr setup
 
done:		
    jsr draw

Checking Win/Loss

To check if the player won (reached the finish), or lost (reached a maze wall), I used the same technique as the demo circle game, checking the color of the pixel. In the circle game, I used the backgorund BKG and border BRD colors as variables, stored in memory, but to simplify the game I decided to set static values – black (#$00) for walls/borders, white (#$01) for the maze path, and green (#$05) for the maze finish. In short, I check if the current pointer stores black, if it does – player loses. If the player reached green, the player won. In either case, a message is displayed on the character screen, and the bitmap display gets filled with a graphic (GAME OVER for loss, and a blank GREEN screen for winning – to be improved later).

draw:		

  ; Ensure ROW & COL are in range 0:31
  lda ROW		
  and #$1f
  sta ROW
  lda COL
  and #$1f
  sta COL

  ; Update POINTER with the location
  ldy ROW						
  lda table_low,y 	
  sta POINTER
  lda table_high,y
  sta POINTER_H
  
  ; Check if we hit wall or finish
  ldy COL								
  lda (POINTER), y     ; load color at curr position
  
  cmp #BORDER_CLR      ; check if it's a border color (lost)
  bne check_won        
  
  ; PRINT GAME LOST MESSAGE
  ldy #$00
  print_game_over:
    lda game_over_screen1 , y
    sta $0200,y   ; color pixel on page
   
    lda game_over_screen2 , y
    sta $0300,y      
    
    lda game_over_screen3 , y
    sta $0400,y    
    
    lda game_over_screen4 , y
    sta $0500,y    
    iny           ; increment Y
    bne print_game_over
    
  jsr SCINIT
  jsr PRINT
  dcb $0D,"Y","o","u",32,"l","o","s","t","!","!",$0D,00
  jsr wait_for_restart
  
  check_won:
  
    cmp #FINISH_CLR      ; check if it's finish color (won)
    bne continue_game
    
    ; PRINT GAME WON MESSAGE
    lda #$05
    ldy #$00
    print_game_won:
      sta $0200,y   
      sta $0300,y   
      sta $0400,y   
      sta $0500,y   
      iny           
      bne print_game_won

    jsr SCINIT
    jsr PRINT
    dcb $0D,"Y","o","u",32,"w","o","n","!","!","!",$0D,00
    jsr wait_for_restart

  continue_game:
  ; store DOT_CLR at the current location
  lda #DOT_CLR
  sta (POINTER),y

...

wait_for_restart:
  jsr PRINT
  dcb $0D,"P","r","e","s","s",32,"a","n","y",32,"k","e","y",
      32,"t","o",32,"R","E","S","T","A","R","T",".",".",".",00

  wait_key_press:
    lda $ff		; get a keystroke
    beq wait_key_press
  
  jmp setup

Final Results & Reccomendations

The final result is a fully functioning, small version of a maze game. You can choose a difficulty level, you lose when touching the walls, and can restart anytime. Using the example “Etch-a-Sketch™” code was very helpful in achieving this result, but the custom functionalities were still hard to implement. I ran into many errors and spent a lot of time debugging the code (which is very hard in assembly), but the final result is great nonetheless! There are some things I would improve, given the time and resources:

  1. Adding a better “winning” screen
  2. More efficient and readable code, possibly create a subroutine for filling the screen with an image.
  3. More maze options, and a better selection process, similar to the “color-selector” example.
  4. Some score method, to output score.
  5. Have smaller mazes, less than 32×32, to be displayed in the middle of the screen – to save space.

Download the full maze game source code to see the final result, including the maze image data:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *