Appendix D
A Library of
Subroutines


Here is a collection of techniques you'll need to use in many of your ML programs. Those techniques which are not inherently easy to understand are followed by an explanation.

Increment and Decrement Double Byte Numbers
You'll often want to raise or lower a number by 1. To increment a number, you add 1 to it: Incrementing 5 results in 6. Decrement lowers a number by 1. Single-byte numbers are easy; you just use INC or DEC. But you'll often want to increment two-byte numbers which hold addresses, game scores, pointers, or some other number which requires two bytes. Two bytes, ganged together and seen as a single number, can hold values from 0 ($0000) up to 65535 ($FFFF). Here's how to raise a two-byte number by 1, to increment it:
(Let's assume that the number you want to increment or decrement is located in addresses $0605 and $0606, and the ML program segment performing the action is located at $5000.)
5000 INCREMENT INC $0605; raise the low byte
5003 BNE GOFORTH; if not zero, leave high byte alone
5005 INC $0606; raise high byte
5008 GOFORTH ... continue with program


    The trick in this routine is the BNE. If the low byte isn't raised to zero (from 255), we don't need to add a "carry" to the high byte, so we jump over it. However, if the low byte does turn into a zero, the high byte must then be raised. This is similar to the way an ordinary decimal increment creates a carry when you add 1 to 9 (or 99 or 999). The lower number turns to zero, and the next column over is raised by one.
    To double decrement, you need an extra step. The reason it's more complicated is that the 6502 chip has no way to test if you've crossed over to $FF, down from $00. BNE and BEQ will test if something is zero, but nothing tests for $FF. (The N flag is turned on when you go from $00 to $FF, and BPL or BMI could test it.) The problem with it, though, is that the N flag isn't limited to sensing $FF. It is sensitive to any number higher than 127 decimal ($7F).
So, here's the way to handle double-deckers:

5000 LDA $0605; load in the low byte (affecting the zero flag)
5003 BNE FIXLOWBYTE; if it's not zero, lower it, skipping high byte
5005 DEC $0606, zero in low byte forces this.
5008 FIXLOWBYTE DEC $0605, always dec the low byte.


    Here we always lower the low byte, but lower the high byte only when the low byte is found to be zero. If you think about it, that's the way any subtraction would work.

Comparison
Comparing a single-byte against another single-byte is easily achieved with CMP. Double-byte comparison can be handled this way:
(Assume that the numbers you want to compare are located in addresses $0605,0606 and $0700,0701. The ML program segment performing the comparison is located at $5000.)
5000 SEC
5001 LDA $0605; low byte of first number
5004 SBC $0700; low byte of second number
5007 STA $0800; temporary holding place for this result
500A LDA $0606; high byte of first number
500D SBC $0701; high byte of second number, leave result in A
5010 ORA $0800; results in zero if A and $0800 were both zero.


    The flags in the Status Register are left in various states after this routine-you can test them with the B instructions and branch according to the results. The ORA sets the Z (zero) flag if the results of the first subtraction (left in $0800) and the second subtraction (in A, the Accumulator) were both zero. This would only happen if the two numbers tested were identical, and BEQ would test for this (Branch if EQual).
    If the first number is lower than the second, the carry flag would have been cleared, so BCC (Branch if Carry Clear) will test for that possibility. If the first number is higher than the second, BCS (Branch if Carry Set) will be true. You can therefore branch with BEQ for =, BCC for <, and BCS for >. Just keep in mind which number you are considering the first and which the second in this test.

Double-Byte Addition
CLC ADC and EC SBC will add and subtract one-byte numbers. To add two-byte numbers, use:
(Assume that the numbers you want to add are located in addresses $0605,0606 and $0700,0701. The ML program segment performing the addition is located at $5000.)
5000 CLC; always do this before any addition
5001 LDA $0605
5004 ADC $0700
5007 STA $0605; the result will be left in $0605,0606
500A LDA $0606
500D ADC $0701
5010 STA $0606


    It's not necessary to put the result on top of the number in $0605,0606-you can put it anywhere. But you'll often be adding a particular value to another and not needing the original any longer-adding ten points to a score for every blasted alien is an example. If this were the case, following the logic of the routine above, you would have a 10 in $0701, 0702:

0701 0A; the 10 points you get for hitting an alien
0702 00


You'd want that 10 to remain undisturbed throughout the game. The score, however, keeps changing during the game and, held in $0605,0606, it can be covered over, replaced with each addition.

Double-Byte Subtraction
This is quite similar to double-byte addition. Since subtracting one number from another is also a comparison of those two numbers, you could combine subtraction with the double-byte comparison routine above (using ORA). In any event, this is the way to subtract double-byte numbers. Be sure to keep straight which number is being subtracted from the other. We'll call the number being subtracted the second number.
(Assume that the number you want to subtract [the "second number"] is located in addresses $0700,0701, and the number it is being subtracted from [the "first number"] is held in $0605,0606. The result will be left in $0605,0606. The ML program segment performing the subtraction is located at $5000.)
5000 SEC; always do this before any subtraction
5001 LDA $0605; low byte of first number
5004 SBC $0700; low byte of second number
5007 STA $0605; the result will be left in $0605,0606
500A LDA $0606; high byte of first number
500D SBC $0701; high byte of second number
5010 STA $0606; high byte of final result


Multi-Byte Addition and Subtraction
Using the methods for adding and subtracting illustrated above, you can manipulate larger numbers than can be held within two bytes (65535 is the largest possible two-byte integer). Here's how to subtract one four-byte-long number from another. The locations and conditions are the same as for the two-byte subtraction example above, except the "first number" (the minuend) is held in the four-byte chain, $0605,0606,0607,0608, and the "second number" (the subtrahend, the number being subtracted from the first number) is in $0700,0701,0702,0703.
    Also observe that the most significant byte is held in $0703 and $0608. We'll use the Y Register for Indirect Y addressing, use four bytes in zero page as pointers to the two numbers, and use the X Register as a counter to make sure that all four bytes are dealt with. This means that X must be loaded with the length of the chains we're subtracting-in this case, 4.

5000 LDX #4; length of the byte chains
5002 LDY #0, set Y
5004 SEC; always before subtraction
5005 LOOP LDA (FIRST),Y
5007 SBC (SECOND),Y
5009 STA (FIRST),Y; the answer will be left in $0605-0608.
500B INY; raise index to chains
500C DEX; lower counter
5010 BNE LOOP; haven't yet done all four bytes


    Before this will work, the pointers in zero page must have been set up to allow the Indirect Y addressing. This is one way to do it:

2000 FIRST = $FB; define zero page pointers at $FB and $FD
2000 SECOND = $FD
2000 SETUP LDA #5; set up pointer to $0605
2002 STA FIRST
2004 LDA #6
2006 STA FIRST+1
2008 LDA #0; set up pointer to $0700
200A STA SECOND
200C LDA #7
200E STA SECOND+1


Multiplication

× 2

ASL (no argument used, "Accumulator addressing mode") will multiply the number in the Accumulator by 2.

× 3

    (To multiply by 3, use a temporary variable byte we'll call TEMP.)

5000 STA TEMP; put the number into the variable
5003 ASL; multiply it by 2
5004 ADC TEMP; (X * 2 + X = X * 3) the answer is in A.



× 4
    (To multiply by 4, just ASL twice.)

5000 ASL; * 2
5001 ASL; * 2 again


× 4 (two byte)

    (To multiply a two-byte integer by 4, use a two-byte variable we'll call TEMP and TEMP+1.)

5000 ASL TEMP; multiply the low byte by 2
5003 ROL TEMP+1; moving any carry into the high byte
5006 ASL TEMP, multiply the low byte by 2 again
5009 ROL TEMP+1; again acknowledge any carry.


× 10

    (To multiply a two-byte integer by 10, use an additional twobyte variable we'll call STORE.)

5000; first put the number into STORE for safekeeping
5000 LDA TEMP:STA STORE:LDA TEMP+1:STA STORE+1
500C; then multiply it by 4
500C ASL TEMP; multiply the low byte by 2
500F ROL TEMP+1; moving any carry into the high byte
5012 ASL TEMP; multiply the low byte by 2 again
5015 ROL TEMP+1; again acknowledge any carry.
5018; then add the original, resulting in X * 5
5018 LDA STORE
501B ADC TEMP
501E STA TEMP
5021 LDA STORE+1
501D ADC TEMP+1
5024 STA TEMP+1
5027; then just multiply by 2 since (5 * 2 = 10)
5027 ASL TEMP
502A ROL TEMP+1


× ?

    (To multiply a two-byte integer by other odd values, just use a similar combination of addition and multiplication which results in the correct amount of multiplication.)

× 100

    (To multiply a two-byte integer by 100, just go through the above subroutine twice.)

× 256

    (To multiply a one-byte integer by 256, just transform it into a two-byte integer.)

5000 LDA TEMP
5003 STA TEMP+1
5006 LDA #0
5008 STA TEMP


Division

÷ 2

LSR (no argument used, "Accumulator addressing mode") will divide the number in the Accumulator by 2.

÷ 4

(To divide by 4, just LSR twice.)

5000 LSR; / 2
5001 LSR; / 2 again


÷ 4 (two byte)

    (To divide a two-byte integer, called TEMP, by 2)

5000 LSR TEMP+1; shift high byte right
5001 ROR TEMP; pulling any carry into the low byte


Return to Table of Contents | Previous Chapter | Next Chapter