In part 4 of this series, Jason Pittman provided several variations of creating the attract screen:
Jason Pittman variation #1
If those four corners bother you, then my attempt will really kick in that OCD when you notice how wonky the colors are moving…Jason Pittman
10 CLS0:C=143:PRINT@268,"ATTRACT!"; 20 FOR ZZ=0TO1STEP0:FORX=0TO15:POKEX+1024,C:POKEX+1040,C:POKE1535-X,C:POKE1519-X,C:POKE1055+(32*X),C:POKE1472-(32*X),C:GOSUB50:NEXT:GOSUB50:NEXT 50 C=C+16:IF C>255 THEN C=143 60 RETURN
Jason Pittman variation #2
Also, another option using the substrings might be to fill the sides by printing two-character strings on the 32nd column so that a character spills over to the first column of the next line:Jason Pittman
10 CLS 0:C=143:OF=1:CH$="" 20 FOR X=0TO40:CH$=CH$+CHR$(C):GOSUB 90:NEXT 30 FOR ST=0TO1STEP0 40 PRINT@0,MID$(CH$,OF,31):GOSUB 120 50 FORX=31TO480STEP32:PRINT@X,MID$(CH$,OF,2);:GOSUB 120:NEXT 60 PRINT@481,MID$(CH$,OF,30);:GOSUB120 70 NEXT 80 REM ADVANCE COLOR 90 C=C+16:IF C>255 THEN C=143 100 RETURN 110 REM ADVANCE OFFSET 120 OF=OF+2:IF OF>7 THEN OF=OF-8 130 RETURN
Jason Pittman variation #3
One more try at O.C.D-compliant “fast”:Jason Pittman
10 DIM CL(24):FORX=0TO7:CL(X)=143+(X*16):CL(X+8)=CL(X):CL(X+16)=CL(X):NEXT 20 CLS0:FORXX=0TO1STEP0:FORYY=0TO7:FORZZ=1TO12:READPO,CT,ST,SR:FOR X=SRTOSR+CT-1:PO=PO+ST:POKE PO,CL(X+YY):NEXT:NEXT:RESTORE:NEXT:NEXT 180 REM POSITION,COUNT,STEP,START 190 DATA 1024,8,1,0,1032,8,1,0,1040,8,1,0,1048,6,1,0,1055,8,32,6,1311,6,32,6,1535,8,-1,4,1527,8,-1,4,1519,8,-1,4,1511,6,-1,4,1504,8,-32,2,1248,6,-32,2
The #3 variation using DATA statements is my favorite due to its speed. Great work!
The need for speed: Some assembly required.
It seems clear that even the fastest BASIC tricks presented so far are still not as fast as an attract screen really needs to be. When this happens, assembly code is the solution. There are also at least two C compilers for Color BASIC that I need to explore, since writing stuff in C would be much easier for me than 6809 assembly.
Shortly after part 4, I put out a plea for help with some assembly code that would rotate graphical color blocks on the 32 column screen. William “Lost Wizard” Astle answered that plea, so I’ll present the updated routine in his LWASM 6809 compiler format instead of as an EDTASM+ screen shot in the original article.
* lwasm attract32.asm -fbasic -oattract32.bas --map org $3f00 start ldx #1024 X points to top left of 32-col screen loop lda ,x+ load A with what X points to and inc X bpl skip if not >128, skip adda #16 add 16, changing to next color ora #$80 make sure high gfx bit is set sta -1,x save at X-1 skip cmpx #1536 compare X with last byte of screen bne loop if not there, repeat sync wait for screen sync rts done END
The code will scan all 512 bytes of the 32-column screen, and any byte that has the high bit set (indicating it is a graphics character) will be incremented to the next color. This would allow us to draw our attract screen border one time, then let assembly cycle through the colors.
How it works:
- The X register is loaded with the address of the top left 32-column screen.
- The A register is loaded with the byte that X points to, then X is incremented.
- BPL is used to skip any bytes that do not have the high bit set. This optimization was suggested by William. An 8-bit value can be treated as an unsigned value from 0-255, or as a signed value of -127 to 128. A signed byte uses the high bit to indicate a negative. Thus, a positive number would not have the high bit set (and is therefor not in the 128-255 graphics character range).
- If the high bit was set, then 16 is added to A.
- ORA is used to set the high bit, in case it was in the final color range (240-255) and had 16 added to it, turning it in to a non-graphics block. Setting the high bit changes it from 0-16 to 128-143.
- The modified value is stored back at one byte before where X now points. (This was another William optimization, since originally I was not incrementing X until after the store, using an additional instruction to do that.)
- Finally, we compare X to see if it has passed the end of screen memory.
- If it hasn’t, we do it all again.
- Finally, we have a SYNC instruction, that waits for the next screen interrupt. This is not really necessary, but it prevents flickering of the screen if the routine is being called too fast. (I’m not 100% sure if this should be here, or at the start of the code.)
The LWASM compiler has an option to generate a BASIC program full of DATA statements containing the machine code. You can then type that program in and RUN it to get this routine in memory. The command line to do this is in the first comment of the source code above.
10 READ A,B 20 IF A=-1 THEN 70 30 FOR C = A TO B 40 READ D:POKE C,D 50 NEXT C 60 GOTO 10 70 END 80 DATA 16128,16147,142,4,0,166,128,42,6,139,16,138,128,167,31,140,6,0,38,241,19,57,-1,-1
The program loads at $3f00 (16128), meaning it would only work on a 16K+ system. There is no requirement for that much memory, and it could be loaded anywhere else (even on a 4K system). The machine code itself is only 20 bytes. Since the code was written to be position independent (using relate branch instructions instead of hard-coded jump instructions), you could change where it loads just by altering the first two numbers in the DATA statement (start address, end address).
For instance, on a 4K CoCo, memory is from 0 to 4095. Since the assembly code only uses 20 bytes, one could load it at 4076, and use CLEAR 200,4076 to make sure BASIC doesn’t try to overwrite it. However, I found that the SYNC instruction hangs the 4K CoCo, at least in the emulator I am using, so to run on a 4K system you would have to remove that.
Here is the BASIC program modified for 4K. I added a CLEAR to protect the code from being overwritten by BASIC, changed the start and end addresses in the data statements, and altered the SYNC code to be an RTS (changing SYNC code of 19 to a 57, which I knew was an RTS because it was the last byte of the program in the DATA statements). This means it is wasting a byte, but here it is:
5 CLEAR 200,4076 10 READ A,B 20 IF A=-1 THEN 70 30 FOR C = A TO B 40 READ D:POKE C,D 50 NEXT C 60 GOTO 10 70 END 80 DATA 4076,4095,142,4,0,166,128,42,6,139,16,138,128,167,31,140,6,0,38,241,57,57,-1,-1
Using the code
Lastly, here is an example that uses this routine. I’ll use the BASIC loader for the 32K version, then add Jason’s variation #1 to it, modified by renaming it to start at line 100, and removing the outer infinite FOR Z loop so it only draws once. I’ll then add a GOTO loop that just executes this assembly routine over and over.
5 CLEAR 200,16128 10 READ A,B 20 IF A=-1 THEN 70 30 FOR C = A TO B 40 READ D:POKE C,D 50 NEXT C 60 GOTO 10 70 GOTO 100 80 DATA 16128,16147,142,4,0,166,128,42,6,139,16,138,128,167,31,140,6,0,38,241,19,57,-1,-1 100 CLS0:C=143:PRINT@268,"ATTRACT!"; 120 FORX=0TO15:POKEX+1024,C:POKEX+1040,C:POKE1535-X,C:POKE1519-X,C:POKE1055+(32*X),C:POKE1472-(32*X),C:GOSUB150:NEXT:GOSUB150 130 EXEC 16128:GOTO 130 150 C=C+16:IF C>255 THEN C=143 160 RETURN
And there you have it! An attract screen for BASIC that uses assembly so it’s really not a BASIC attract screen at all except for the code that draws it initially using BASIC.
I think that about covers it. And, this routine also looks cool on normal 32-column VDG graphics screens, too, causing the colors to flash as if there is palette switching in use. (You can actually palette switch the 32-column screen colors on a CoCo 3.)
Addendum: WAR by James Garon
On 7/2/2022, Robert Gault posted to the CoCo list a message titled “Special coding in WAR“. He mentioned some embedded data inside this BASIC program. You can download it as a cassette or disk image here:
You can even go to that link and click “Play Now” to see the game in action.
I found this particularly interesting because this BASIC program starts with one of the classic CoCo attract screens this article series is about. In the program, the author did two tricks: One was to embed graphics characters in a PRINT statement, and the other was to embedded a short assembly language routine in a string that would cycle through the screen colors, just like my approach! I feel my idea has been validated, since it was already used by this game in 1982. See it in action:
And if you are curious, the code in question starts at line 60000. I did a reply about this on the CoCo mailing list as I dug in to what it is doing. That sounds like it might make a part 6 of this series…
Until next time…