See also: part 1, part 2, part 3, part 4 or part 5 (with more coming).
Is it really VIC-20 Tuesday again? Okay, then. Let’s get started…
The theory so far…
When we last left off, I had just described my theory about how my prototype Sky-Ape-Er game loaded as just one file which contained a custom character set — without being contained in DATA statements or anywhere in the BASIC code.
My theory was that I modified BASIC’s “start of variables” pointer (which normally points to just past the end of the BASIC code) so it was after the memory where the custom characters were stored. When saved, the file would contain the entire range of memory including those custom characters. When the program was LOADed and ran, the first thing it had to do was set the “start of variables” pointer back to where it needed to be, just after the BASIC code.
Today I want to test that theory by trying to create a standalone BASIC program that contains custom character set data. I am going to use the excellent CBM prg Studio development environment to make a BASIC project that will have three things:
- A custom character set. I will use the editor to export the characters out as DATA statements into a BASIC file.
- That new file will be turned in to a program that will READ the DATA statements and POKE the values into RAM memory.
- Finally, I will have a simple test program that will do the necessary POKEs to enable RAM characters and animate them.
Since I haven’t owned a VIC-20 since 1983, I am going to do all of this in the VICE VIC-20 emulator. To do it like I did it back in 1982, I am going to use a virtual cassette tape for program storage. I could probably do this easier using an emulated disk drive, but never had a disk drive on my VIC-20 and I want to keep this as virtually real as possible.
Except for the whole part of using a Mac and virtual PC for development, of course.
Step 1: Custom characters and loader program.
Using CBM prg Studio’s character set editor, I created a few custom characters:
I then used the “Character Set -> Export -> To Listing” option to output the DATA statements containing those characters.
I then added the following code to load the DATA statements into memory, and display them to verify they work.
0 rem custom charset 1 rem protect chars 2 rem from basic. 3 REM set string end 4 POKE51,0:POKE52,28 5 REM set memory end 6 POKE55,0:POKE56,28 7 REM clear vars 8 CLR 10 for l=7168 to 7168+12*8-1 15 read v:poke l,v 20 next 25 rem clear 'space' 30 for l=7424 to 7432 35 poke l,0:next 40 print"{clear}{reverse on}charset:{reverse off}" 45 print" a b c d" 50 print" e g i" 55 print"@f@h@j@k@" 60 poke 36869,255 65 get a$ 70 if a$="" then 65 75 poke 36869,240 80 print l and 255;int(l/256) 85 end 1000 DATA 255,2,4,8,16,32,255,255 1010 DATA 0,28,46,71,142,92,56,0 1020 DATA 16,40,68,98,118,62,28,8 1030 DATA 0,28,58,113,226,116,56,0 1040 DATA 16,56,124,110,70,34,20,8 1050 DATA 96,240,96,62,185,89,28,30 1060 DATA 189,68,132,66,33,0,255,255 1070 DATA 24,60,24,126,189,189,189,60 1080 DATA 189,36,36,36,102,0,255,255 1090 DATA 6,15,6,124,157,154,56,120 1100 DATA 189,34,33,66,132,0,255,255 1110 DATA 255,102,129,66,138,150,223,255
Here is what it is doing:
- Lines 4 and 6 – These POKEs are used to protect the characters in memory so BASIC will not override them. They set the highest memory location that BASIC and strings can use. I set them to 7168, the address where the custom characters load.
- Line 10 to 20 – FOR/NEXT loop of READ and POKE the first 8 bytes where character RAM will be. This is where the “@” symbol is (character 0).
- Line 30 to 35 – These POKEs clear out the “space” character in the custom character set. I do this so my DATA statements don’t have to contain all the characters up to space.
- Line 40 to 55 – Clear screen then print reverse text (which will still show up even after we switch to RAM character mode) and the custom characters.
- Line 60 – Set VIC chip to use RAM starting at 7168 for custom characters. At this point, the screen will show my custom characters, and the reverse video should appear as normal text.
- Line 65 and 70 – Wait for key to be pressed.
- Line 75 – Set VIC chip to use normal ROM area for characters.
- Line 80 – Print the two bytes that represent the last memory location used by the character set. These will be POKEd into 45 and 46 before SAVING the demo program later.
- Line 85 – End.
- Line 1000 to 1110 – Each line has eight bytes that make up a custom character.
Here is what it looks like when it runs:
Then when you press enter, it disables the custom characters and you will see it says “CHAR:” in reverse view with letters a-i and @ where the custom characters were. It then prints two numbers, which I need to write down. Those numbers represent the address of the end of the custom characters my test program uses.
I will build this into a “.prg” file, and then load that into VICE. Next, I will “Create and attach an empty tape image” (I called mine “Custom Char Demo.tap“) and then save this loader program to that virtual tape:
SAVE "CHAR SET LOAD"
Step 2: Program to use the custom characters.
The next part will be a standalone program that will make use of these characters. I am creating a simple demo where spinning bricks fall from the sky and a player character on a sidewalk below has to dodge them. Except nothing happens if a brick hits the player because this is just a demo.
Here is my demo program:
0 rem charset demo 1 REM set vars start 2 POKE45,104:POKE46,19 3 REM set string end 4 POKE51,0:POKE52,28 5 REM set memory end 6 POKE55,0:POKE56,28 7 REM clear vars 8 CLR 9 for l=7424 to 7432 10 poke l,0:next 11 REM charset in RAM 12 POKE 36869,255 13 rem 100 print "{clear}{down*20}"; 105 print "@@@@@@@@@@@@@@@@@@@@@@"; 110 for l=38400 to 38911:poke l,0:next 115 rem init bricks 120 for b=0 to 3:bl(b)=7680+rnd(1)*22+88*b:bc(b)=1+b:next 125 rem init player 130 p1=8109:p2=8131:pt=7:pb=8 135 rem main loop 140 pokep1,pt:pokep2,pb 145 for b=0 to 3:poke bl(b),32 150 bl(b)=bl(b)+22:if bl(b)>8120 then bl(b)=7680+rnd(1)*22 155 bc(b)=bc(b)+1:if bc(b)>4 then bc(b)=1 160 poke bl(b),bc(b) 165 next 170 get a$ 175 if a$="a" then if p2>8119 then pokep1,32:pokep2,0:p1=p1-1:p2=p2-1:pt=5:pb=6:goto 140 180 if a$="s" then if p2<8141 then pokep1,32:pokep2,0:p1=p1+1:p2=p2+1:pt=9:pb=10:goto 140 185 if a$="q" then 510 190 pt=7:pb=8 195 goto 140 500 REM charset in ROM 510 POKE 36869,240 520 END 1000 PRINT PEEK(45);PEEK(46)
Here is what it is doing… Actually, I’ll skip the demo logic and just mention a few important things:
- Line 1000 – This prints the programs’ current end (start of variables). Since I need the program to restore this when it loads (after being saved with the custom characters), I can load this program and “RUN 1000” to get those values. I then change the POKEs in line 2 to match those values. Thus, when the real program is loaded, it will fix those pointers which will get messed up by the SAVE process.
Thus, I would load this program into memory (but NOT run it) and do “RUN 1000” and note those numbers. I changed the POKEs on line 2 to match those values. Then I saved this after the “CHAR SET TEST” program as:
SAVE "CHAR SET TEST"
Step 3: Save the all-in-one test and charset file.
Now I reset the virtual VIC and rewind the virtual tape. Here are the steps:
- LOAD and RUN the “CHAR SET LOAD” program to get the character set in memory. I make a note of the two numbers printed out at the end.
- LOAD (but DO NOT run) the “CHAR SET TEST” program.
- With the TEST program in memory, I do the following POKEs to change the end of BASIC pointer:
POKE 45,X:POKE 46,Y
…where X is the first number the loader program printed and Y is the second number the loader program printed. - I now can SAVE the test program and it should save all of the BASIC and continue saving until it gets to the end of RAM.
SAVE "CHAR SET DEMO"
Step 4: Test!
After a reboot, and rewind of the virtual tape, I try loading the “CHAR SET DEMO” program and running it…
Oh no! My theory is not correct. Something is still wrong. Running this program produces parts of the custom character, but not all. It’s clear I am off somewhere.
What am I doing wrong? I guess I’m gonna need a part 6. . .
Until next time…
Haven’t tried it myself, but what happens when basic runs out of variable space? Can load even display the file name string or does basic need room for a temporary string?
As you only use a few user defined chars, and not the total possible 64 with that memory configuration, you might want to set end of basic and start of variables just after the last actually used user defined char (char33, screen code 32, space?) so basic at least has about 120 bytes for temporary string storage. Don’t forget to do a CLR. In the program that uses the chars you probably need to poke the start of variables and possible do a clr before you also poke the top of variable / string space and do a second clr, just to make sure there is always free space for any temporary stuff. Maybe poke can do with two constants without any such temp space though – not sure where poke stores it’s address while it converts the second parameter to binary – probably somewhere in zero page so the cpu can do a zero page indirect address mode thing.
I considered this. My character set data loader prints a value at the end which is the last character, then I POKE to that (I may need to update my article text). It starts variables and still has room. But, what about strings? I wouldn’t think it would save any pointers to strings with the program since they don’t exist until runtime. It only needs to know start and end of the program then it runs and initializes from there. I made one that worked early on, then started writing about it and cannot remember what step I missed.
P.S. just came to think about another thing: Instead of the classic read-data loop with data statements, you might point the kernal screen editor to the user defined graphics area and use print to form the data. As print (with rvs on and rvs off) can output all 256 possible screen codes, it must be able to write any binary data to ram. That seems far more space and speed efficient than the read-data loop. With read-data look each entry has to be 1-4 bytes (if a blank is allowed as a 0, otherwise 2-4 bytes) while print would use 1-2 bytes (2 bytes each time the high bit differs between two consecutive “chars”, otherwise 1 byte), and of course printing strings is far faster than a read-poke-loop.
interesting ideas. I could do the same self modifying code to get the data within the quotes, but can all characters be used? There has to be a delimiter to know when the quoted string ends, yes? I did see POKEs that change where it outputs, so making those go somewhere else looks easy to do.
Totally forgot that you can’t write a quote mark any other way than using CHR$, and that is true both for inverted and non inverted quotation marks. So those would take up quite a few more bytes. Every other byte should be printable though. As there are 256 petscii codes but only 128 screen codes plus 128 inverted screen codes, there are enough petscii codes to have all the formatting “chars” like CR, CLR, HOME, color, RVS ON/OFF and so on without interfering with the ability to print all chars. Had inverted chars been a part of some attribute memory, like on a PC, you would had ran out of possible codes, which is what happens on a PC (where you can’t print the first chars using for example print in basic).
Pingback: VIC-20 “smooth move”. | Sub-Etha Software