Superdesert is a version of Desert where the city is placed in a random location at the beginning of each game. It is a good chance to learn how to generate random numbers using assembler and ROM routines.
One tip. My best advice: if you do not know how to write anything in assembler, write it in BASIC first and search for the ROM routines. I use this advice myself with random number. If you program in a C-64 you have enough memory to implement your own routines, but that is hardly possible with the memory of the unexpanded VIC-20.
The code for generating a random number in BASIC is:
REM Random number between 1 and 6
INT(RND(1)*6)+1
RND generates a random float number between 0 and 0.999…. Parameter number determines how the random seed is initialized.
A positive number generates a different random number each time from a sequence stored in the memory. A negative number generates the same random number always. If you want a different random number, you have to change the negative number. Zero generates a random number from the internal timer and clock. I will use this one.
This has been a brief resume. There is more in the RND function but this is enough for Superdesert. We will see the code soon.
After generating a random number, you have to multiply them for the range of the number you want. For example, if you want a random number between 0 and 9 you multiply it plus 10. Zero plus ten is 0 and 0.99… plus ten is 9.999…
You probably do not want a float number, so it is time to convert it into an integer. That is the goal of the function INT. This function just remove the decimal part. If you want a number between 1 and 10 instead of 0 and 9, you just add 1 at the end.
Wow, this is a lot of work. Now it is time to check the ROM routines to find how to do all these operations.
RND function is at address $E094. If you read the code for this function (I did), you will find a bunch of jumps at the very beginning of the routine depending on the sign of the parameter. With this jumps, we can jump to the part of the routine we want without worry about the parameter. The address of RND(0) is $E09b
We have our random number, but where is it? Due it is a float number (and Commodore computers usually perform all numeric operations with float numbers), it is in the FAC. FAC is a 6 bytes long floating pointer number. There are two in the Commodores machines and these machines use them very often because many mathematical operations are calculated using floating number. For that reason, ROM is full with routines to manage FAC (and FAC2) and you do not need to manipulate them by yourself.
Next step is to multiply the random number. Reading the list of ROM routines again, I found the MULTEN routine to multiply the FAC plus 10. I am going to use that and I will get a number between 0 and 9 which is fine for Superdesert. The address of the MULTEN routine is $DAE2
The last step is converting a four-byte integer into a two bytes integer. Routine INTIDX (address $D1AA) do this conversion storing the higher part of ht integer number in accumulator and lower part in Y register. Due the number is between 0 and 9, A will always be 0.
Finally the code. This code is part of the subroutine we will see in the next section.
; Random subroutines
RND_S = $E09b ; BASIC RND (no standard entry point)
MULTEN = $DAE2 ; Multiply FAC by 10.
INT = $DCCC ; FAC is rounded down to an integer in floating point format (4 bytes)
INTIDX = $D1AA ; INTIDX (A higher, Y lower)
; Calculate a random number
; Between 0 and 9
;
jsr RND_S
jsr MULTEN ; MULTEN
jsr INT
jsr INTIDX ; INTIDX (A higher, Y lower)
Now we have our random number, it is time to use it to place the city randomly in the map.
This routine is long because it has two do two different tasks and I do not reuse code, so there are duplicated code. Sadly, it is shorter to duplicate code than to make calls to subroutines.
First task is to clean the previous location of the city. I calculate the index in the map and I place an EMPTY value. I can do it in this way because there is always an empty space to the left and the right of the city.
Random_City
; Clean previous city
; Ocupa lo menos que rusar una subrutina
; Porque la subrutina no divide entre dos
lda city_x
asl
asl
asl
asl
clc
adc city_y
lsr
tax
lda #EMPTY
sta row00,x
; every loc is 4 bits only
; the locations of city have nothing next
; so I can save the whole byte safely.
lda #EMPTY
sta row00,x
;….
Second task is to generate the random number and placing the city.
Too much randomness is not fun. To avoid bad locations for the city, I have selected a set of locations. The random number between 0 and 9 selects one of these locations. The drawback is that a player may discover than city always appear in some specific locations but I do not see a problem.
First, the code where I sore locations for the city.
;--- Random temple ---------
v_city_x BYTE $2, $5, $9, $d, $f, $3, $2, $9, $f, $e
v_city_y BYTE $e, $f, $f, $c, $9, $a, $e, $f, $9, $f
I did not found ten good places for the city, so I repeat some of them.
Now, it is time to see the rest of the routine. Here is the code.
Random_City
; Clean previous city
; Calculate a random number
; Between 0 and 9
;
jsr RND_S
jsr MULTEN ; MULTEN
jsr INT
jsr INTIDX ; INTIDX (A higher, Y lower)
; A is always 0
; Random numer is in Y
; Repeat this code is shorter than call
; subroutine Return_LOC_in_X
lda v_city_x,y
sta city_x
lda v_city_y,y
; Stores the temple to reste it in a new game
sta city_y
asl
asl
asl
asl
clc
adc v_city_x,y
; save the original value
; I will use it soon again
tay
lsr
tax
; Load again LOC number to see
; if it is pair or odd
; To see whcih part of the byte use
tya
and #%00000001
bne @LOC_is_odd ; odd
; Change the higher part of the byte to put the temple
lda #FINAL_LOC
asl
asl
asl
asl
clc
adc row00,x
; Done
jmp @Exit
@LOC_is_odd
; change the lower part
lda #FINAL_LOC
clc
adc row00,x
@Exit
sta row00,x
lda row00,x
rts
You may now identify the lines that generates the random number easily. After that I change the map to place the cit. first I have to calculate the index in the map combining the X and the Y (remember Y is the highest part of the byte and X is the lower one). Then, I check if location is odd or pair to see if I have to change the first middle or the second middle of the byte in that location.
I always place the city in an empty space, so, there is always four zeros in that part of the byte. I just add the value that indicate a city in that temple and job is done.
If you want to try different things using random routines, or other routines involving float numbers, you should use FLTASC to convert the FAC1 into a STR to print it on screen. The String is stored in the memory area starting at address $100.