MACHINE LANGUAGE
Jim Butterfield, Associate Editor
A Bagel Break
Let's walk through an example of programming a complete game, including machine language. We'll make it a simple one: "Bagels," a guessing game that has appeared under other names, including the commercially packaged game, Master Mind.
We'll make this one simple, with few frills. We could do it entirely in BASIC, of course; we're using machine language for the practice and for the thrill of seeing the answers come up instantly. You can judge for yourself whether or not machine language handles the job more efficiently.
Ground Rules
We will assume that BASIC will generate the random codes. Yes, you can generate pseudo-random numbers in machine language, too, but we'll shorten the job with BASIC. Once we're into a game, we'll stay entirely in machine language.
The program is written to work on all Commodore machines up to and including the VIC and 64. This means that we need to be careful about memory, since different machines have differently arranged memories. We'll avoid this problem by using the cassette buffer area that is located in the same area in all these machines. And of course, we'll use the built-in Kernal routines that work on all Commodore units: FFD2 to print, FFE4 to get a character.
Planning
We'll need the following work areas:
- A counter which keeps track of the number of guesses (let's put this at $0240 hexadecimal);
- A counter which says how many "exact" matches have been found on this guess (let's use $0241);
- A counter which says how many "inexact" matches have been found (use $0242);
- A counter to keep track of the number of characters typed by the player (we'll use $0243);
- A place to keep the mystery code (four locations from $0244 to $0247 hex);
- A place to put a copy of the mystery code (from $0248 to $204B);
- A place for the user's guess (from $024C to $024F).
Why do we make a copy of the mystery code? Because we will destroy parts of this copy as we test for matches. That way, we will never count the same item twice as a match.
Writing The Program
We lay out a blank piece of paper and try to write the logic. We assume that the BASIC program has placed the mystery code (alphabetic characters from A to F) into hex addresses 0244 to 0247 before it calls upon our program to play the game. Here we go: we'll write a "main routine" first. Although we plan to put it into the cassette buffer (starting at hex 033C), we don't need to write in the addresses – yet.
START LDA #$00 STA $0240
We set our "number of guesses" to zero for starting. Now, on to the next guess:
GUESS INC $0240 LDA $0240
Our guess-number is set one higher, and we bring it into the A register.
CMP #$0A BEQ QUIT
If we've had nine guesses, we quit here and let BASIC take over. By the way, we don't know exactly where to branch ahead, so we give the branch location a name rather than an address. We'll fill this in soon. In the meantime, if we don't branch, it's time to play:
JSR PLAY
This subroutine will do the whole job of receiving one guess from the user and accounting for it. If the user guesses perfectly, the Z flag will be set. In any other case, we'll need to go back:
BNE GUESS QUIT RTS
Again, we may not know the exact address to which we're looping back at the time we scribble down our first program outline. We'll fill it in later. Sometimes we do this by "hand," and sometimes an assembler program will do it for us. A full-scale assembler will take the "labels" we have used – GUESS, QUIT, and PLAY – calculate their addresses, and make the substitution for us. If we have a smaller assembler, or are assembling by hand, we'll need to write in the addresses. We do this in two columns:
033C LDA #$00 033E STA $0240 GUESS 0341 INC $0240 0344 LDA $0240 0347 CMP #$0A 0349 BEQ $0350 034B JSR $0351 034E BNE $0341 QUIT 0350 RTS
The programmer will quickly learn to convert the program into whatever form his development programs need.
We'll assume this translation (at least in part) and continue with subroutine PLAY. First, we must print the guess number. The binary number in the A register must be converted to ASCII, and printed, together with a following space:
0351 PLAY ORA #$30 JSR $FFD2 LDA #$20 JSR $FFD2
Now, on to the main play. Let's zero the counters, including the player input count:
LDX #$00 STX $0241 STX $0242 STX $0243
Here comes another loop, as we wait for each character to be input. We test each character to make sure that it's a letter from A to F:
0366 INLOOP JSR $FFE4 CMP #$41 BCC INLOOP CMP #$47 BCS INLOOP
We have a legal letter; echo it to the screen and put it to memory.
JSR $FFD2 LDX $0243 INC $0243 STA $024C,X
We must also copy the "secret" code into a work area, so that we can destroy it as we test for matches:
LDA $0244,X STA $0248,X
Have we received all four letters of the guess yet? If not, go back:
CPX #$03 BNE INLOOP
Now we may cheek for exact matches. X is conveniently at three, so we may count it down as we compare:
0381 COMPAR LDA $0248,X CMP $024C,X BNE SKIP
If they don't match, we'll skip the next part. If they do, we must count the match and destroy the values so that we don't use them again:
INC $0241 LDA #$00 STA $0248,X STA $024C,X
Now, our coding rejoins. We move along to test for the next match:
0394 SKIP DEX BPL COMPAR
We have logged any exact matches. Now we must look for the out-of-place matches. We may use X and Y to move through the two values, remembering to skip zeros.
LDY #$00 0399 RETRY LDX #$00 039B CHECK LDA $0248,Y BEQ PASS CMP $024C, X BNE PASS
Again, if we see a zero (already counted) or no match, we skip the next bit and go to PASS. Otherwise, we've got a match; we count it and destroy the entry, as before:
INC $0242 LDA #$00 STA $0248, Y STA $024C, X
Our code comes together again. We have two loops to pick up:
03B0 PASS INX CPX #$04 BCC CHECK INY CPY #$04 BCC RETRY
Now we may print the two results, stored in $0241 and $0242. A loop will save a little time and space:
LDX #$00 03BC PLOOP LDA #$20 JSR $FFD2 LDA $0241, X ORA #$30 JSR $FFD2 INX CPX #$02 BCC PLOOP
Now a carriage return to end the line. Finally, we must check for a "correct" solution (exact matches = 4) so that the calling routine will know whether to quit or not:
LDA #$0D JSR $FFD2 LDA $0241 CMP #$04 BNE PLAY RTS
That's it for our machine language part; we'll start to put it together next time.