Skip to main content
Dansbo TechBlog

My 1st assembler game for CX16 – part 1

After I got done with Hello World in assembler for the Commander X16, I needed a new project to keep on learning about assembler programming for the 6502 processor and the CX16.

As usual, when I need an idea, I could not think of anything that would fit. But one day I was playing a game, called Amaze, on my phone and I noticed that even though it is a graphical game, it actually uses “blocks” and I have yet to find a level that has more than 20×20 blocks. It was perfect for a text-mode game.

So the plan is to create a game similar to Amaze. It is simple, can be done in text-mode and does not need any sound to be playable. Here is a video showing one of the levels from Amaze to give you an idea of what the game is about.

As the documentation for VERA seems to be very volatile and subject to changes, I want to avoid using it for now. Instead I will be using the KERNAL APIs that are also referenced in the Commander X16 Programmers Reference.

I used my Hello World example as a template, but added some extra KERNAL functions and other constants.

; Generate BASIC code so program can be
; startet by issuing RUN command
*=$0801			; Assembled code should start at $0801
			; (where BASIC programs start)
			; The real program starts at $0810 = 2064
!byte $0C,$08$0A,$00		; 2-byte line number ($000A = 10)
!byte $9E		; SYS BASIC token
!byte $20		; [space]
!byte $32,$30,$36,$34	; $32="2",$30="0",$36="6",$34="4"
			; (ASCII encoded nums for dec starting addr)
!byte $00		; End of Line
!byte $00,$00		; This is address $080C containing
			; 2-byte pointer to next line of BASIC code
			; ($0000 = end of program)
*=$0810			; Here starts the real program
; ********** Commodore 64 KERNAL API ********************
CHROUT=$FFD2		; CHROUT outputs a character
CHRIN=$FFCF		; CHRIN reads from default input
GETIN=$FFE4		; GETIN reads a single byte from
			; input, returns #0 if no key is
			; pressed = non-blocking
PLOT=$FFF0		; PLOT gets or sets cursor position

; ********** Commodore 128 KERNAL API *******************
SWAPPER=$FF5F		; Switches between 40 & 80 col mode

; ********** Commander X16 specific *********************
COLPORT=$0286		; This address contains both
			; background and foreground color
			; On C64, only foreground
			; (low nibble) works.
; ********** Available Zero-Page addresses **************
TMP0=$00		; The first 3 and last 4 unused
TMP1=$01		; zero-page locations are used
TMP2=$02		; as temporary storage (registers)

TMP3=$FB		; More space is available if we
TMP4=$FC		; are sure that we are not using
TMP5=$FD		; the BASIC Kernal
TMP6=$FE		; I am NOT sure

NOTE: In the above code, SWAPPER=$FF5F refers the older emulator/ROM where the function was copied directly from the C128 KERNAL. At ROM versions larger than R34, $FF5F is now called SCRMOD and does not work the way it is used in these examples. See The CX16 Programmers Reference

Some information about Zero-Page addresses. The first 256 bytes ($0000-$00FF) of memory are referred to as Zero-Page. These can be used in a number of different addressing modes which can provide shorter/faster instructions. They can also be used to allow indirect memory access. As the 6502 have a very limited number of registers, zero-page addresses are often used almost like registers. In the Commander X16 there are only 7 zero-page addresses that are available to the user at alle times.

After I got the KERNAL functions and constants in place, I startet creating small “helper” functions. The first couple of functions are very small and might just as well be macros. The reason I have created them as functions and not macros is to have the same calling convention for both the small and the large functions.

; **************************************************************
; GotoXY
; **************************************************************
; Description:	Move cursor to specified coordinates
; **************************************************************
; Inputs:	Register X = Column (Y coordinate)
;		Register Y = Row    (X coordinate)
; **************************************************************
GotoXY:
	clc		; Set Carry flag to ensure that
			; PLOT sets the position instead
			; of reading it
	jsr	PLOT	; Call PLOT
	rts		; Return to caller

; **************************************************************
; HLine
; **************************************************************
; Description:	Print a horizontal line
; **************************************************************
; Inputs:	Register A = Character used to print the line
;		Register X = Length of the line
; **************************************************************
HLine:
	jsr	CHROUT	; Print character in register A
	dex		; Decrement value in register X
	bne	HLine	; Jump to top if X > 0
	rts		; Return to caller

The two functions above are very short and with the comments, my hope is that they are easy to understand as well. PLOT and CHROUT are both functions in the C64/CX16 Kernal API. Now that we have a function to create a horizontal line, we also need a function to create a vertical line.

When creating a horizontal line, I was taking advantage of the fact that when a character is written to the screen with the CHROUT function, the cursor automatically advances to the next row and I could just write characters until I reached the length of the line.

I do not have the same advantage when creating a vertical line. This time I need to ensure that the cursor is moved to the next column/line before each character is printed.

; **************************************************************
; VLine
; **************************************************************
; Description:	Print a vertical line
; **************************************************************
; Inputs:	Register A = Character used to print the line
;		Register X = Height of the line
; **************************************************************
VLine:
	stx	TMP0	; Store line length in TMP0 variable
	sec		; Set carry flag to get cursor position
	jsr	PLOT	; Get cursor position into X and Y regs
	stx	TMP1	; Store Y position in TMP1 variable

.loopVL	jsr	CHROUT	; Write character
	inc	TMP1	; Increment Y position
	sta	TMP2	; Save A reg as it is changed by GotoXY
	ldx	TMP1	; Load Y position into X register
	jsr	GotoXY	; Move cursor
	lda	TMP2	; Restore A register (character)
	dec	TMP0	; Decrement line height
	bne	.loopVL	; Jump to top if we have not reached 0
	rts

Now the absolute basics are in place. I can move the cursor, I can draw horizontal- and vertical lines. Keep in mind that the line-drawing functions only work from left to right and top to bottom.

Next up: Function to print strings. Function that can convert a value in a byte to decimal number represented in a string. Function that initializes the screen. Stay tuned for Part 2.