There’s a problem when you build your own computer and therefore have to write your own operating system. All the things you rely on when coding in other environments – such as maths functions – are not there. You have to write them yourself.
The Zolatron 64, like most homebrew computers, is effectively a bare metal project. I’m writing all the code in assembly, talking direct to the 6502 processor and other chips.
That wasn’t a problem. Until today.
Hunt the Zumpus
I decided that, to give the Zolatron 64 something to do, I would write a game for it. I’m not a fan of most video games – I find them simultaneously difficult and boring. But I’ve always had an affection for text-based games, such as the classic Adventure. And I may create a version of that. But in the meantime I figured I’d start with something simpler – Hunt the Wumpus, or, because this is the Zolatron, Hunt the Zumpus.
I’ll leave it to Wikipedia to describe the game. But, in essence, it consists of wandering around a group of 20 connected rooms looking for a beast to shoot, being careful not to end up in the same room as it (you get eaten) or in a room with a bottomless pit (in which case your fate is obvious).
Much of the challenge of writing the games lies in user interaction – dealing with user input. But there’s another snag.
At the beginning of the game, the program needs to place the Zumpus, the player, the two bottomless pits (lift shafts in my version) and two bats in random locations. (In my game, the bats are replaced with sales reps, although in early versions they started off as under-managers.)
During the gameplay, the program also needs to effectively roll a dice to decide (randomly) whether the Zumpus wakes up, moves in one direction or another and so on. But how do you generate these random decisions in a system with no maths functions?
My immediate problem was placing the various characters in random locations. And what I came up with worked quite well.
I created a loop in the code in which the X register was decremented from a start value of 19 down to 0 over and over. I then prompted the user to hit the ENTER key. Whenever that happens, the code reads the value of X at that moment. Given how fast the computer is looping through values of X, this produces a sufficiently random result.
The one glitch is that each of the six random locations we need to generate has to be unique. This meant adding a checking routine to see if the captured value has been used before. If so, the user needs to keep hitting ENTER until we have six different values. You can see the result in the image below, with the six random values being printed out at the end.
A matter of timing
This is all very well. But demanding that the user hit ENTER every time we need a random number is tedious. There is a better way.
What I’ve switched to now exploits the Timer 1 function in one of the 65C22 VIA chips in the Zolatron. I load the value 19 into the timer’s registers and start it up in free-running mode. (I don’t bother enabling interrupts – they’re not necessary for this purpose.) The timer will constantly run down to 0 and then restart from 19, over and over. This is much like my X loop earlier except that I don’t have to write a loop every time I need a random number – the timer handles that and runs constantly in the background. Whenever I need a random number, I just read the low byte of the timer’s counter register.
That will work for the latter stages of the game, because the moments when we need a random number wil be indeterminate – it all depends at what speed the player is playing.
It won’t work for that initial setting up of random locations, though, because that will happen at a fixed, deterministic time after the launch of the game. I still need the user’s input for those – but that routine happens once per game, which is fine.
True or false
The timer approach has another advantage, too. In addition to providing a random number in the range 0..19, I can also use it to determine a true/false or yes/no value. The subroutine I’ve written (.roll_dice) treats any value in the Accumulator as a ‘threshold’ value. It then looks to see if this value is equal to/above the randomly selected value (read from the address VIAC_T1CL in the code below). If so, it returns a 0 in the memory location referred to by FUNC_RESULT (a one-byte location I use for storing the results of subroutines). Otherwise it returns a 1. The random number is returned in the Accumulator.
TEST_VAL in the code below is another one-byte memory location I use as a general-purpose variable.
.roll_dice stz FUNC_RESULT ; Set to 0 (false) by default ldx VIAC_T1CL ; Put Counter value in X stx TEST_VAL ; and store it in a handy memory location cmp TEST_VAL ; Compare threshold (in A) to the counter value bcs roll_dice_end ; threshold >= Counter, so default false value is fine lda #1 ; Otherwise, return a true value sta FUNC_RESULT .roll_dice_end txa ; Transfer counter value to A rts
So now I can generate random room numbers 0-19 and make yes/no or 0/1 random decisions.
Right … now to hunt that Zumpus…