My 1st assembler game for CX16 – part 2
Continuing from part 1 where I created 3 small functions: GotoXY, HLine and VLine. I am going to create a couple more “helper” functions as well as the function that will initialize the screen and make it ready to start the game.
First I will look at the function to print a string on the screen. It is actually fairly simple. We start by finding the address in memory where the string is stored. When that is found, we print the character that is stored there, increment the address and start over until we reach a 0-byte. As the function being used (CHROUT) automaticly move the cursor we do not have to think about anything other than incrementing the address and checking for 0-byte.
; ************************************************************** ; PrintStr ; ************************************************************** ; Description: Write a zero-terminated PETSCII string to scr ; ************************************************************** ; Inputs: Register X = Low byte of string start address ; Register Y = High byte of string start address ; ************************************************************** PrintStr: stx TMP0 ; Store string start address in sty TMP1 ; Zero-page memory in order to ; do indirect adressing ldy #0 ; Y used as offset, start at 0 .doprint lda (TMP0), Y ; Load character into reg A beq .printdone ; If char=0, we are done jsr CHROUT ; Print the character iny ; Increment offset (Y) jmp .doprint ; Loop back, get next character .printdone: rts
Now that we have the PrintStr function, let me show how it is called. I will create a small snippet of code to show how to use the PrintStr function. This snippet will not function correctly on its own, but if it is combined with the information found in part 1, it should be possible to assemble a .prg file that will actually run on the Commander X16.
; Load X with low-byte of .title address ldx #<.title ; Load Y with high-byte of .title address ldy #>.title jsr PrintStr ; Variable containing our string .title !pet "x16-maze",0
The next function converts a !byte value into a 3 digit PETSCII string. As we all know, a byte can hold any value from 0 to 255 so we need 3 digits to represent any number that can be held in a byte. The function works directly on 2 global variables. A !byte called .lvl and a !pet called .lvltxt
.lvl !byte 0 .lvltxt !pet "000",0
Since the .lvltxt variable is initialized with “000”, we only need to change the digits that are not 0. So if the value in .lvl is 53, we only change the 2nd and 3rd digit and leave the first 0 in place. The .lvltxt will then contain “053”. The function works, roughly this way:
if value >= 200 then
write '2' in the first digit
subtract 200 from the value
else if value >= 100 then
write '1' in the first digit
subtract 100 from the value
endif
for digit=9 downto 1
num = digit * 10
if value >= num then
convert digit to petscii char
write petscii-char in 2nd digit
subtract num from value
break out of for-loop
endif
endfor
convert value to petscii char
write petscii-char in 3rd digit
In the hundred’s we check for either 200 or 100 to see if we need to change the first zero to 2 or 1. For the ten’s a loop is created that counts from 90 down to 10 and checks if we need to change the middle digit. Finally for the one’s, we simply convert the remaining value to a writeable char (i.e. add $30 to the value).
; ************************************************************** ; LVLtoPET ; ************************************************************** ; Description: Convert value in byte to 3-digit decimal string ; ************************************************************** ; Inputs: .lvl = Global variable containing a byte value ; .lvltxt = Global variable containing string ; ************************************************************** LVLtoPET: ; Local names to zero-page constant to make the code ; a little more readable .value=$00 .digit=$01 .num=$02 lda .lvl ; Load Reg A with current level ; Check if .lvl is >= 200 cmp #200 bcc .is100 ; branch to .is100 if .lvl <200 ldy #'2' ; Write '2' to first digit of .lvltxt sty .lvltxt sbc #200 ; Subtract 200 from .lvl beq .allDone; If result = 0, we are done jmp .Tens ; Check if .lvl is >= 100 .is100: cmp #100 bcc .Tens ; branch to .Tens if .lvl < 100 ldy #'1' ; Write '1' to first digit of .lvltxt sty .lvltxt sbc #100 ; Subtract 100 from .lvl beq .allDone; If result = 0, we are done ; Check if .lvl contains any tens (10-90) .Tens: ldy #9 sty .digit ; Store digit in zero-page memory ldy #90 sty .num ; Store digit*10 in zero-page memory .DoTens cmp .num bcc .Any10 ; branch to .Any10 if .lvl < .num sta .value ; Save current value, we need the a reg lda .digit clc ; Clear carry to ensure correct add adc #$30 ; Add $30 to digit to get petscii char sta .lvltxt+1;Write digit to 2nd space of .lvltxt lda .value ; Restore value into A register sec ; Set carry to ensure correct subtraction sbc .num ; Subtract .num from current .value jmp .Ones .Any10 cmp #10 bcc .Ones ; branch to .Ones if A < 10 ; subtract 10 from .value sta .value ; Save current value lda .num ; Subtract 10 from .number sec sbc #10 sta .num lda .value ; Restore value into A register dec .digit jmp .DoTens .Ones: clc ; Clear carry to ensure correct add adc #$30 ; Add $30 to get petscii char sta .lvltxt+2;Write digit to 3rd space of .lvltxt .allDone: rts
This is, by far, the largest and most complicated helper-function yet. There might be a simpler way of doing it (I hope so) but I have not searched for it.
Finally we have reached a point where we can actually start to get something written to the screen. Now it is just a question of deciding how the screen should look. At the time I was designing the screen layout for the game, I was unaware that it is possible to write to the bottom right without scrolling the entire screen (See my note on Line to scroll on on the Programming Ramblings page). For this reason I am clearing the screen with a black background and creating a frame that sits 1 char in from the border all the way around. This reduces the area for the actual mazes, but there is still plenty of space for some fairly complex mazes.
The InitSCR function initializes the screen to 40×30 mode. It creates the area that will contain the mazes, level information and a bit of helpful information on how to play the game. The function depends on the following global constants.
CHROUT=$FFD2 ; CHROUT outputs a character (C64 KERNAL API) SWAPPER=$FF5F ; Switches between 80x60 and 40x30 modes COLPORT=$0286 ; This address contains foreground and ; background colors Wall=113 ; PETSCII circle WallCol=$0B ; Black/Darkgray Space=' ' ; Char used for spaces
NOTE: SWAPPER is now called SCRMOD and is a new KERNAL function for the CX16 instead of an old function from C128. This means that these examples are no longer working. See the CX16 Programmers reference for using SCRMOD
The following global variables are used by the function.
.lvl !byte 1 .title !pet "cx16-maze",0 .helptxt !pet "w,a,s,d=move spc=next r=reset q=quit",0 .lvlstr !pet "lvl:",0 .lvltxt !pet "000",0
And finally for the function it self. It makes use of the helper functions that have been explained previously so this function in it self is not very complex.
; ************************************************************** ; Initializes the screen ; ************************************************************** ; INPUTS: Gloabl variables ; .lvl ; .title ; .helptxt ; .lvlstr ; .lvltxt ; ************************************************************** InitSCR: lda $D9 ; $D9 contains the number of ; columns being shown cmp #80 ; if this is 80, we will switch to 40x30 beq .SetIt ; Set 40 column mode jmp .NoSet .SetIt: jsr SWAPPER ; Switch screenmode .NoSet: lda #$01 ; Black background, white text sta COLPORT ; Set Color lda #147 ; ClrHome jsr CHROUT ; Clear Screen lda #$10 ; White background, black text sta COLPORT ; Set color ldx #1 ; Setup to create top horizontal line ldy #1 jsr GotoXY lda #Space ldx #38 jsr HLine ; Draw horizontal line ldx #28 ; Setup to create bottom horizontal line ldy #1 jsr GotoXY lda #Space ldx #38 jsr HLine ; Draw horizontal line ldx #2 ; Setup to create left most vertical line ldy #1 jsr GotoXY lda #Space ldx #26 jsr VLine ; Draw left most vertical line ldx #2 ; Setup to create right most vertical line ldy #38 jsr GotoXY lda #Space ldx #26 jsr VLine ; Draw right most vertical line lda #$12 ; Set color, white background, red text sta COLPORT ldx #1 ; Set up for title text ldy #15 jsr GotoXY ldx #<.title; Write the title text ldy #>.title jsr PrintStr ldx #1 ; Set up for level text (top right corner) ldy #31 jsr GotoXY ldx #<.lvlstr; Write the level text ldy #>.lvlstr jsr PrintStr jsr LVLtoPET ; Create level as a petscii string ldx #<.lvltxt ldy #>.lvltxt jsr PrintStr ldy #2 ; Set up for help text (bottom line) ldx #28 jsr GotoXY ldx #<.helptxt; Write help text ldy #>.helptxt jsr PrintStr jsr FillGA rts
NOTE: First line of above function is not correct. $D9 no longer contains the number of columns, instead it can be found on address $02AE. As mentioned in previous note. SWAPPER no longer exists and is no SCRMOD.
As you might have noticed, the last line before the rts is actually a call to another helper function that I have not yet described.
FillGA is a function, who’s only function is to fill the center of the screen, the area within the white border, with “wall” characters. It does this by creating horizontal lines of the “wall” character.
; ************************************************************** ; Fill the "gamearea" with "wall tiles" ; ************************************************************** ; INPUTS: Global constant WallCol is used. ; ************************************************************** FillGA: lda #WallCol ; Set background and foreground sta COLPORT ; color ldx #1 ; X register holds Y coordinate .StartOfFill: inx ; Increment Y coordinate stx TMP0 ; Save the Y coordinate in ZP cpx #28 ; If we have reached line 28, beq .EndOfFill ; We are done, so branch to end ldy #2 ; Y register holds X coordinate jsr GotoXY ; Place cursor at X, Y coordinates lda #Wall ; Load A with 'wall' character ldx #36 ; Create a horizontal line that is jsr HLine ; 36 characters wide ldx TMP0 ; Restore Y coordinate from ZP jmp .StartOfFill .EndOfFill rts
Now with the last helper function in place, the initial screen should look something like this:

Next up: Defining the mazes and getting them drawn on the screen and possibly getting startet with some “animation”. Stay tuned for part 3.
[…] 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. […]
[…] from part 2 where finished by getting the initial game screen shown. In the meantime I have made it to a […]