Classic Computer Magazine Archive COMPUTE! ISSUE 39 / AUGUST 1983 / PAGE 216

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.