We will (somewhat) conclude this series by the translation of yet another program in the VIC-20 User's Manual. It is one of the example programs/games at the end of the manual, originally written by Duane Later - "Killer Comet":
Code: Select all
0 REM"{6 DEL}"{3 SPACE}KILLER COMET{7 SPACE}BY{2 SPACE}DUANE LATER
1 C=7680+22*6:DIMB(12):LL=C:POKE9*16^3+15,8
2 PRINT"{YEL,CLR}*** KILLER COMET{2 SPACE}***":T=0
3 PRINT"{4 SPACE}HIT ANY KEY"
8 POKE8179,160:
9 FORA=38444TO38400+505:POKEA,1:NEXT
10 :
20 FORA=1 TO 12:B(A)=160:NEXT
21 IFW=0THEN25
22 POKE9*16^3+14,U:U=U-2:IFU<=0THENW=0:POKE9*16^3+13,0
25 F=1:REM ERA METEOR
26 FORE=0TO44STEP22
27 FORD=C+ETOC+3+E:POKED,32 :F=F+1:NEXT:NEXT
28 C=C+1
29 F=1:REM DRAW METEOR
30 POKE8179,160:FORE=0TO44STEP22
35 FORD=C+ETOC+3+E:POKED,B(F):F=F+1:NEXT:NEXT
36 IFPEEK(8178)=160THENPRINT"{CLR,4 DOWN}MOON BASE DESTROYED !":GOTO500
40 GETA$:IFA$<>""ANDG=0THENG=1:S=7680+15+22*21
50 IFG=0THEN80
55 POKES,32:S=S-22
60 IFS<7746THENG=0:GOTO21
70 IFPEEK(S)=160THENPOKES,32:G=0:T=T+1:W=1:POKE9*16^3+13,128+000:U=15:GOTO80
71 IFPEEK(S-1)=160THENG=0:POKES-1,32:T=T+1:W=1:POKE9*16^3+13,128+000:U=15:GOTO80
75 POKES,81
80 F=1:REM CHECK MET
81 IFT=12THENPRINT"{CLR,3 DOWN}***METEOR DESTROYED***":FORRR=1TO2500:NEXT:LL=LL+44:C=LL:GOTO2
82 FORE=0TO44STEP22
84 FORD=C+ETOC+3+E:IFPEEK(D)=32THENB(F)=32
86 F=F+1:NEXT:NEXT
90 GOTO21
500 POKE9*16^3+13,128+5
505 POKE9*16^3+14,5:FORRR=1TO300:NEXT
510 FORA=15TO0STEP-1
511 POKE9*16^3+14,A
520 FORRR=1TO500:NEXT
530 NEXT
540 FORRR=1TO2000:NEXT:RUN
- Killer Comet uses some PRINT statements, and it's already feasible here to employ a PRIMM subroutine which - after a JSR - prints the following bytes up to a zero byte in a loop with JSR $FFD2,
- There are also some visible and not-quite-so-visible delay loops, which we put into a time-delay subroutine that works by checking the low-byte of TI at $A2.

The memory map suggests, that the full code of Killer Comet will fit into $1000..$12FF. $1100 will be used as start address. We first fill $1000..$12FF with zeroes and then begin by entering the PRIMM routine in $10D0..$10ED:

Again we'll have to cope with forward references of branches. I will note them as the type-in progresses, but will also give a list of those references at the bottom to cross check. The branch at $10E0 is corrected to BEQ $10E7:

The delay loop is put into $10F0..$10FF. The value in the accumulator specifies the delay time in 1/60 seconds (this works the same both on PAL and NTSC!):

In line 1, the DIM statement is handled by the prepared memory layout. Three NOPs and LDA #$F0/STA $9005 are included for later use. The constant expression 7680+22*6 = 7812 = $1E84 is put into C := $FB/$FC and LL := $1040/$1041. Actually, we predecrement this address by 1 to prepare for an optimization.
Likewise, the address 9*16^3+15 is nothing other than 36879 and we'll simply do a "POKE36879,8" - which sets background and border to black: LDA #$08/STA $900F. At $111B, the instruction JSR $10D0 acts as begin of the first PRINT statement:

We precede the string "{YEL,CLR}*** KILLER COMET{2 SPACE}***" with a {RVS OFF} character and move it into place with the H+T technique explained in the earlier postings. The two "X" at the end are placeholders for a carriage return character (code $0D = 13) and the terminating 0 byte. Check with M 111E 1137, that everything is at its place:

... with the two instances of $58 (="X") already replaced by $0D, $00.
The instructions in $1138..$1143 conclude line 2. We'll not only initialise T := $1043 to 0, but also G := $1042, U := $1044 and V := $1045.
The PRINT in line 3 works out the same way as above, beginning at $1146:

Again, the "X" placeholders are replaced by $0D, $00 in $1159 and $115A:

After the moon base has been put into place by the equivalent of line 8 in $115B..$115F, line 9 somewhat uselessly initialises a portion of the colour RAM to be 1 (=white). Well it has already that value, but anyhow:
38444 to 38905 are exactly 462 positions, and as the exact fill order doesn't matter, we use two indexed STA instructions in the loop $1164..$116B to fill the memory, with X counting down from 231 (=462/2!) to 1 and excluding 0:

As a small exercise, try to work out how I came up with $962B and $9712 for the indexed STA instructions.

Actually, all that line 9 does is wasting a little bit time, giving the player an opportunity to read the game instructions. I timed out the loop at roughly 100 jiffies (i.e. 100/60 seconds) and thus call the delay subroutine at $10F0 with A=100.
The game initialisation is completed by line 20. This fills the array B() - and here we'll take a departure from the 1:1 translation of the game and put in a major optimization!
If you take a look at lines 25 to 35, the program first erases the meteor with a doubly nested FOR loop and then repaints the still existing parts from the array B() with another doubly nested FOR loop. At the speed of BASIC, that makes the meteor blinking out and into existence again, which results in a quite flickery display.
All the two nested FOR loops do is: calculate some address offsets from the top-left of the meteor (12 in total). We can eliminate this cumbersome construction by putting the offsets into a table at $1020:

Additionally we add another empty column to the left of the meteor. This "cleans up" after the meteor as it moves to the right. The loop at $1172..$117B now copies the constant init values to the "life" version of the meteor data at $1030.
Remember address $117D. This is the start of the main game loop!
Lines 21 ($117D..$1181) and 22 ($1182..$1199) are assembled in the next screen:

The forward references at $1180, $118E and $1190 are resolved here:

... with ".A 1180 BEQ $119A", ".A 118E BEQ $1192" and ".A 1190 BCS $119A".
With the exception of line 28 (which we defer for later) and the POKE8179,160 in line 30, the whole lot of lines 25 to 35 now collapses into the small loop at $119A to $11A5:

After the moon base has been repainted (see POKE8179,160 of line 30 -> $11A7..$11AB), line 36 checks the position left to the base. We replace the check for a meteor value by another logic: the position left to the base is supposed to be clear. This is also done in preparation for the improvement with user defined graphics.
Regardless, when the moon base has been destroyed, we have to print this. The forward reference to line 40, where the program continues - if the THEN in line 36 doesn't execute - is yet unresolved:

The string "{CLR,4 DOWN}MOON BASE DESTROYED !" is put at $11B6..$11D2, once again with the H+T technique. When the "X" placeholders have been replaced by $0D, $00, a JMP instruction at $11D3 does the equivalent of GOTO 500 - and we don't have the faintest idea at the moment where this will end up!
Therefore, we have to remember to correct that placeholder address of JMP $11D3.

At least we can now correct the branch at $11B1: ".A 11B1 BEQ $11D6".
Before going to line 40, we insert a small delay at $11D6..$11DA: 15/60 = 1/4 of a second, giving the player this time after a screen update to think about whether to press a key (and thus fire a ball "missile"), or not:

GETA$ is replaced by JSR $FFE4 and the three instructions at $11DE..$11E4 perform the logic of the IF condition. The THEN clause then is executed in $11E5..$11F1 and we initialise S := $FD/$FE with 7680+15+22*21 precalculated as $1FDD, and predecremented by 1 - in preparation to lines 70 and 71 where S-1 and S are PEEKed.
The forward references skipping the THEN clause of line 40 are resolved here:

... with ".A 11DE BEQ $11F2" and ".A 11E3 BNE $11F2".
Line 50 does a huge branch forward, and it's not quite clear if this is within the half a page maximum branch distance. We therefore invert the branch condition to jump over a JMP instruction as equivalent to "...THEN80":

Lines 55 to 75 in the program send the ball "missile" flying - POKES,32 is performed by STA ($FD),Y at $11FE, with Y=1 to compensate the pre-decrement of S:

After S=S-22 (in $1200..$120C), 7746 is also decreased by 1 and compared with S (in $120D..$1216) - BCS $121F branches over the THEN clause, when S-1 >= 7746-1. The THEN clause sets G=0 and thus stops the missile at the top of the screen and then returns to the start of the game main loop at $117D.
The conditions of the IF statements in lines 70 and 71 are changed to check for a blank space (=32) rather than a meteor 'tile' (=160). Again, this is in preparation for the improvement with user defined graphics:

A sharp look at the two THEN clauses tells us, they're identical (safe for POKES,32 and G=0 being swapped over) - we therefore fuse them into one and place them behind to be branched to at $1233 - see the two branches at $1223 and $122A.
PEEK(S-1) is performed with Y=0 at address $1226.
Finally, the STA ($FD),Y at $122F puts the missile on screen and uses a branch on a known condition (NE) to jump over the deferred/fused THEN clauses:

... which is contained at $1233 .. $124D. As line 75 has already been coded at $122D..$122F, the GOTO 80 statement is redundant. The branch at $1231 is corrected to "BNE $124E".
Also, the JMP at address $11F7 can now be resolved, it also points to $124E - we therefore interrupt the assembly, disassemble the JMP at $11F7, and replace the target address:

The translation of line 80 is deferred to the meteor check loop. Instead the missile hit count in T (:= $1043) is compared with 12, then the meteor is history and we can print the "METEOR DESTROYED" message.
The trailing space in the first H command is replaced by a "X" placeholder (it would otherwise be ignored by the VICMON command line) - note the replacement by $20 at $1266 and - once again! - the $0D, $00 sequence at $1273.

The rest of the THEN clause in line 81 translates quite easily: a JSR to the delay subroutine, the meteor position is shifted two screen rows lower and then the game restarts in line 2, at address $111B.
We now also know the target address of the branch at $1253 (which branches over the somewhat longer THEN clause) - it's corrected to BNE $1292:

Lines 82..86 are now assembled to $1292..$12A1 with the same optimization that was used for the meteor redraw loop at $119A. Line 80 (F=1) 'hides' as LDX #$0F at $1294. The check loop runs backwards and "BNE $12A0" skips the THEN clause, which otherwise sets meteor tiles to a blank space when they've been hit. Thus "B(F)=32" literally translates to "STA $102F,X" as A is equal to 32 throughout the whole loop!

Line 28 (C=C+1) ends up at $12A3 and calculates the next position of the meteor in $FB/$FC. JMP $117D closes the game main loop.
Finally, we can resolve the big forward reference of line 36: the sound effect routine to call when the moon base is destroyed! The jump at $11D3 is corrected to "JMP $12AC".

Translation of the sound effect routine is straightforward. Just a few POKEs to the addresses $900D and $900E and three different delays, which are handled by the subroutine in $10F0:

The game is nearly finished, but we first save a raw dump of the game from $1010..$12CF. Note: the S command expects the end address + 1 as argument!
Check with a disassembly, that all these forward references are correct!
Code: Select all
., 10E0 BEQ $10E7
., 1180 BEQ $119A
., 118E BEQ $1192
., 1190 BCS $119A
., 11B1 BEQ $11D6
., 11D3 JMP $12AC
., 11DE BEQ $11F2
., 11E3 BNE $11F2
., 11F5 BNE $11FA
., 11F7 JMP $124E
., 1215 BCS $121F
., 1223 BNE $1233
., 122A BNE $1233
., 1231 BNE $124E
., 1253 BNE $1292
., 129B BNE $12A0
., 12A5 BNE $12A9
However, VICMON has the issue that it disturbs the workspace of the BASIC interpreter. We therefore perform a reset (or power cycle) beforehand:

After you input "2020 SYS 4352", LOAD"RAW.PRG",8,1 will not only load the previously typed-in machine code into fresh memory, it will also correct the pointer in 45/46 (end of program) - you can simply save the whole game with the SAVE command as shown.
RUN and ...

... play!

Cheers,
Michael
P.S. Keep the file "RAW.PRG" for the following session where we produce a version of Killer Comet with user defined graphics!