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:
SHIFT | MIDVAL | AND |
---|---|---|
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 | 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!