10 PRINT Maze in 6502 Assembly Language

Most retro computer people probably now the simple maze drawing created by a graphical slash and a backslash character in random order. Although there are also pseudo 3D variations, the most common Basic code is

 10 PRINT CHR$(109.5+RND(1)); : GOTO 10

for a non-stop drawing of above maze. Basically, the task is to randomly alternate between two graphical characters „\“ and /„. In Commodore PETSCII, these characters are represented by code 109 ($6D) and 110 ($6E), hence the 109.5 in above example that is being added to a random floating-point number between 0 and 1 using the RND() function.

In 2020 I wrote a little 6502 assembly program for above task. Thanks to the constant rain we currently have in Germany, I found some time to go thru my code and discovered not only a mistake I (and some other people) made but also a more elegant solution. Although the task sounds very simple, there are three solutions (and maybe more) to randomly select one of two characters. The solutions differ in style and in required CPU cycles.

Let us start with some common code:

     *= $1000

     ; Fill the screen with a random
     ; maze based on slash and
     ; backslash PETSCII chr. At
     ; the end wait for key pressed.

This probably does not need an explanation. Our little program starts at address $1000. Instead of non-stop drawing of the maze I decided to wait for a key pressed at the end of the screen in order to better enjoy the results.

     slash = $6e
     backslash = $6d

First we define our „slash“ and „backslash“ characters.

     lda $d018  ; lower chr set
     and #253
     sta $d018

The next three lines select the lower character set. This is important and was my mistake in the beginning. If for whatever reason the computer is currently using the second character table, then the characters „M“ and „N“ are displayed instead. Yes, the user could switch between the two tables with <Commodore>-<Shift> on the keyboard, but of course we want to solve this in software,

     jsr init_random
main
     jsr $e544 ; clear screen
     jsr print_maze
     jsr getkey
     rts
;---------------------------------------

Now we initialize our random number generator init_random (explained later). Next we have our main code that first clears the screen (using system function $e544), prints the maze, then waits for a key pressed, and finally returns control.

print_maze
     .block
     sec
     ldy #24 ; amount of rows
loop_y
     ldx #40 ; amount of columns
loop_x
     lda $d41b ; get random nr

The interesting part is the print_maze subroutine, After setting the carry flag with the command „sec“, we load the number of rows (24) on the screen into the y register and the number of columns (40) into the x register. The „lda $d41b“ loads a new random number between 0 and 255 into the accumulator. Following are three variations to decide between the slash and the backslash to print:

SHIFTMIDVALAND
Take the random integer between 0 and 255 and shift all bits to the right (bit 0 moves into the carry flag), then branch to label „bs“ if carry is set. Otherwise continue. This makes use of the fact that an even random number always has bit 0 set to „0“, whereas an uneven number always to „1“.Subtract the median between 0 and 255 (which is 127) from the ransom number and branch to label „bs“ if result is a negative number. Otherwise continue.A more elegant version from Steve Koch. Take the random number between 0 and 255 and logic AND it with the number 1. The result is either 0 or 1, which will be added to the absolute number $6d, resulting in the random character codes $6d or $6e in the accumulator.
lsr a ; carry is 0 or 1
bcs bs ; branch on carry
sbc 127 ; mid val bw 0-255
bmi bs ; branch if minus
and #1 ; and accu with 1
adc #$6d ; add with carry

Followed by the rest of the code:

     lda #slash
     jmp print
bs
     lda #backslash

Load either the slash or the backslash into the accumulator and print the character onto the screen using system function CHROUT ($ffd2). Please note that this function expects PETSCII codes not screen codes. Also, the example from Steve Koch does not need to load the character codes into the accumulator, as the „adc #$6d“ took care of this already.

print
     jsr $ffd2 ; chrout
     dex
     bne loop_x
     dey
     bne loop_y
     rts
.bend
;---------------------------------------

Characters are printed onto the screen until the loop_x and loop_y loops come to their end.

getkey
     ; Return from sub as soon
     ; as any key is pressed.
     jsr $ffe4 ; getin
     beq getkey
     lda #0
     sta $c6   ; clear keyb buffer
     rts

The getkey function simply waits for the user to press any key using the GETIN ($ffe4) system function.

;---------------------------------------
init_random
     ; Prepare SID voice 3.
     ; Get random nr from 0-255 by
     ; loading accu with $d41b.
     ; Load maximum freq ff
     ; into voice 3 lb $d40e
     ; into voice 3 hb $d40f
     ; noice waveform, gate bit off
     ; into voice 3 control register
     ; Call init_random only once!
     lda #$ff
     sta $d40e
     sta $d40f
     lda #$80
     sta $d412
     rts
     .end

Last but not least, the init_random function uses the sound chip SID to generate random integers between 0 and 255. That’s it!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert


Der Zeitraum für die reCAPTCHA-Überprüfung ist abgelaufen. Bitte laden Sie die Seite neu.