NOTE: I have a follow-up to this ELSE thing already written which “changes everything” for me. There’s stuff about ELSE I never knew, which makes me want to never use it. More on that in the next installment. And now back to your regularly scheduled blog post…
First, a quick BASIC memory saving tidbit.
Shaun Bebbington left a comment about my VIC-20 dissection article asking if I knew you could omit zeros from DATA statements like this:
100 DATA 128,0,0,50,239,0,123,42,0,4
If you leave them out (comma comma), READ will still get a zero! It saves a byte each time you do that, which could be important on a machine with 3584 bytes free on startup like my VIC-20.
100 DATA 128,,,50,239,,123,42,,4
Good tip, Shaun! Thanks!
Is there anything ELSE I can help you with?
And while I had his attention, I asked him a few Commodore BASIC questions. I was wondering how you did IF/THEN/ELSE without ELSE! One of the many things I liked about the BASIC in the CoCo was it had ELSE and commands like PLAY, DRAW, CIRICLE, etc. My VIC-20 had no ELSE. In my Sky-Ape-Er game, I’d see my young self doing things like this:
125 IF K=17 THEN L=L-1:M=14 130 IF K=41 THEN L=L+1:M=13 135 IF K=39 THEN 180
In that code snippet, K represented the key value that was pressed. If it was 17 (left), I’d move the player left one and change its graphic character. If it was 41 (right), I’d move the player right one and change its graphics character. If it was 39 (F1, jump), I’d go to a routine that would handle the jump.
Without ELSE, if K was 17, it would do that, then check K two more times, even though it didn’t need to. Maybe I did not “see” it back then, but If I had the bytes to spare, I probably should have done something like this:
125 IF K=17 THEN L=L-1:M=14:GOTO 140 130 IF K=41 THEN L=L+1:M=13:GOTO 140 135 IF K=39 THEN 180
That way, if K=17 was true, it would do it’s thing, then GOTO would skip over the next two lines. This could be a huge time saver if there were a bunch of conditions (up, down, left, right, diagonals, jump, fire, punch, windshield wipers, etc.)
Someone has already suggested that I may have done it my original way to get consistent timing in the game loop. Somehow I doubt my junior high self was that clever. But I digress…
With ELSE, it could have been written like this:
125 IF K=17 THEN L=L-1:M=14 ELSE IF K=41 THEN L=L+1:M=13 ELSE IF K=39 THEN 180
Less code (the ELSE token takes up less memory than an addition line number and/or the extra GOTO to skip lines), and faster execution, maybe.
Side Note: Maybe? There is still the issue of, when completing a successful IF, BASIC’s parser still having to run through the rest of the line characters to find the end and the next line. Adding a “GOTO” wouldn’t help, either, since it would have to parse that and then STILL have to scan to the end of the line. (At least on Color BASIC, which does not remember line pointers once it is parsing a line.) It may actually be faster to break up a long set of IF/ELSE into small lines with a GOTO.
But Shuan mentioned a way of using ON to simulate an ELSE. A quick search led me to a forum post discussing this very thing.
It supposedly works like this… since a compare (K=42, A$=”RED”, G>100) will return either -1 (false) or 0 (true), you can do an ON GOTO based on the result of that compare. Since it’s a -1 or 0, you can just make the result negative (thus, -1 becomes 1, and 0 becomes -0 which is still 0):
10 ON -(A=42) GOTO 20:PRINT "NOT 42":END 20 PRINT "42!"
Er… what? I thought BASIC did not execute anything after a GOTO. It doesn’t, does it?
But… but… How can ON/GOTO with something after it work, then? It turns out, ON/GOTO is special since the conditions of the “ON” may not be met, and thus the GOTO part may not get executed and BASIC will continue.
ON A GOTO 100,200,300:PRINT "A WAS NOT 1, 2 or 3"
Looking at it like that makes perfect sense to me. If A is not 1, 2 or 3, it won’t GOTO anywhere and the line continues to be processed.
Thus, this odd code serves as a simple “ELSE” when you don’t have ELSE:
10 INPUT "VALUE";A 20 ON -(A=42) GOTO 30:GOTO 10 30 PRINT "YOU KNOW THE ANSWER!"
…would be like…
10 INPUT "VALUE";A 20 IF A=42 THEN 30 ELSE 10 30 PRINT "YOU KNOW THE ANSWER!"
Interesting! I have not benchmarked this to see if it’s faster than using GOTO to skip lines, but it might be smaller due to not needing another few bytes for each line number.
Would this have helped my “left, right, jump” code? Maybe not. You can string these together like this:
124 ON -(K=17) GOTO 125:ON -(K=42) GOTO 130:ON -(K=39) GOTO 180 125 L=L-1:M=14:GOTO 140 130 L=L+1:M=13:GOTO 140
Since I was doing code in response to the IF, the ON/GOTO approach would just let me GOTO a line that does that code, which then still needs a GOTO to skip the lines after it that shouldn’t be executed. Not great for that use.
But, if I were dispatching to routines based on direction (like I do with the “jump” check to line 180), it would have worked just fine. Instead of this:
125 IF K=17 THEN 200 130 IF K=41 THEN 300 135 IF K=39 THEN 400 ... 200 REM Handle LEFT ... 300 REM Handle RIGHT ... 400 REM Handle JUMP ...
Those three lines could be combined into one like this:
124 ON -(K=17) GOTO 200:ON -(K=42) GOTO 300:ON -(K=39) GOTO 400
But, doing a quick test typing in JUST those lines on a VIC-20 emulator, I got 3530 bytes free after each approach. No penalty, but no savings, and all the parsing with parens and the negatives is probably slower.
Interesting, for sure. Useful? I guess I’ll find out if I get around to updating my VIC-20 code.
I also saw this example in the Commodore forum:
10 rem if then else demo 20 input a 30 if a = 1 then goto 60 40 print "this is the else part" 50 goto 70 60 print "this is the if (true) part" 70 end
This is how I’d write that with ELSE:
20 INPUT A 30 IF A=1 THEN PRINT "THIS IS THE IF (TRUE) PART" ELSE PRINT "THIS IS THE ELSE PART"
So we could change the Commodore example to match this a bit closer:
20 input a 30 if a = 1 then print "this is the if (true) part":goto 70 40 print "this is the else part" 70 end
…which leads us back to just adding a GOTO at the end of each separate IF:
10 GET A$:IF A$="" THEN 10 20 IF K$="U" THEN Y=Y-1:GOTO 70 30 IF K$="D" THEN Y=Y+1:GOTO 70 40 IF K$="L" THEN X=X-1:GOTO 70 50 IF K$="R" THEN X=X+1:GOTO 70 60 GOTO 10 70 REM DRAW X,Y... 80 GOTO 10
…which on the CoCo’s Extended Color BASIC might look like:
10 GET A$:IF A$="" THEN 10 20 IF K$="U" THEN Y=Y-1 ELSE IF K$="D" THEN Y=Y+1 ELSE IF K$="L" THEN X=X-1 ELSE IF K$="R" THEN X=X+1 ELSE 10 70 REM DRAW X,Y... 80 GOTO 10
Ultimately, we are just trading “ELSE” with “GOTO xx” and a new line, with ELSE being smaller due it it just being a token, verses the overhead of a GOTO token and the line number characters after it, AND a new line for each additional “else” condition.
Until next time, ELSE maybe sooner…
20 ON -(K=41)-2*(K=17)-3*(K=39) GOTO 30,40,50
Is … is this a way to do ARBITRARY values in ON GOTO?
Must … go … test.
Wow. That is brilliant. I guess I’m doing a follow-up! I’ll have to do some benchmarks on that and see how the adding the math to the fast ON/GOTO compares to doing a bunch of IF/THENs. Very, very cool. Where did you learn this trick?
0 REM robgoto.bas
10 INPUT “41, 17 or 39”;K
20 ON -(K=41)-2*(K=17)-3*(K=39) GOSUB 30,40,50
25 GOTO 10
30 PRINT “30”:RETURN
40 PRINT “40”:RETURN
50 PRINT “50”:RETURN
You could also split that to two separate statements. One handling K=17 case, and then do ON K-38 GOTO 50,x,30 where x is just the line following the ON GOTO line.
don’t know about speed but you could also try ON K-16 GOTO 40,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,50,x,x,30 (also where x is the following line)
Btw which article does the code snippet come from? What does the M variable do? If M is the screen code for a user defined char, which differs for moving in two different directions, then you might instead have those chars two screen codes apart and just assign an addition/subtraction variable that’s both used to do the movement (L) and also set the screen code. Maybe that’s slower though. Like
IF … THEN D=-1
IF …. THEN D=-1
IF D THEN L=L+D:M=D+13;D=0
Yet another thing you could do is to have arrays containing what to do with L and M. Also probably slower but worth benchmarking. Like L=L+D(K);IF(C(K)THENM=C(K). Btw not sure which is fastest if doing this in assembler, either doing actual compares or using self modifying code, which afaik is the fastest way to point to an array which starts on an even page boundary, and just store the get-keyboard-char-code directly in a load instruction that loads whatever should be added/subtracted to/from “L”. If you’d use joysticks instead of keyboard you could limit the array to 16 bytes or so. The VIC 20 is a bit special in that joystick right is on a different port than up/down/left/trig so you’d still need to do some trick for joystick right and that might be slower than regular compares. On the C64 all five signals for a joystick is on the same port so the array solution might be faster there though.
Yep, could also do something like
ON C(K) GOSUB 20,30,40
But that’s probably a bit memory hungry.
Sticking all the comparisons in one ON is just a logical extension of realising that -TRUE == 1. 40 years experience… ;-)
I think I’ve been in this box too long – I’ve forgotten how to think outside of it! That’s also clever. I want to do some tests on that, too. It might be a fun option when wasting a bit of memory isn’t an issue. I also just checked to see if you could re-DIM, like DIM C(0) afterwards, but no luck. “?DD ERROR” on Color BASIC. Bummer.
Using ON/GOTO for 2 of 3 is an interesting hybrid. I’ve already written a follow-up testing a bunch of things, based on Rob’s technique. I mention the dummy gap like your “40,x,x,x,x,50” example. There is a point when parsing becomes slower than brute force IF/THEN so I was trying to figure where that was.
My code snippet is from an upcoming VIC-20 Sky-Ape-Er dissection article. You are correct about M. Three characters, facing left, forward, and right. I need to test to see if “M=M+1” is slower than “M=15”. Math is probably slower since it has to decode the 1 and then do math, versus just decoding. Maybe it’s less work to decode one digit then add (M=M+1) versus decode a three 2 or 3 digit number.
Please tell me more about self modifying code in BASIC. We have VARPTR() that gives us the address of a variable in memory, so plugging something directly in to it might be possible. Intriguing.
You know what would be cool? A BASIC editor that shows you how long it takes to parse what you type! Just like assemblers will show you clock cycles. I bet that could be done, though with conditional code it would either have to let you highlight the code path, OR just show you the worst case for all things on the line if they ALL ran (impossible for an “IF x THEN y else Z” but you could break statements apart, or it could group them based on path. Wow, I like this idea.
The lack of an ELSE on the MC-10’s stock Micro Color BASIC made it a bit (pun intended) of a hassle to cleanly write decision logic segments.
The lack of ELSE helps make JIm Gerrie’s code faster, it turns out.
During some testing, I found alot of things that make using ELSE much slower than separate IF/THENs. I had no idea.
Not sure about self modifying code, I’m not familiar enough with the machine…
Can you do computed GOTOs, like
100 GOTO 10-100*(K=17)-110*(K=49).. Etc…
I’ll test, but I know Color BASIC cannot use a variable for normal GOTO.
Pingback: ON/GOTO/GOSUB with arbitrary values!?! | Sub-Etha Software
Pingback: More Crazy ON/GOTO/GOSUB and IF/THENs | Sub-Etha Software
How about at the start, have
ON INSTR(CHR$(K), X$) GOTO (or GOSUB) wherever
Ah, very interesting. I use INSTR for INKEY$ exactly like that. I had not thought about using it like this. I will have to benchmark it. Thanks for the tip!
The VIC-20 also lacked INSTR. Sigh.
Pingback: Compressing BASIC DATA with Base-64 – part 2 | Sub-Etha Software