Version 2 of this code added support for moving the cursor UP, DOWN, LEFT and RIGHT. BUT, it was processing these special characters at all times, including when you typed them in. This was confusing to me, so I wanted to add some way to make them only process when the program is PRINTing them, not when you are just typing them from a command line.
I was unsure how to approach this but ended up looking at CURLIN. This is what line number BASIC is processing or &HFFFF if in “direct mode” (where you type in program lines).
From checking the disassembly, I should be able to add a quick check when deciding to process the special characters and only do that if CURLIN was &HFFFF. The code I added looked like this:
newcode ...snip...
* Do this only if NOT in Direct mode. Problem: After a BREAK, CURLIN * has not been updated yet, so the very first line you type will be * processing the special characters. Lines after that will not. Trying * to find a different way to detect this. pshs a save A lda CURLIN GET CURRENT LINE NUMBER (CURLIN) inca TEST FOR DIRECT MODE puls a restore A beq continue if 0, in direct mode.
...snip...
I was unsure what registers I could use, so I saved A and used it, then restored it. If you look at the ROM code, you will see sometimes it will load the bull 16-bit value and check against FFFF, and other times it just loads the first byte and checks that. I decided to do it that way.
This code loads the first byte then increments it. If it was FF, then it should increment/wrap around back to 0, which will set the zero bit (I think) and match a BEQ (branch if equal). I expect there is a better way to do this, but this is what I came up with.
Now, typing the lowercase “u”, “d”, “l” and “r” do not move the cursor around the screen, but PRINTing them in a program will. The downside is if you try to type a PRINT from the command line, it will not be processed — only the PRINTs from the program when it is RUNning.
Is this good enough?
Ah, but there was still a problem. When BASIC returns from running a program, I found it would still process those characters! Then it would stop. I eventually figured out it would process the FIRST line after returning to direct mode, then would behave as I wanted it after that.
I found this code in the ROM:
* THIS IS THE MAIN LOOP OF BASIC WHEN IN DIRECT MODE LAC73 JSR LB95C MOVE CURSOR TO START OF LINE LDX #LABE1-1 POINT X TO ‘OK’, CR MESSAGE JSR LB99C PRINT ‘OK’, CR LAC7C JSR LA390 GO GET AN INPUT LINE LDU #FFFF THE LINE NUMBER FOR DIRECT MODE IS $FFFF STU CURLIN SAVE IT IN CURLIN
For “some reason”, the routine first jumps (JSR) to the code to read a line, THEN it sets the direct mode line number to FFFF. This means the first line you type still has CURLIN set to the last line the program executed. I am sure there is reason for this, but that explains the problem.
I now present the full version of the code, but will show a way William Astle suggested I make it more better in the next installment:
* Allow embedded characters to move the cursor in a PRINT.
UP equ 'u character for up DOWN equ 'd character for down LEFT equ 'l character for left RIGHT equ 'r character for right
CURLIN equ $68 *PV CURRENT LINE # OF BASIC PROGRAM, $FFFF = DIRECT DEVNUM equ $6f device number being used for I/O CURPOS equ $88 location of cursor position in RAM RVEC3 equ $167 console out RAM hook VIDRAM equ $400 VIDEO DISPLAY AREA
org $7f00
init lda RVEC3 get op code sta savedrvec save it ldx RVEC3+1 get address stx savedrvec+1 save it
lda #$7e op code for JMP sta RVEC3 store it in RAM hook ldx #newcode address of new code stx RVEC3+1 store it in RAM hook
rts done
uninstall * TODO
newcode * Do this only if DEVNUM is 0 (console) tst DEVNUM is DEVNUM 0? bne continue not device #0 (console)
* Do this only if NOT in Direct mode. Problem: After a BREAK, CURLIN * has not been updated yet, so the very first line you type will be * processing the special characters. Lines after that will not. Trying * to find a different way to detect this. pshs a save A lda CURLIN GET CURRENT LINE NUMBER (CURLIN) inca TEST FOR DIRECT MODE puls a restore A beq continue if 0, in direct mode.
leas 2,s remove PC from stack since we won't be returning there.
* Now this is the start of what Color BASIC ROM does for PUTCHR: * PUT A CHARACTER ON THE SCREEN LA30A PSHS X,B,A SAVE REGISTERS LDX CURPOS POINT X TO CURRENT CHARACTER POSITION
checkup cmpa #UP bne checkdown CMPX #VIDRAM+32 second line or lower? blt goLA35D disallow if on top line. leax -32,x move up one line bra done
checkdown cmpa #DOWN bne checkleft cmpx #VIDRAM+512-32 bge goLA35D disallow if on bottom line. leax 32,X move down one line bra done
checkleft cmpa #LEFT bne checkright cmpx #VIDRAM top left of screen? beq goLA35D leax -1,X move left one character bra done
checkright cmpa #RIGHT bne goLA30E cmpx #VIDRAM+511 bottom right of screen beq goLA35D leax 1,x increment X, skipping that location. bra done
goLA30E jmp $A30E jump back into Color BASIC ROM code.
done stx CURPOS update cursor position goLA35D jmp $A35D jump back into Color BASIC ROM code.
continue savedrvec rmb 3 call regular RAM hook rts just in case...
end
And here is a BASIC loaded:
5 CLEAR 200,&H7F00
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 32512,32616,182,1,103,183,127,105,190,1,104,191,127,106,134,126,183,1,103,142,127,24,191,1,104,57,13,111,38,77,52,2,150,104,76,53,2,39,68,50,98,52,22,158,136,129,117,38,10,140,4,32,45,50,48,136,224,32,43,129,100,38,10,140,5,224,44,36
90 DATA 48,136,32,32,29,129,108,38,9,140,4,0,39,22,48,31,32,16,129,114,38,9,140,5,255,39,9,48,1,32,3,126,163,14,159,136,126,163,93,32620,32620,57,-1,-1
One thing that bugs me about this series is the title. We are not actually hacking the PRINT command. Instead, we are changing the CHROUT character output routine. Close enough, I guess, since PRINT uses this routine…
Now let’s take a tangent to my “Attract Screen” series of posts from a few years ago, specifically the final installment:
In that series, I was playing with ways to create the classic CoCo attract screens with the colored blocks that went around the screen:
War by James Garon (title screen)
In that article, Robert Gault had pointed me to this specific “WAR!” program sold by Radio Shack. It had embedded stuff in a PRINT command. I dissected those lines. I found some contained embedded CHR$ graphics characters, and another contained an assembly language routine. Very cool BASIC hacking.
In BASIC, there is an “uncrunch” routine that converts tokenized BASIC to full-text output via the LIST command. LIST will not have a good time LISTing such programs. In BASIC, any byte with the high-bit set (128-255 value) is seen as a token and the tokenizer takes over to convert that one (or two) byte sequence to a word.
Instead of showing a CHR$(128) graphics block, LIST will show the token keyword that is 128.
This led me to wonder if I could create a patch for BASIC that would allow it to properly LIST lines with embedded characters like this. And I did. Here is the test program I wrote back then and completely forgot about until a few days ago.
UPPERCASE stuff is taken from the Color BASIC ROM code. You will see I had to clone most of the token parsing code in my program so I could modify its behavior.
* lwasm uncrunch.asm -fbasic -ouncrunch.bas --map
*
* 0.00 2022-07-04 allenh - initial klunky version.
*
* Allow LIST to display graphics characters inside of quoted strings.
RVEC24 equ $1A6 UNCRUNCH BASIC LINE RAM hook
COMVEC EQU $0120 Some BASIC locations we need.
LINBUF EQU $02DC
SKP2 EQU $8C
LBUFMX EQU 250
org $3f00
init
lda RVEC24 get op code
sta savedrvec save it
ldx RVEC24+1 get address
stx savedrvec+1 save it
lda #$7e op code for JMP
sta RVEC24 store it in RAM hook
ldx #newcode address of new code
stx RVEC24+1 store it in RAM hook
rts done
newcode
* UNCRUNCH A LINE INTO BASIC'S LINE INPUT BUFFER
LB7C2
clr AREWEQUOTED
*JSR >RVEC24 HOOK INTO RAM
LEAS 2,S Remove JSR from stack
LEAX 4,X MOVE POINTER PAST ADDRESS OF NEXT LINE AND LINE NUMBER
LDY #LINBUF+1 UNCRUNCH LINE INTO LINE INPUT BUFFER
LB7CB
LDA ,X+ GET A CHARACTER
LBEQ LB820 BRANCH IF END OF LINE
* Check for quote/unquote
cmpa #34 Is A a quote character?
bne quotedone
togglequote
tst AREWEQUOTED
bne quoteoff
quoteon
inc AREWEQUOTED
bra quotedone
quoteoff
clr AREWEQUOTED Toggle quote mode off.
quotedone
tst AREWEQUOTED
beq notquoted
quoted
* If we are quoted, just store whatever it is.
lda -1,x
CMPY #LINBUF+LBUFMX TEST FOR END OF LINE INPUT BUFFER
BCC LB820 BRANCH IF AT END OF BUFFER
*ANDA #$7F MASK OFF BIT 7
STA ,Y+ * SAVE CHARACTER IN BUFFER AND
CLR ,Y * CLEAR NEXT CHARACTER SLOT IN BUFFER
BRA LB7CB GET ANOTHER CHARACTER
notquoted
lda -1,x
LBMI LB7E6 BRANCH IF IT'S A TOKEN
CMPA #': CHECK FOR END OF SUB LINE
BNE LB7E2 BRNCH IF NOT END OF SUB LINE
LDB ,X GET CHARACTER FOLLOWING COLON
CMPB #$84 TOKEN FOR ELSE?
BEQ LB7CB YES - DON'T PUT IT IN BUFFER
CMPB #$83 TOKEN FOR REMARK?
BEQ LB7CB YES - DON'T PUT IT IN BUFFER
FCB SKP2 SKIP TWO BYTES
LB7E0
LDA #'! EXCLAMATION POINT
LB7E2
BSR LB814 PUT CHARACTER IN BUFFER
BRA LB7CB GET ANOTHER CHARACTER
* UNCRUNCH A TOKEN
LB7E6
LDU #COMVEC-10 FIRST DO COMMANDS
CMPA #$FF CHECK FOR SECONDARY TOKEN
BNE LB7F1 BRANCH IF NON SECONDARY TOKEN
LDA ,X+ GET SECONDARY TOKEN
LEAU 5,U BUMP IT UP TO SECONDARY FUNCTIONS
LB7F1
ANDA #$7F MASK OFF BIT 7 OF TOKEN
LB7F3
LEAU 10,U MOVE TO NEXT COMMAND TABLE
TST ,U IS THIS TABLE ENABLED?
BEQ LB7E0 NO - ILLEGAL TOKEN
LB7F9
SUBA ,U SUBTRACT THE NUMBER OF TOKENS FROM THE CURRENT TOKEN NUMBER
BPL LB7F3 BRANCH IF TOKEN NOT IN THIS TABLE
ADDA ,U RESTORE TOKEN NUMBER RELATIVE TO THIS TABLE
LDU 1,U POINT U TO COMMAND DICTIONARY TABLE
LB801
DECA DECREMENT TOKEN NUMBER
BMI LB80A BRANCH IF THIS IS THE CORRECT TOKEN
* SKIP THROUGH DICTIONARY TABLE TO START OF NEXT TOKEN
LB804
TST ,U+ GRAB A BYTE
BPL LB804 BRANCH IF BIT 7 NOT SET
BRA LB801 GO SEE IF THIS IS THE CORRECT TOKEN
LB80A
LDA ,U GET A CHARACTER FROM DICTIONARY TABLE
BSR LB814 PUT CHARACTER IN BUFFER
TST ,U+ CHECK FOR START OF NEXT TOKEN
BPL LB80A BRANCH IF NOT DONE WITH THIS TOKEN
BRA LB7CB GO GET ANOTHER CHARACTER
LB814
CMPY #LINBUF+LBUFMX TEST FOR END OF LINE INPUT BUFFER
BCC LB820 BRANCH IF AT END OF BUFFER
ANDA #$7F MASK OFF BIT 7
STA ,Y+ * SAVE CHARACTER IN BUFFER AND
CLR ,Y * CLEAR NEXT CHARACTER SLOT IN BUFFER
LB820
RTS
* Unused at the moment.
savedrvec rmb 3 call regular RAM hook
rts just in case...
AREWEQUOTED rmb 1
end $3f00
And here is a BASIC loader for this routine:
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 EXEC 16128 80 DATA 16128,16290,182,1,166,183,63,163,190,1,167,191,63,164,134,126,183,1,166,142,63,24,191,1,167,57,127,63,167,50,98,48,4,16,142,2,221,166,128,16,39,0,121,129,34,38,13,125,63,167,38,5,124,63,167,32,3,127,63,167,125,63,167,39,14,166,31,16,140 90 DATA 3,214,36,91,167,160,111,164,32,214,166,31,16,43,0,21,129,58,38,13,230,132,193,132,39,198,193,131,39,194,140,134,33,141,48,32,187,206,1,22,129,255,38,4,166,128,51,69,132,127,51,74,109,196,39,231,160,196,42,246,171,196,238,65,74,43,6,109 100 DATA 192,42,252,32,247,166,196,141,6,109,192,42,248,32,141,16,140,3,214,36,6,132,127,167,160,111,164,57,16294,16294,57,-1,-1
If you RUN that, then install it using EXEC &H3F00, you can now LIST and see characters embedded in strings:
I bring this up now because 1) I forgot to post about it back in 2022, and 2) because I think I want to do something similar with my cursor movement PRINT patch. Ideally, you should be able to LIST a program and see the original characters in it, and just have them move around when the program is running and PRINTs those characters. This matches how the VIC-20 worked with embedded characters inside a PRINT:
* Allow LIST to display graphics characters inside of quoted strings.
RVEC24 equ $1A6 UNCRUNCH BASIC LINE RAM hook
COMVEC EQU $0120 Some BASIC locations we need. LINBUF EQU $02DC SKP2 EQU $8C LBUFMX EQU 250
The start is very similar to the consmove.asm code presented in the first parts of this series. RVEC24 is the RAM hook for the UNCRUNCH routine. Microsoft made this available so future BASICs (like Extended, Disk, etc.) could support LISTING their new tokens, I assume.
LINBUF is the BASIC input buffer. This is where things go when you are typing at the OK prompt.
SKP2 is a thing Microsoft used in the ROM as a shortcut to skip two bytes. It is $8C, which is “CMPX #”. It seems they could place that in the code and it would be an “Load X” then whatever two bytes were behind it, allowing a shortcut to branch over those two bytes. Can anyone explain this better (or more accurately, or just accurately)? Leave a comment if you can.
And lastly was a define that was the max size of the line buffer – 250 bytes. I have not looking into why it was 250 instead of, say, 255, but this is as many characters as you can type before input stops and you can only backspace or press ENTER.
org $3f00
init
lda RVEC24 get op code
sta savedrvec save it
ldx RVEC24+1 get address
stx savedrvec+1 save it
lda #$7e op code for JMP
sta RVEC24 store it in RAM hook
ldx #newcode address of new code
stx RVEC24+1 store it in RAM hook
rts done
This is the code that installs the RAM hook. It should look close to what I have in the previous routine, just with a different vector.
Now we get to the new code. For this I needed to duplicate some code in the BASIC ROM. The code in UPPERCASE represents code I brought in from the Unravelled disassembly listing. The ROM code has no provision for doing something different if it is a quoted string, so I needed to use the original code, with some extra code around that it turns on or off the detokenizer if we are listing something within quotes.
Things are about to get messy…
newcode
* UNCRUNCH A LINE INTO BASIC'S LINE INPUT BUFFER
LB7C2
clr AREWEQUOTED
*JSR RVEC24 HOOK INTO RAM
LEAS 2,S Remove JSR from stack
LEAX 4,X MOVE POINTER PAST ADDRESS OF NEXT LINE AND LINE NUMBER
LDY #LINBUF+1 UNCRUNCH LINE INTO LINE INPUT BUFFER
LB7CB
LDA ,X+ GET A CHARACTER
LBEQ LB820 BRANCH IF END OF LINE
This code is mostly from the BASIC ROM except for that first “clr”. I reserve a byte of memory to use as a flag for when we are or are not parsing something in quotes. If a quote is seen, that flag is set. It stays set until another quote is seen or the end of line is reached. The commented-out line is in the original ROM and that would be the line that jumps to this hook. I kept the line in for clarity, but it is not used in my function.
Now we have my custom code. If we are not at the end of line, I check for quotes and turn the flag on or off:
* Check for quote/unquote
cmpa #34 Is A a quote character?
bne quotedone
togglequote
tst AREWEQUOTED
bne quoteoff
quoteon
inc AREWEQUOTED
bra quotedone
quoteoff
clr AREWEQUOTED Toggle quote mode off.
quotedone
tst AREWEQUOTED
beq notquoted
This flag will be used in a moment. I clone some of the ROM code that outputs the un-crunched line but will bypass the un-crunch for items within quotes.
quoted
* If we are quoted, just store whatever it is.
lda -1,x
CMPY #LINBUF+LBUFMX TEST FOR END OF LINE INPUT BUFFER
BCC LB820 BRANCH IF AT END OF BUFFER
*ANDA #$7F MASK OFF BIT 7
STA ,Y+ * SAVE CHARACTER IN BUFFER AND
CLR ,Y * CLEAR NEXT CHARACTER SLOT IN BUFFER
BRA LB7CB GET ANOTHER CHARACTER
For quoted items, A is loaded with a character. Then the ROM code runs but I commented out the thing that would mask off bit 7. Tokens have that bit set, but I wanted to leave high-bit bytes alone.
This batch of code is more Color BASIC ROM code I duplicated here after that first “lda” line:
notquoted lda -1,x
LBMI LB7E6 BRANCH IF IT'S A TOKEN CMPA #': CHECK FOR END OF SUB LINE BNE LB7E2 BRNCH IF NOT END OF SUB LINE LDB ,X GET CHARACTER FOLLOWING COLON CMPB #$84 TOKEN FOR ELSE? BEQ LB7CB YES - DON'T PUT IT IN BUFFER CMPB #$83 TOKEN FOR REMARK? BEQ LB7CB YES - DON'T PUT IT IN BUFFER FCB SKP2 SKIP TWO BYTES LB7E0 LDA #'! EXCLAMATION POINT LB7E2 BSR LB814 PUT CHARACTER IN BUFFER BRA LB7CB GET ANOTHER CHARACTER
* UNCRUNCH A TOKEN LB7E6 LDU #COMVEC-10 FIRST DO COMMANDS CMPA #$FF CHECK FOR SECONDARY TOKEN BNE LB7F1 BRANCH IF NON SECONDARY TOKEN LDA ,X+ GET SECONDARY TOKEN LEAU 5,U BUMP IT UP TO SECONDARY FUNCTIONS LB7F1 ANDA #$7F MASK OFF BIT 7 OF TOKEN LB7F3 LEAU 10,U MOVE TO NEXT COMMAND TABLE TST ,U IS THIS TABLE ENABLED? BEQ LB7E0 NO - ILLEGAL TOKEN LB7F9 SUBA ,U SUBTRACT THE NUMBER OF TOKENS FROM THE CURRENT TOKEN NUMBER BPL LB7F3 BRANCH IF TOKEN NOT IN THIS TABLE ADDA ,U RESTORE TOKEN NUMBER RELATIVE TO THIS TABLE LDU 1,U POINT U TO COMMAND DICTIONARY TABLE LB801 DECA DECREMENT TOKEN NUMBER BMI LB80A BRANCH IF THIS IS THE CORRECT TOKEN * SKIP THROUGH DICTIONARY TABLE TO START OF NEXT TOKEN LB804 TST ,U+ GRAB A BYTE BPL LB804 BRANCH IF BIT 7 NOT SET BRA LB801 GO SEE IF THIS IS THE CORRECT TOKEN LB80A LDA ,U GET A CHARACTER FROM DICTIONARY TABLE BSR LB814 PUT CHARACTER IN BUFFER TST ,U+ CHECK FOR START OF NEXT TOKEN BPL LB80A BRANCH IF NOT DONE WITH THIS TOKEN BRA LB7CB GO GET ANOTHER CHARACTER LB814 CMPY #LINBUF+LBUFMX TEST FOR END OF LINE INPUT BUFFER BCC LB820 BRANCH IF AT END OF BUFFER ANDA #$7F MASK OFF BIT 7 STA ,Y+ * SAVE CHARACTER IN BUFFER AND CLR ,Y * CLEAR NEXT CHARACTER SLOT IN BUFFER LB820 RTS
* Unused at the moment.
savedrvec rmb 3 call regular RAM hook rts just in case...
AREWEQUOTED rmb 1
end $3f00
Looking at this now, I expect I had to duplicate all this ROM code because I need to stay in the un-crunch loop for the complete line. If you look at the ROM code and see a better approach, please leave a comment.
I also think this may be wrong, since I do not see it calling the original vector when complete. This may not work for tokens added by Extended or Disk BASIC. We will have to fix that at some point.
For now, I will just leave this here as an example, and then figure out how I could do something similar with my cursor move codes so they LIST showing the codes, and only move around the screen when PRINTing them.
This should be interesting… The way I patched CHROUT, it has no idea if the character is from LIST or PRINT. I will have to figure out how to solve that, next.
Keep this in mind as I continue my exploration of using Color BASIC RAM hooks to change how the PRINT output works.
Previously, I created a small assembly language routine that would intercept BASIC’s output to the 32 column screen and then treat a lowercase “r” (as in “right”) as a transparent space. My proof-of-concept seemed to work.
Today, I present updated code that now supports “u” for up, “d” for down, “l” for left, and “r” for right, semi-simulating how you can embed cursor movements in a PRINT command on a VIC-20 (and, I assume, the C64 and maybe even the PET before it).
Let me walk you through what I came up with:
* lwasm consmove2.asm -fbasic -oconsmove2.bas --map
* Convert any lowercase characters written to the
* screen (device #0) to uppercase.
UP equ 'u character for up
DOWN equ 'd character for down
LEFT equ 'l character for left
RIGHT equ 'r character for right
I start with some definitions of the characters I want to use to move the cursor UP, DOWN, LEFT or RIGHT. I wanted to be able to easily change these characters. In a future installment, I may try to implement this using “DEF USR” so you could do something like X=USR0(“abcd”) and pass in a string containing four characters to use for up, down, left and right. For now, hard-coded.
DEVNUM equ $6f device number being used for I/O
CURPOS equ $88 location of cursor position in RAM
RVEC3 equ $167 console out RAM hook
VIDRAM equ $400 VIDEO DISPLAY AREA
These next defines are things that the Color BASIC Unravelled disassembly listings had defined. The first is the memory location that contains the current device number for output (0 for screen, -2 to printer, etc.). After that is where BASIC tracks the cursor position on the 32 column screen. Neat is the address of the RAM Vector for the console out routine. Lastly, VIDRAM is the start of the 32-column screen in memory (1024, or &H400 in hex).
org $7f00
init
lda RVEC3 get op code
sta savedrvec save it
ldx RVEC3+1 get address
stx savedrvec+1 save it
lda #$7e op code for JMP
sta RVEC3 store it in RAM hook
ldx #newcode address of new code
stx RVEC3+1 store it in RAM hook
rts done
uninstall
* TODO
Here, I changed the start address of this program to &H7F00 instead of &H3F00. If trying this on a 16K machine, you would have to change it back. Once I know how big the final assembly code is, I’ll probably move it as close as possible to the end of the 32K RAM used by BASIC so it can leave as much RAM for the BASIC program as possible.
This init routine pulls out the three bytes at RVEC3 (which are either “rts” three times for Color BASIC, or patched to be a “JMP xxxx” 3-byte instruction if other BASIC ROMs (Extended, Disk, CoCo 3) are installed I save those to 3-bytes of reserved memory at the end of the program.
I then replace it with a JMP (&H73) instruction followed by the two byte address of my “newcode” that I want to run any time a character is output.
For now, there is no way to uninstall this routine, but I left myself a reminder I should create an uninstall routine. I think I may just make it so if you EXEC the first time, it installs, and if you EXEC again, it uninstalls, rather than using two different memory locations the user would need to know. We shall see.
newcode
* Do this only if DEVNUM is 0 (console)
tst DEVNUM is DEVNUM 0?
bne continue not device #0 (console)
leas 2,s remove PC from stack since we won't be returning there.
* Now this is the start of what Color BASIC ROM does for PUTCHR:
* PUT A CHARACTER ON THE SCREEN
LA30A
PSHS X,B,A SAVE REGISTERS
LDX CURPOS POINT X TO CURRENT CHARACTER POSITION
When BASIC needs to output a character, it ends up at the PUTCHR routine in the ROM, starting at address A282. The very first thing it does is “jsr RVEC3” which means it will now jump to this “newcode” routine. The character to output will be in register A.
The first thing I do is check the DEVNUM value to see which device number is being used. I use “tst” to check for zero. If the device is not zero (screen), I branch to a “continue” routine which will be the code that was originally in the RVEC3. (Thus, on Color BASIC it will just branch to an “rts” return, or it will branch to whatever “jmp xxxx” was in the vector before I hijacked it. More on this in a moment.
If I get past that, I will be doing some work and then jumping back into the ROM so the rest of BASICs output routine can run. In that situation, I will not be doing an “rts” from my custom code. I may be wrong on this, but my understanding is that I need to remove the return address off the stack (since I will not be returning), which I do by moving the S stack pointer up by 2. Is this what I need to be doing?
Since BASIC already checks for special characters like backspace and ENTER, I was able to pattern my code after what the ROM does for those and leave out the part where it puts a character on the screen. I take the first two line that BASIC would have done (from LA30A in the Unravelled listing) and put them into my code. This then lets me have my custom blocks checking for special characters the same way BASIC does for the ones it handles.
checkup
cmpa #UP
bne checkdown
CMPX #VIDRAM+32 second line or lower?
blt goLA35D disallow if on top line.
leax -32,x move up one line
bra done
The first check is for the UP character. If not seen, we branch to the “checkdown” routine just after this one. If it was UP, then we check to see if X is 32 bytes past the start of screen memory — the start of the second line. If you are on the top line, moving UP is ignored and we branch to a label that will then JMP back into the Color BASIC ROM to finish the normal checks that BASIC does.
Else, decrement X (cursor position) by 32 to move up one line, then branch to the “done” routine which will get us back into BASIC ROM code.
checkdown
cmpa #DOWN
bne checkleft
cmpx #VIDRAM+512-32
bge goLA35D disallow if on bottom line.
leax 32,X move down one line
bra done
The check for DOWN follows a similar pattern, this time disallowing down if you are on the bottom line of the screen. This is different than how Commodore handles it, since down on the Commodore will cause the screen to scroll up. I plan to fix this later to make it act more like the VIC-20 does.
If we cannot move down, we branch to a label that will then JMP back into the Color BASIC ROM to finish the normal checks that BASIC does. If we can move down, the cursor position in X is increased by 32.
NOTE: During my testing, I found a VIC-20 bug. If you cursor to the bottom right of the screen then press RIGHT, the first time you do this is scrolls up and then the cursor appears one line before the last line. If you then cursor to the bottom right position again and go RIGHT, it scrolls up and now the cursor is on the bottom left position. Odd. I should find a VIC-20 BASIC disassembly and see if I can figure out what causes it do misbehave, but only the first time.
checkleft
cmpa #LEFT
bne checkright
cmpx #VIDRAM top left of screen?
beq goLA35D
leax -1,X move left one character
bra done
The process repeats again for LEFT. This is much like what BASIC does with backspace (BS) where it disallows it if you are in the top left position of the screen. If we cannot move left, we branch to a label that will then JMP back into the Color BASIC ROM to finish the normal checks that BASIC does. If we can, the cursor position is decremented by 1.
checkright
cmpa #RIGHT
bne goLA30E
cmpx #VIDRAM+511 bottom right of screen
beq goLA35D
leax 1,x increment X, skipping that location.
bra done
Lather, rinse, repeat. For RIGHT, I disallow moving right from the bottom right character position. This is another difference from VIC-20 which I plan to fix later. If we cannot, we branch to the same label that gets us back into the BASIC ROM routine. If we can move right, the cursor position is incremented by 1.
And now some of the labels…
goLA30E
jmp $A30E jump back into Color BASIC ROM code.
done
stx CURPOS update cursor position
goLA35D
jmp $A35D jump back into Color BASIC ROM code.
A30E in the disassembly is the first instruction right after the two lines I cloned into my routine at my LA30A label. Thus, I start out with those first two lines, then do my code and either jump back in to let the ROM code continue, OR I modify the cursor position then jump to DONE so it can finish up.
“done” will update the saved cursor position that is in the X register, then jump to A35D which is the final line of the output ROM routine where the registers that were pushed/saved at the top of the routine are pulled/restored (including PC which acts as an rts to whoever called the routine).
continue
savedrvec rmb 3 call regular RAM hook
rts just in case...
end
Lastly, if the original check for console (device 0) was not true, that code jumps to this end part so it can continue on to whatever the vector was pointing to before we hijacked it.
Here is a BASIC loader for this program. I added LINE 5 to reserve memory for the assembly, else BASIC will clobber it with string storage;
5 CLEAR 200,&H7F00
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 32512,32607,182,1,103,183,127,96,190,1,104,191,127,97,134,126,183,1,103,142,127,24,191,1,104,57,13,111,38,68,50,98,52,22,158,136,129,117,38,10,140,4,32,45,50,48,136,224,32,43,129,100,38,10,140,5,224,44,36,48,136,32,32,29,129,108,38,9
90 DATA 140,4,0,39,22,48,31,32,16,129,114,38,9,140,5,255,39,9,48,1,32,3,126,163,14,159,136,126,163,93,32611,32611,57,-1,-1
And here is the complete assembly listing:
* lwasm consmove2.asm -fbasic -oconsmove2.bas --map
* Convert any lowercase characters written to the
* screen (device #0) to uppercase.
UP equ 'u character for up
DOWN equ 'd character for down
LEFT equ 'l character for left
RIGHT equ 'r character for right
DEVNUM equ $6f device number being used for I/O
CURPOS equ $88 location of cursor position in RAM
RVEC3 equ $167 console out RAM hook
VIDRAM equ $400 VIDEO DISPLAY AREA
org $7f00
init
lda RVEC3 get op code
sta savedrvec save it
ldx RVEC3+1 get address
stx savedrvec+1 save it
lda #$7e op code for JMP
sta RVEC3 store it in RAM hook
ldx #newcode address of new code
stx RVEC3+1 store it in RAM hook
rts done
uninstall
* TODO
newcode
* Do this only if DEVNUM is 0 (console)
tst DEVNUM is DEVNUM 0?
bne continue not device #0 (console)
leas 2,s remove PC from stack since we won't be returning there.
* Now this is the start of what Color BASIC ROM does for PUTCHR:
* PUT A CHARACTER ON THE SCREEN
LA30A
PSHS X,B,A SAVE REGISTERS
LDX CURPOS POINT X TO CURRENT CHARACTER POSITION
checkup
cmpa #UP
bne checkdown
CMPX #VIDRAM+32 second line or lower?
blt goLA35D disallow if on top line.
leax -32,x move up one line
bra done
checkdown
cmpa #DOWN
bne checkleft
cmpx #VIDRAM+512-32
bge goLA35D disallow if on bottom line.
leax 32,X move down one line
bra done
checkleft
cmpa #LEFT
bne checkright
cmpx #VIDRAM top left of screen?
beq goLA35D
leax -1,X move left one character
bra done
checkright
cmpa #RIGHT
bne goLA30E
cmpx #VIDRAM+511 bottom right of screen
beq goLA35D
leax 1,x increment X, skipping that location.
bra done
goLA30E
jmp $A30E jump back into Color BASIC ROM code.
done
stx CURPOS update cursor position
goLA35D
jmp $A35D jump back into Color BASIC ROM code.
continue
savedrvec rmb 3 call regular RAM hook
rts just in case...
end
And now I can write a program like this:
As I typed that in, after the four Xs I typed a lowercase “d” for down, then four lowercase “l” for left, then X and two “r” then another X, then “dlll” followed by the four Xs again. It looks weird as it moves the cursor around as I type, but I will be fixing that with more additions to this routine in a future installment.
For now, it lets me put this 4×3 thing on the screen without erasing stuff around or behind it:
Progress!
Please, folks, let me know in the comments if you see something I am doing wrong. Am I handling the stack pointer thing properly? I am just experimenting and … well, “it works for me.”
When BASIC wants to output a character, it jumps to a ROM routine called CHROUT. This is one of a few documented ROM calls in Color BASIC. Here is how it is documented in the EDTASM+ manual:
I have used this ROM call in previous blog posts, showing how to output “HELLO WORLD” or whatever using assembly:
That small program makes the X register point to the first byte of the MSG data. It then loads that byte into register A and increments X. If A is 0 (the end of string marker), it branches to DONE. If not, it jumps to the subroutine pointed to at $A002 which outputs whatever is in A. It then goes back to do it again.
I wrote a two-part post about Color BASIC RAM hooks awhile ago. There I explored these extra spots where later BASIC ROMs (like Extended, DISK, and the CoCo 3 ROM) can patch in and make the original Color BASIC call new code first. That new code did things like add support for DISK devices or the (gone in CoCo 3) DLOAD command. I assume it is also used to patch in the CoCo 3’s high resolution 40/80 column text screens, as well.
The idea is before Color BASIC does its thing with the CHROUT routine, it will jump to “new code” and let it run first, and that code may then return back to let the normal code process. When disk devices were added as device #1 to device #15, this jump goes to code in Disk Extended BASIC which checks the incoming device number. If it is 1-15, new code is called that takes care of writing data to a disk file. If not, it returns back and Color BASIC handles the devices it knows about (device 0 for console, device -1 is the cassette, and device -2 if the serial/printer port).
For the task Erico was inquiring about, I thought I could patch in a new routine that would check for device 0 (writing to the screen) and then if it was, look for a special character that now means “transparent”. Instead of putting the character on the screen then moving the cursor position over by one, it would just move the cursor position and leave the screen alone.
Here is what I came up with in a quick and dirty hack:
* lwasm consmove.asm -fbasic -oconsmove.bas --map
* Convert any lowercase characters written to the
* screen (device #0) to uppercase.
DEVNUM equ $6f device number being used for I/O
CURPOS equ $88 location of cursor position in RAM
RVEC3 equ $167 console out RAM hook
org $3f00
init
lda RVEC3 get op code
sta savedrvec save it
ldx RVEC3+1 get address
stx savedrvec+1 save it
lda #$7e op code for JMP
sta RVEC3 store it in RAM hook
ldx #newcode address of new code
stx RVEC3+1 store it in RAM hook
rts done
newcode
* Do this only if DEVNUM is 0 (console)
tst DEVNUM is DEVNUM 0?
bne continue not device #0 (console)
* If here, device #0 (console)
cmpa #'r compare A to lowercase 'r'
bne continue if not, continue
leas 2,s remove PC from stack since we won't be returning there.
* Now this is the start of what Color BASIC ROM does for PUTCHR:
LA30A
pshs x,b,a
ldx CURPOS X points to current cursor position
leax 1,x increment X, skipping that location.
jmp $a344 jump back into Color BASIC ROM code.
continue
savedrvec rmb 3 call regular RAM hook
rts just in case...
end
The “init” code saves out whatever is in the console out RAM hook (three bytes, either “rts” if not being used, or “jmp abcd” to jump to new code). It then patches in a “jmp” to the new code in this program, then returns back. This installs the new code into the RAM hook.
Now when anything tries to print through CHROUT, BASIC first calls whatever the RAM hook points to. That will be the “newcode” here.
This new code first checks if the device is 0, indicating we are printing to the screen. If it is not, it branches to the “continue” spot where the original RAM hook jump was copied, allowing it to jump there and proceed as normal.
Next it checks to see if the character to print is a lowercase ‘r’ (I chose ‘r’ for “right”). If not, it returns.
Now things get weird. This is not a normal RAM hook because I can’t just “do stuff” then return to the original ROM code. That original code would still want to output whatever is in register A to the screen. To make mine work, I need to take over some of the functions of the ROM and then jump back into the normal ROM code so it can continue after I have done my thing.
In a normal RAM hook, BASIC would use a JSR (jump subroutine) to get there, then have that code return back. But for this hack to work, my new code has to jump directly back into Color BASIC. Because of this, there is a return address (where RTS will return to) on the stack that we won’t be using. Doing “leas 2,s” moves the stack pointer so it skips that. This allows me to jump directly out of this new code back into the ROM.
Now we have to do whatever code the ROM would have done before processing the character. I looked at the disassembly and see it pushes three registers to save them, then at the end it will pull them back off the stack to restore them.
So I do that in my code.
Next, all I need to do is increment the cursor position then jump back into the Color BASIC ROM just past where it would normally put the character on the screen. From there on, everything will be normal.
I may not have explained this very well, but I am sure someone can help me in the comments.
Patching BASIC with BASIC
Thanks to LWTools, here is a simple BASIC loader for this test code:
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,16170,182,1,103,183,63,43,190,1,104,191,63,44,134,126,183,1,103,142,63,24,191,1,104,57,13,111,38,15,129,114,38,11,50,98,52,22,158,136,48,1,126,163,68,16174,16174,57,-1,-1
If you load this into a CoCo or emulator, then RUN it, the code will be loaded at address &H3F00. Type “EXEC &H3F00” to install the new RAM hook and now any print of lowercase ‘r’ skips to the right without erasing anything.
That simple program will clear to a black screen, then you will see “THIS” then four blocks to the right “WILL” then four blocks past that “WORK”. Normally it would erase those blocks with spaces, but the lowercase ‘r’ now means “just skip to the right”.
Over in the CoCo Facebook group, Erico Patricio Monteiro asked an interesting question:
“Would it be possible to PRINT a transparent character?”
– Erico Patricio Monteiro
His idea was for putting stuff on the CoCo’s text screen without wiping out the background. He used an example of PRINT”OOOOX” where “O” represented the transparent character. The way I see it, if you had the first line printed like this:
PRINT @0,"ABCDEFG";
…and you had such a transparent character and did this:
PRINT @0,"OOOXOOO";
…your top line would then look like this:
ABCXEFG
This reminded me of how my Commodore VIC-20 had cursor movement characters you could embed in a PRINT statement. Once you typed the quote, you could now embed typeable escape codes to change colors, clear the screen, home the cursor, insert or delete characters, or just move the cursor up, down, left or right. This made VIC-20 program listings require a graphical printer to represent those characters. Here is something from the VIC-20 manual:
This is many pages into the manual, after it had explained how you can embed things like color changes or reverse video. The program listings in the VIC-20 manual had graphical characters in them, and you had to learn what to type to recreate them:
Program listings for the VIC looked weird ;-)
At some point, Commodore BASIC listings were represented with text strings instead of the graphical characters, making a modern listing look like this:
Then you just had to know what key was “black” or “left” (three times, as the “left*3” indicates).
But I digress…
Since there was no PRINT@ or LOCATE on the VIC-20, any time you wanted to print something in a particular spot on the screen you had to print the HOME (move cursor to top left of the screen) character then use a bunch of cursor controls to move to where you wanted to print.
This was … not optimal. And thus, most BASIC VIC-20 programs would print their information (lives left, etc.) on the top of the screen since it was shorter code just to home and print there:
VIC-20 Sky-Ape-Er, screen 3.
My Sky-Ape-Er VIC-20 game had a timer, and I got it in that top corner like this:
You will notice the above snipped says “reverse on” and has “time” in lowercase, but on my screenshot it is uppercase without being reversed. That is due to the character sets of the VIC-20 where some modes were upper and lower, some were upper with reverse, and other combinations. For the mode I was in, reverse was getting the uppercase characters (and uppercase characters typed with SHIFT would be the graphical characters for those keys).
“But that’s really not important to this story…”
If you look at the Color BASIC Unraveled book you can find the Console Out routine (PUTCHR) on page 84. I did not want to type it all in here, but I did find this GitHub repository by tomctomc that has this already in a text file:
; CONSOLE OUT
PUTCHR JSR >RVEC3 ; HOOK INTO RAM
PSHS B ; SAVE ACCB
LDB DEVNUM ; GET DEVICE NUMBER
INCB ; SET FLAGS
PULS B ; RESTORE ACCB
BMI LA2BF ; SEND TO LINE PRINTER
BNE LA30A ; SEND TO SCREEN
...snip...
; PUT A CHARACTER ON THE SCREEN
LA30A PSHS X,B,A ; SAVE REGISTERS
LDX CURPOS ; POINT X TO CURRENT CHARACTER POSITION
LA30E CMPA #BS ; IS IT BACKSPACE?
BNE LA31D ; NO
CMPX #VIDRAM ; AT TOP OF SCREEN?
BEQ LA35D ; YES - DO NOT ALLOW BACKSPACE
LDA #$60 ; BLANK
STA ,-X ; PUT IN PREVIOUS POSITION
BRA LA344 ; SAVE NEW CURPOS
LA31D CMPA #CR ; ENTER KEY?
BNE LA32F ; BRANCH IF NOT
LDX CURPOS ; GET CURRENT CHAR POSITION
LA323 LDA #$60 ; BLANK
STA ,X+ ; PUT IT ON SCREEN
TFR X,D
BITB #$1F ; TEST FOR BEGINNING OF NEW LINE
BNE LA323 ; PUT OUT BLANKS TILL NEW LINE
BRA LA344 ; CHECK FOR SCROLLING
LA32F CMPA #SPACE
BCS LA35D ; BRANCH IF CONTROL CHARACTER
TSTA ; SET FLAGS
BMI LA342 ; IT IS GRAPHIC CHARACTER
CMPA #$40
BCS LA340 ; BRANCH IF NUMBER OR SPECIAL CHARACTER
CMPA #$60 ; UPPER/LOWER CASE?
BCS LA342 ; BRANCH IF UPPER CASE ALPHA
ANDA #$DF ; CLEAR BIT 5, FORCE ASCII LOWER CASE TO BE UPPER CASE
LA340 EORA #$40 ; INVERT BIT 6, CHANGE UPPER CASE TO LOWER & VICE VERSA
LA342 STA ,X+ ; STORE CHARACTER TO SCREEN
LA344 STX CURPOS ; SAVE CURRENT CHAR POSITION
CMPX #VIDRAM+511 ; END OF SCREEN BUFFER?
BLS LA35D ; RETURN IF NO NEED TO SCROLL
You can see at LA30A the code begins checking for things like backspace and enter. Eventually at LA342 it puts the character on the screen an increments X which is the current screen location. It then has code (not shown) that detects being at the bottom of the screen and scrolling up a line if needed.
To patch this CHROUT routine to support a “transparent” character, I think we’d just have to intercept the code at LA342 and decide if it should put a character on the screen (STA ,X+) or just increment X (LEAX 1,X or something) without putting anything there.
And that would be a real simply patch. A CoCo 1/2 with 64K could run the program that copies the ROM into RAM then switches over, then this could code easily be patched.
And while we were there, maybe it could be extended to support cursor movements as well, replicating how the VIC-20 output works.
ALERT! ALERT! We are doing this wrong. It has been pointed out in a comment to an earlier installment that we missed an important part about what this contest was supposed to produce!
More on that in a moment… But first, let’s look at a faster version of the challenge, created by Dillon Teagan:
This one clocks in at 2.7 seconds, and does some wonderful optimizations!
First, Dillon clearly understands how the BASIC interpreter works. He is doing things like using hex &HFF instead of decimal 255 which makes that bit parse a tad faster. Next, you see him declare a variable, followed by a DIM which pre-declared R, L, U and D. In this example, that DIM probably does not help, but in a real program, you can declare your variables up front and do them in the order of “most accessed” to least. This sets their order in the variable table, so when you try to access one, the one you access the most can be at the top of the list. I’ve posted about this before, but consider this:
10 DIM A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z 20 PRINT Z 30 Z=Z+1 40 IF Z<100 THEN 20
If we truly did have 26 variables in use, and declared them like this, Z would be at the end of the variable table. EVERY time Z is needed, BASIC has to scan through all 26 variables trying to match Z so it can be used. This would be MUCH slower than, if you knew Z was being used the most often, you did this:
10 DIM Z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y
20 PRINT Z
30 Z=Z+1
40 IF Z<100 THEN 20
With that one minor change (declaring Z first), Z is now the first variable in the table and will be found much quicker. Try it sometime.
But I digress…
The next cool optimization Dillon does is by drawing the initial bottom line (bottom left to bottom right, 256 pixels wide) and right side (bottom right to top right, 192 pixels tall) in line 50, along with the first left (top right to top left, 256 pixels wide) before entering the loop.
The loop itself is using a FOR/NEXT loop which is faster than using GOTO since no line scanning has to be done. BASIC stores where the FOR is, and when NEXT is encountered it pops back to that location rather than scanning forward to find the target line, or starting at the first line and scanning forward from there. Nice.
With the first three “full width/height” lines out of the way, the loop is just doing the “minus 3” size of all four lines. That’s clever. The entire draw string is in a string (D$) and I am unsure if this speeds things up versus having it directly in the line itself (line 60).
Impressive. I wish I’d thought of that!
However… we have been doing it wrong, so far, it seems.
We’ve been doing it wrong so far, it seems.
In a comment left on part 1 of this series, Paul Fiscarelli pointed out something that I completely got wrong:
Hello Allen – I sent you a DM on FB, but I don’t think you’ve seen it yet. What you have posted above is not exactly an accurate reproduction of what is being asked in the challenge question. You are using an offset decrease of 3 in each of your iterations, which produces a gap of only 2-pixels in both height and width. The challenge is indicating a gap of 3-pixels between lines, which requires an offset decrease of 4 in each iteration. This is further evident in the challenge’s diagram of the spiral, which indicates a line-length of 188-pixels for the line on the far left-hand side of the screen. If you perform a screen grab in XRoar (pixel perfect geometry of 640×480 window and 512×384 screen resolution), you will find your code generates a line length of 189 pixels (scale the 512×384 in half).
If you change the offset decrease in your code to 4 instead of 3, you will achieve a render time of roughly 2.43 seconds. This is due to the fact that you are rendering about 23% fewer lines with the DRAW statement.
You can reduce this time even further if you were to use only a single offset variable for drawing both the horizontal and vertical lines, and by adding a separate width to the horizontal lines with a value of 64 = (256w – 192v). This method will shave roughly 0.10 seconds off your render time, down to approximately 2.33 seconds.
10 TIMER=0 20 PMODE4,1:PCLS:SCREEN1,1 30 OF=191:DRAW”BM0,191″ 40 DRAW”R=OF;R64″ 50 DRAW”U=OF;L=OF;L64;” 60 OF=OF-4 70 DRAW”D=OF;R=OF;R64;” 80 OF=OF-4 90 IF OF>0 THEN 50 100 TM=TIMER 110 IF INKEY$=”” THEN 110 120 PRINT TM/60
As an additional optimization step, you can replace the IF/THEN boundary check with a FOR/NEXT loop, to shave another 0.05 seconds from your render time – getting it down to roughly 2.28 seconds.
10 TIMER=0 20 PMODE4,1:PCLS:SCREEN1,1 30 OF=191:DRAW”BM0,191″ 40 DRAW”R=OF;R64″ 50 FOR N=0 TO 23 60 DRAW”U=OF;L=OF;L64;” 70 OF=OF-4 80 DRAW”D=OF;R=OF;R64;” 90 OF=OF-4 100 NEXT 110 TM=TIMER 120 IF INKEY$=”” THEN 120 130 PRINT TM/60
There are probably some other optimizations that can be done here – but it’s a start. Oh, I also tested these examples with VCC 2.1.9.2-pre2. I’m sure there will be slight variations in timing with the different emulators, and probably even with different versions of VCC.
– Paul Fiscarelli
So, uh … oops. It seems obvious now:
Let’s regroup, and take a look at Paul’s versions in the next installment.
See Also:part 1, part 2, with part 3 and part 4 coming (and maybe more).
These types of posts are probably my favorite. Someone posts something with a coding challenge, and folks start submitting their ideas, and I get to play collector and just repost without doing any work… (Well, except I am submitting ideas as well, this time.)
This all kicked off when Michael Pittsley shared a contest he found in a 1984 issue of UnderColor magazine that challenged BASIC programmers to draw a specific spiral pattern:
Michael tackled it with this code:
10 ' SPIRAL THING 20 ' 30 XL=3:YT=0:XR=252:YB=188:PX=3 40 PMODE 4,1 50 PCLS 60 SCREEN 1,1 65 LINE (XL,YB) - (XR-PX,YB),PSET 200 ' RIGHT 210 XR=XR-PX 220 LINE - (XR,YB),PSET 300 ' UP 306 YT=YT+PX 307 IF YT=96 THEN 600 310 LINE - (XR,YT), PSET 400 ' LEFT 410 XL=XL+PX 420 LINE - (XL,YT),PSET 500 ' DOWN 510 YB=YB-PX 520 LINE - (XL,YB),PSET 550 GOTO 200 600 GOTO 600
A nicely formatted and documented (with REMmarks) example of the power BASIC. He was even aware that LINE does not need both a start and end point each time you use it. If you just do “LINE-(x,y),PSET” it will draw from the last point to the new coordinates. This greatly reduces the amount of parsing BASIC has to do versus if you wrote the program always having a pair of start/end points.
Let’s modify it with some timing stuff.
10 ' SPIRAL THING 20 ' 25 TIMER=0 30 XL=3:YT=0:XR=252:YB=188:PX=3 40 PMODE 4,1 50 PCLS 60 SCREEN 1,1 65 LINE (XL,YB) - (XR-PX,YB),PSET 200 ' RIGHT 210 XR=XR-PX 220 LINE - (XR,YB),PSET 300 ' UP 306 YT=YT+PX 307 IF YT=96 THEN 600 310 LINE - (XR,YT), PSET 400 ' LEFT 410 XL=XL+PX 420 LINE - (XL,YT),PSET 500 ' DOWN 510 YB=YB-PX 520 LINE - (XL,YB),PSET 550 GOTO 200 600 PRINT TIMER/60
Running it reports 3.16666667 seconds at the end, so really close to the time my version using DRAW got. AND, that is with REMs and spaces and multiple lines to parse. Let’s see what tricks we can do to speed it up by removing spaces, REMs, and packing lines together:
This drops it down to 3.03333334! Parsing LINE may be faster than parsing a DRAW string. Also, longer line numbers take longer to parse, so we could RENUM0,0,1:
However, this did not seem to make any consistent measurable difference.
A further optimization could be done by changing the 2-letter variables to 1-letter. But, beyond that, it would take adjusting the logic to find more improvements. Parsing integers is slow, such as “IF YT=96” as is parsing line numbers (thus, lines 1, 2 and 3 should be faster to “GOTO 1” than 100, 200, 300 and “GOTO 100”).
And, one question:: Michael started in from the corner of the screen. Here is a screen shot done in the Xroar emulator with artifact colors turned off so we can see the actual lines better:
This means this version is drawing a few lines less than the version I made. I need to go back and re-read the article and see if I got the details wrong, or if Michael is just keeping the 3-pixel border more consistent. I like the 3-pixel padding, but perhaps the first line to the right on the bottom should have gone further (3 pixel padding) and then at the top left a bit more (3 pixel padding).
Meanwhile, in response to Michael Pittsley‘s post. Andrew Ayers took the version I did and modifies it to work on the CoCo 3. The original contest was a few years before the CoCo 3 even existed, but one would imagine if the article had appeared later it might have had a contest for fastest CoCo 1/2 version, and fastest CoCo 3 version:
A couple more slightly improved CoCo 3 versions of Allen’s code above: … Both of these take longer to run than the original Allen posted, even with the high-speed poke…at least, running on XRoar Online (no idea about real hardware).
– Andres Ayers
CoCo 3 RGB b/w 320×192:
5 POKE65497,0 10 TIMER=0 20 HSCREEN2:PALETTE0,0:PALETTE1,63:HCOLOR1 30 W=320:H=191:HDRAW"BM0,191" 40 HDRAW"R=W;" 50 HDRAW"U=H;L=W;" 60 H=H-3:W=W-3 70 HDRAW"D=H;R=W;" 80 H=H-3:W=W-3 90 IF H>0 THEN 50 100 TM=TIMER 110 IF INKEY$="" THEN 110 120 PRINT TM/60 130 POKE65496,0
CoCo 3 RGB b/w 640×192:
5 POKE65497,0 10 TIMER=0 20 HSCREEN4:PALETTE0,0:PALETTE1,63:HCOLOR1 30 W=640:H=191:HDRAW"BM0,191" 40 HDRAW"R=W;" 50 HDRAW"U=H;L=W;" 60 H=H-3:W=W-3 70 HDRAW"D=H;R=W;" 80 H=H-3:W=W-3 90 IF H>0 THEN 50 100 TM=TIMER 110 IF INKEY$="" THEN 110 120 PRINT TM/60 130 POKE65496,0
It has been so long since I worked in BASIC on the CoCo 3 that I had forgotten about HDRAW and such being there. And, as expected, it takes longer to draw 320 or 640 pixel lines than it would to draw the 256 pixel lines on the CoCo 1/2 PMODE 4 display.
This does make me wonder how it would compare if limited to the same 256×192 resolution of PMODE 4. I expect the overhead of banking in and out the CoCo 3 high resolution graphics screens will add some extra time, and likely working with a screen with more than just 1-bit color (on/off pixels) is more memory to plot through.
Anyone want to work on that experiment?
Make it faster!
Revisiting my version from part 1, I changed the screen to PCLS1 (to make it white) and set COLOR 0 (to make the drawing black) so it would look like the printout in the magazine. This looks nice, but now we cannot tell how close to the original border of the image my results are:
I also see that because of the 256×191, the very last line does not appear to have a 3 pixel padding. Maybe we can look at the math later.
I simply took my version and combined lines and removed any spaces:
0 'SPIRAL2.BAS 5 FORA=1TO1000:NEXT 10 TIMER=0:PMODE4,1:PCLS1:SCREEN1,1:COLOR0:W=255:H=191:DRAW"BM0,191R=W;" 50 DRAW"U=H;L=W;":H=H-3:W=W-3:DRAW"D=H;R=W;":H=H-3:W=W-3:IFH>0THEN50 100 TM=TIMER 110 IF INKEY$="" THEN 110 120 PRINT TM/60
I could also do the “RENUM0,0,1” trick, but all of this only gets me to 3.016666667 seconds.
NOTE: I put a FOR/NEXT at the start so when I type “RUN” I have a moment to release the ENTER key before the timing starts. If you hit a key during the drawing, BASIC tries to handle that key and it will slow down the program. Run it and pound on the keyboard while it is drawing and you can see it slow down quite a bit (I got 3.7 seconds doing that).
But I digress…
Let’s collect some more examples, and see what other methods folks come up with. Now I want to get the same logic, just DRAW versus LINE to draw the lines, and see which one is faster.
See Also:part 1, part 2, with part 3 and part 4 coming (and maybe more).
And now back to CoCo …
– Michael Pittsley posted in the TRS-80 Color Computer (CoCo) group on Facebook:
Many of us have our CoCos and have memories or how good we once were writing basic programs on it. Including myself. I found this article in the first UnderColor magazine. It was a contest to see who could write an ECB program that created a spiral. — Write an Extended Basic program that draws a spiral figure on graphics screen 0 on PMODE 4. The figure, when done should look like the picture. Use any combination of Basic commands, but no assembly language. The winner will be the person whose program executes in the shortest possible time. (Entries that simply list a series of LINE commands will be disqualified). I took a stab at it and realized how much I had forgotten about basic, so this was fun for me. I put my results as the first comment. Feel free to try your hand at it, post a screen shot and the time it took to complete.
– Michael Pittsley
This caught my attention.
UnderColor magazine (1984-1985) was one I never saw, though the name sounds familiar so I may have at least read a reference to it, or seen an ad for it somewhere. You can find the issues preserved here:
The article, by Bill Barden, presented a contest to see who could write a program in BASIC (no assembly allowed) that would generate a spiral as demonstrated by this graphic:
The winner would be the program that could do this in the least amount of time.
The most obvious approach would be to use the LINE command. It takes a set of X and Y coordinates and draws a line between them, like this:
LINE (0,0)-(255,191),PSET
However, with what I know about BASIC these days (and wish I knew back then), that is alot of parsing of numbers and characters and such. That makes it slower than it might need to be.
One shortcut is that LINE remembers where it left off, so you can start a new line just by specifying the destination:
LINE-(127,0),PSET
Doing this trick should speed up a spiral program, since you only need to give the starting point once, then you can just “draw to the next spot” from then on out.
But I did not attempt this. Instead, I thought about DRAW.
The DRAW command is very powerful, and does allow you to draw to specific coordinates. You can do a “blank” draw just to move the starting point, like this:
DRAW"BM0,191"
That will do a Blank Move to (0,191), which is the lower left corner of the screen and the location where the spiral is supposed to start.
You can then do things like…
DRAW"R10"
…and that will draw a line 10 pixels to the right. (Well, the coordinates are scaled, I think, so it is 10 pixels on a PMODE 4 screen, but at other lower resolutions, that number of pixels will be scaled down.)
How can we spiral like that? One way would be to build a string and append it:
X=100 X$=STR$(X) DRAW"R"+X$
That works, but all that parsing and creating strings and such would certainly be slower than using a built-in feature of DRAW which lets you use a variable inside the quotes! You just put “=” before the variable name, and a “;” after it.
X=100 DRAW"R=X;"
That will draw to the right however many pixels X is set to!
Could this be faster than line?
Here is what I came up with:
0 'SPIRAL1.BAS 10 TIMER=0 20 PMODE4,1:PCLS:SCREEN1,1 30 W=255:H=191:DRAW"BM0,191" 40 DRAW"R=W;" 50 DRAW"U=H;L=W;" 60 H=H-3:W=W-3 70 DRAW"D=H;R=W;" 80 H=H-3:W=W-3 90 IF H>0 THEN 50 100 TM=TIMER 110 IF INKEY$="" THEN 110 120 PRINT TM/60
This sets a TIMER variable at the start, draws the spiral, then reads the value of the TIMER again. When you press any key, the program exits and prints the time (TIMER/60) it took to run the main code.
Here is what I get:
And pressing a key shows me:
3.03333334
Three seconds.
I expect I can optimize my program to speed it up. In the meantime, what can you come up with? Is there a faster way?
This topic came up because of Juan Castro‘s experiments with updating HDB-DOS to add new functionality on a CoCo 1 and 2 (but that is a discussion for a dedicated blog post sometime). Juan had recently “fixed” Extended Color BASIC to allow using “PCLEAR 0” to remove all graphics memory and give more RAM to BASIC. I have discussed PCLEAR 0 in the past…
This mysterious line performs a PCLEAR 0 without needing to load and run program of assembly code!
Us humans (this is not an A.I. post, bleep bloop) have a tendency to try to find patterns in randomness. For example, when asked to pick a number between 1 and 10, a magician/mentalist will know that statistically humans are more likely to choose certain numbers. There is alot of “human nature” that makes us somewhat predictable.
In a deck of 52 playing cards, if you were asked to predict what card is on the top of a shuffled deck, you probably wouldn’t say Ace of Diamonds, but that card is just as likely to be there as any other. No matter which card you guess, you have a 1 in 52 chance of being correct.
Call it in the air…
When it comes to a coin toss, do you always call heads? Always tails? Or do you alternate?
When a gambling casino game presents a grid of squares and asks you to pick four squares, do you “randomly” pick various squares, or do you just click the first four on the top row?
If it is random, either should have the same outcome.
And don’t get me started on picking lottery numbers. While we do not often see the picked numbers be “1, 2, 3, 4, 5 and 6”, that sequence should be just as likely as any other.
If it is random.
So let’s play a game in BASIC with a coin toss. Heads or tails will be represented using CoCo’s Color Basic RND() command. Doing RND(2) will produce either a 1 or 2 result.
NOTE: This is not random. This is psuedo random. I have discussed this previously, but for the sake of this blog post we will pretend it truly is random.
Would calling heads every time produce a better result than calling tails? Or would randomly choosing heads or tails each flip be better?
Let’s try…
0 'COINFLIP.BAS
5 'POKE 65495,0
10 W1=0:W2=0
20 FOR A=1 TO 1000
30 V=RND(2)
40 IF V=1 THEN W1=W1+1
50 IF V=RND(2) THEN W2=W2+1
60 NEXT
70 PRINT "ALWAYS GUESSING 1:";W1
80 PRINT "GUESSING RAND 1-2:";W2
This program will “randomly” flip a coin 1000 times and count how many times it landed on heads (1) versus how many times it matched a randomly (1-2) chosen value. At the end, it will print the results:
As you can see, in this “random” test, neither method really proved to be that different. We could also alter the output to print how many times guessing tails (2) would have worked (1000 clips minus how many times it was heads, 511 in this example, so 489 if my math is correct).
But it still feels better thinking we have some “control” over things and guessing rather than always choosing the same guess.
Alphabetically speaking…
Let’s modify the program to select a random letter, A-Z (represented by 1-26). We will now always guess A, versus randomly guess a letter (1-26):
0 'COINFLP2.BAS 5 'POKE 65495,0 10 W1=0:W2=0 20 FORA=1TO1000 30 V=RND(26) 40 IF V=1 THEN W1=W1+1 50 IF V=RND(26) THEN W2=W2+1 60 NEXT 70 PRINT"ALWAYS GUESSING 1:";W1 80 PRINT"GUESSING RND 1-26:";W2
And here is what I get…
Maybe this perspective will help you “always choose tails” or “always guess Aces of Spades” in the future.
And speaking of the future, there is another “random” test I want to experiment with, coming soon.