A sample programming session in VICMON

Basic and Machine Language

Moderator: Moderators

User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

Hi, y'all!

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
What follows now is a "single segment" session in VICMON. At this program size, this requires a good deal of ahead planning: laying out a memory map, preparing tables, identifying candidates of code snippets to be put into subroutines beforehand and spotting opportunities for optimization:
  • 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.
While doing so, we'll prepare the game a bit to use user defined graphics. :)


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:

Image

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:

Image

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!):

Image

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:

Image

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:

Image

... 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:

Image

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

Image

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:

Image

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:

Image

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:

Image

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

Image

... 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:

Image

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:

Image

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.

Image

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:

Image

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:

Image

... 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":

Image

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:

Image

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:

Image

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:

Image

... 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:

Image

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.

Image

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:

Image

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!

Image

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".

Image

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:

Image

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
As a final step, we prepend the game with a BASIC stub, so it can simply be started with RUN instead of a difficult to remember SYS call.

However, VICMON has the issue that it disturbs the workspace of the BASIC interpreter. We therefore perform a reset (or power cycle) beforehand:

Image

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 ...

Image

... play! :mrgreen:

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!
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

As a hint to those who are reluctant to type in the game listing from the preceding post: you will need roughly 1 1/2 hours for it.

I checked this by re-typing in the listing for myself from the screenshots, to ensure the game can actually be reconstructed this way.
Mike wrote:Keep the file "RAW.PRG" for the following session where we produce a version of Killer Comet with user defined graphics!
Here we go - after loading "RAW.PRG", we amend the game with a prepared character set (download charset.prg).

The file contains 1 blank space, 12 characters for the meteor graphics, 1 character for the moon base and 1 character for the missile. It loads to $1050, between BASIC stub and main game code. We will use a new routine to copy the new character set to $1D00, where a "POKE36869,255" then will access those characters in the range 32..46:

Image

Remember the initialisation data for the meteor in $1010..$101E? Instead of 160 (an inverse blank space, i.e. block, in the regular character set), the meteor now is drawn with the characters 33..44.

We had prepared 3 NOPs at $1100. These are now patched with JSR $12D0. The yet to be written routine at $12D0 will copy the character set into place.

".A 1103 LDA #$FF" changes the "POKE" to 36869 to POKE36869,255. This activates the new character set. ".A 115B LDA #$2D" and ".A 11A7 LDA #$2D" write the new character for the moon base into place. Finally, ".A 122D LDA #$2E" replaces the character for the missile:

Image

The patches of $111E, $1149, $11B6 and $1258 to $12 replace the (quasi neutral) RVS OFF character at the begin of the four text strings with RVS ON. With POKE36869,255, the non-inverse characters of the upper case character set can still be accessed by printing them in inverse. That means, all the text strings will appear like before, and it is not necessary to provide a RAM copy of the ROM character set for the letters!

A small loop at $12D0 .. $12DB copies the user defined graphics from $1050..10C7 to $1D00..$1D77.

Image

Believe or not, that's all to produce a new version of the "RAW.PRG" file, which we call "REFINED.PRG" and save it to - once again - build the complete game with BASIC stub from a freshly reset VIC-20:

Image

As in the preceding post, we first enter the line "2020 SYS 4352" and then load "REFINED.PRG" behind in memory. All pointers are correct (especially the one in 45/46), we save away the game as "KILLER.2.PRG" ...

... and RUN:

Image

This post concludes my VICMON primer series. Thanks for watching! ;)

Greetings,

Michael
Vic20-Ian
Vic 20 Scientist
Posts: 1214
Joined: Sun Aug 24, 2008 1:58 pm

Re: A sample programming session in VICMON

Post by Vic20-Ian »

Very Good. Thank you Mike.

I look forward to finding some time to work through these.

Ian
Vic20-Ian

The best things in life are Vic-20

Upgrade all new gadgets and mobiles to 3583 Bytes Free today! Ready
MartinC
Vic 20 Drifter
Posts: 33
Joined: Tue Oct 25, 2022 12:18 pm
Website: https://winterfam.co.uk
Location: Kent,uk
Occupation: Author

Re: A sample programming session in VICMON

Post by MartinC »

I just did this first lesson on real hardware, thx Mike!

20230211_143348.jpg

Do or do not there is no try :mrgreen:
Last edited by Mike on Sat Feb 18, 2023 5:28 am, edited 2 times in total.
Reason: Image attachment repaired - no need to place extra "img" tags around the "attachment" tags here!
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

MartinC wrote:I just did this first lesson on real hardware, [...]
Now go for the other ones! :wink:
MartinC
Vic 20 Drifter
Posts: 33
Joined: Tue Oct 25, 2022 12:18 pm
Website: https://winterfam.co.uk
Location: Kent,uk
Occupation: Author

Re: A sample programming session in VICMON

Post by MartinC »

So, I got as far as ballping Mike.

Understood the code and typed it into vicmon, faithfully (I think).

Code: Select all

[...]
When I ran it, the screen colour changed, the beeps started, but no ball!
So I copied it off, loaded it into vice and ran it... voila it worked.
Copied it from the pc back to my .d64 and it worked as expected....

What's going on, is my real Vic broken or is it me?

(mod: disassembly listing removed as the actual type-in work is part of the exercise. The issue at hand has been sorted out.)
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

MartinC wrote:When I ran it, the screen colour changed, the beeps started, but no ball!
BALLPING assumes screen RAM at $1E00. With a RAM expansion of at least +8K, screen RAM gets relocated to $1000 rendering the animation invisible.
MartinC
Vic 20 Drifter
Posts: 33
Joined: Tue Oct 25, 2022 12:18 pm
Website: https://winterfam.co.uk
Location: Kent,uk
Occupation: Author

Re: A sample programming session in VICMON

Post by MartinC »

Ohhhh... Der
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

MartinC wrote:Ohhhh... Der
Whatever that means.

My line of thought went alongside: "reason most probably a soft-loaded VICMON with a bigger RAM expansion during type-in, reloading the program later into an unexpanded VIC-20, so screen RAM then was at the correct place". As the program worked on the second attempt on the VIC-20 (and before that, also in VICE), the probability of a type-in error was fairly low. Likewise, having a turnaround transfer VIC -> PC -> VIC correct a program error had been an unlikely scenario in the first place.

BTW, VICMON has its own issues with bigger RAM expansions. When VICMON is soft-loaded, usually at least a +24K RAM expansion is present to cover BLK3, implying that screen RAM moves to $1000. Unfortunately, some routines in VICMON related to the bi-directional scrolling also assume screen RAM at $1E00, heavily messing up things with a relocated screen. In the worst case, unsuspecting data in $1E00..$1FFF is thrashed.

To avoid these issues, it is best to 'unexpand' the VIC-20 before soft-loading VICMON. You can use "3K.PRG" for this. Just do a LOAD"3K.PRG",8,1 - you'll then get the start-up message with 3583 BYTES FREE, with screen RAM at $1E00. This setting remains in place until the next reset or power cycle. Incidentally, the utility also protects the code of VICMON from being overwritten by BASIC string variables, which actually grow down from top of BASIC memory, and at some time could reach the $6000..$6FFF range where VICMON resides, down from $7FFF.

The extra RAM is still 'there' though, just BASIC can't access it. Enter LOAD"VICMON.PRG",8,1 (or with whatever name you had VICMON saved with) and follow up with NEW to correct some pointers of BASIC, and there you go.
MartinC
Vic 20 Drifter
Posts: 33
Joined: Tue Oct 25, 2022 12:18 pm
Website: https://winterfam.co.uk
Location: Kent,uk
Occupation: Author

Re: A sample programming session in VICMON

Post by MartinC »

Thanks Mike, just going to do the next lesson.
User avatar
MrSterlingBS
Vic 20 Enthusiast
Posts: 174
Joined: Tue Jan 31, 2023 2:56 am
Location: Germany,Braunschweig

Re: A sample programming session in VICMON

Post by MrSterlingBS »

Thanks a lot for the very good lession.

I read something in the KERNAL-ROM comments,...
It is also possible to jump directly to the PRINT ON SCREEN SUB ROUTINE $E742
This will save some cycles. :-)

BR Sven

Have fun!
User avatar
MrSterlingBS
Vic 20 Enthusiast
Posts: 174
Joined: Tue Jan 31, 2023 2:56 am
Location: Germany,Braunschweig

Re: A sample programming session in VICMON

Post by MrSterlingBS »

But i have a question.
Why can we use the data tables in the code? And why jumps the printing routine to the next data?
Is there an explanation about it?

BR Sven
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

MrSterlingBS wrote:It is also possible to jump directly to the PRINT ON SCREEN SUB ROUTINE $E742 This will save some cycles. :-)
Those few cycles saved are unlikely to make any noticable speed difference. And while it is also somewhat unlikely, that the examples here get ported to another computer, or have their output redirected to a file, changing character output from a standard KERNAL API call (JSR $FFD2) to a call in the midst of the KERNAL ROM loses these options without good reason (read this as: JSR $FFD2 is immediately recognizable as character output on all CBM 8-bit machines - to know what JSR $E742 does, you need to consult a VIC-20 ROM listing).
Why can we use the data tables in the code?
The data tables containing the position offsets and initial values of the meteor are not placed within the code area, please check their addresses in the left-most column. I just happen to write the data into memory (and at those other addresses) as I briefly interrupt the assembly at those places where code first references those data tables.
And why jumps the printing routine to the next data? Is there an explanation about it?
PRIMM is somewhat a standard idiom, which has not only been implemented on CBM computers, it is generic enough to be found on many other computers that use a 65xx CPU and the idea even works for other, non-65xx CPUs.

In a nutshell, the routine pops the return address off stack, internally stores it as running address, uses that running address to read bytes following the JSR call and sends these bytes to character output. The running address is advanced by 1 for each character read. When a 0 byte is read, the routine pushes the updated address back to the stack. RTS then lets the CPU continue the code execution behind the 0 byte.

What gets pushed by a JSR instruction on a 65xx CPU actually is the return address minus 1, so the main loop of the routine first advances the running address, and then reads the character. When a 0 byte has been read, exactly its address is then pushed back to stack. That 0 byte in turn happens to be at the address of the following code minus 1, so everything works out.

Other implementations of PRIMM also preserve the CPU registers A, X and Y but this involves somewhat more complicated shuffling/re-indexing within the stack - not necessarily worth the trouble, IMO.
Merytsetesh
Vic 20 Amateur
Posts: 40
Joined: Sat Mar 02, 2024 8:57 pm
Location: Canada

Re: A sample programming session in VICMON

Post by Merytsetesh »

Mike wrote: Fri Jan 25, 2019 4:28 pm The THEN clause inverts the sign of DX. That's actually the same as if we'd subtract the original value from zero, and that's how we do it: DX = -DX (= 0 - DX!). Let's not forget the POKE into 36876:
Thank you for this expedition into the heart of the processor :-)

One question, though. To get DX = -DX, why not XOR with 128 instead of using SBC? Wouldn't that be faster? Or am I misremembering my two's complement?...
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

Merytsetesh wrote:To get DX = -DX, why not XOR with 128 instead of using SBC?
That would only be correct with sign-magnitude representation.
Post Reply