what do you mean- you don't like videos of make-up influencers reacting to the reaction of an unboxing of a professional VFX artist who never had a job in that field but is a bright star on reddit?
As a matter of curiosity: You can LIST all the individual basic lines you want, except for line 0! 'LIST 0' gives the same result as a simple 'LIST' command. :-) Great video as usually!
@@eugenetswong to reduce the amount of stress in the button mechanism. Not that it's necessarily a common failure point, but if you can reduce the force of that sudden stop, why not?
I/we did on almost every cassette players as the spring actuated release was very annoying. At least in closed space. When we listened cassette outside, then we used ALL the spring force, without dampening. Thank you, I almost forgot it!
@@batlin however, wouldn't you by that increase stress to the actual tape? the button mechanism can be replaced, the data on the tape (potentially) can't!
This was nice to see but I do remember seeing these being used in long BASIC listings in various computer magazines. (The German 64'er and the Sonderheft particularly). The multiple parameter INPUT was very often used with the disk drives, so INPUT#15,A,B$,C,D (IIRC!) would be used to check the disk error. Menus would use the ON A GOSUB for the various submenus, etc. Kinda makes me miss typing in long BASIC listings as a result. So thanks for this trip down memory lane :)
There's a potential issue with RND(0) depending on how you use it - if it's called at regular intervals, it will produce a limited subset of random numbers. I remember doing experiments like 10 POKE 1024+(RND(0)*1000),160:GOTO 10 and seeing that it never actually filled the whole screen with inverse-space characters. Instead, what I recall various books (or BASIC programs in REM statements? It's been a while) recommending is to use a single X = RND(-TI) statement to seed the pseudorandom number generator with the number of jiffies since the power was turned on, which is highly unpredictable, followed by RND(1).
The random seed value has always been a great way to create the same levels (procedural) over and over again with just one number. I remember the game Modem Wars using this. You entered a sequence of characters, this way you could make a word, name etc... and use that to generate the level. The game could then simply convert the letters to the ASCII values and use that as the seed for the random number generator. Minecraft still uses this technique to this day, you enter a number to generate the same world. Great stuff anyhow.
Yes, I love this concept too. I keep meaning to make a video about either Pitfall or River Raid or both, as they use the same concept to build large worlds in a tiny 4K cartridge on the lowly Atari 2600.
@@8_Bit A good one to do on this would be about Elite, the space game. It has a massive universe that was completely procedural for the C64 and other similar machines. That's probably the grand daddy of them all. I was always impressed with what they did in that game.
I didnt realise Pitfall was kind of procedurally generated but it does make perfect sense - from meory iirc negative numbers put into a random number generator would return the same results but positives wree truly random @@8_Bit
By the way in BASIC 7.0 (C128) the SYS command was extended to pass parameters to (unfortunately not back from) an assembly program. Parameter 2,3,4,5 are A, X, Y, status in this order, if specified.
In BASIC 2.0 the SYS command loads the A, Y, X, and STATUS from $030C-$030F (780-783) before calling the function and stores the A, Y, X and STATUS at the same location after the call.
Thanks for the tip at 23:49! I never met this handbook, I downloaded this in PDF, very very useful, although I still have my old guide books. This was a perfect video, waked up joyfully my sleeping memories, I haven't used C64 professionally almost three decades.
"WAIT" in most program languages is an evolutionary hold over from a time when computers mainly controlled mechanical devices and output to teletype for display. It's specific purpose was to halt all processing at a particular place in the program ("wait" = "freeze the program counter") until a specific condition was met, either in a manufacturing process or to wait for a user input. This is why it is effected only by hard-wired, electrical changes like pushing a button. Most manuals for the various languages that use the command will WARN you to avoid using it (except in specific applications) because it essentially stops the computer from doing anything else other than interrupt driven functions. In some really early (1950s and 60s) CPUs the waiting for an electrical change was all it could do and is the reason most CPUs were later designed with "maskable" and "non-maskable" interrupts with WAIT being put into the maskable category. "SLEEP" is similar, not part of C-64 BASIC, in that the CPU freezes all function for a specified number of clock cycles.
Back in the early days of Amazon you could enter quantities up to 10E300. Adding 2 different items to the cart and setting quantities of each to 10E300 would crash the system. Now it appears to be limited to 999.
I thought I might pass along that using DEF FN you can do IF/THEN, but you have to use boolean math. Boolean operators return 0 or -1 for false and true, respectively. Also, you can use variables defined in the program, not just the passed parameter, in your function. So, let's define S as a "switch" to control the value in DEF FN FOO(X)= X*2 *-(S10). Using a bit of addition and multiplication identity math we can get one side of the equation to zero out by the boolean returning 0 or -1. If S is 10 or less, it returns X times 2; otherwise, it returns X times 3. You can make this a bit simpler if your controlling variables are a 0 or 1. Instead of testing with a boolean operator, use AND. AND returns 0 if the two numbers don't match or the number if they do, so you don't have do the negation.
RND(-1) or other negative values can be used in games to program pseudo preprogrammed surroundings. I once made a side scroller using BASIC together with assembler routines, the user could chose 4 different ways he wanted to go, they where generated with RND(-1), RND(-2), RND(-3) and RND(-4).
I used most of those commands when I got my Commodore VIC-20/64 by reading its BASIC user manual... those were the good old days.. though I didnt learn much with machine language, since it wasn't built in.
Great tips! To make the USR function more useful, you could call the subroutine located in BASIC ROM that turns the floating point number into a 16-bit value. I believe that routine is called FACINX and is located at $b1aa. The 16-bit number is returned in the A and Y registers, hi and lo byte, respectively. I used it to fill the color RAM with a 4-bit value, in ML code, of course, and called from BASIC with the USR function.
@@8_Bit does not have to do with this video exactly but I have a question. In that menu that was brought up when you power cycled AKA and turned off then on in a bit The c64 AKA Commodore 64 what is it referring to and the option for extended life that's what I'm curious about anyone please comment thanks.
I watch each video of yours and I have to admit that your way of presenting all those things is so good. I would even say that your videos are quite good for beginners to start with real IT. I mean that you explain everything as to the people who doesn't know the basics while doing a showcase of what you talk about. I think about that stuff like memory addressing., how CPU works, all basic concepts in programming etc. It is soo goood and your approach is so much quality to me.
I agree. There were 2+ times, when I thought that he must have really prepared his lesson. I really liked how he demonstrated features in many ways to ensure that we got a good grasp of the feature.
I started programming on TRS-80 Basic, and during my younger years messed about with Apple 2 Basic, ZX80 Basic and plenty of others that in my middle age I just can't remember anymore. At some point I have used all of these commands in some way, especially the ON..GOSUB (a very useful thing indeed). Its been very interesting watching this video and realizing how many of these functions were available in Commodore Basic. Many thanks for the insight 👍
Hi, I learned a lot from this video, thank you very much! I wrote a BASIC game that I can improve now with the new knowledge of this video: First, I will exchange the RND(1) to RND(0) just to get ALWAYS a new random series of numbers each time the commodore powers up. Second, I will use the on-gosub command for the joystick query. And third, the wait command is very useful, each time I wait for the fire button of the joystick. It’s way better than doing it with if-then-loop. That’s very helpful, thank you very much and keep up your good work👍👍👍
Another quirk of RND() - try these two lines: 10 PRINT [clear/home] ; 20 POKE 1024 + RND(1)*1024, 81 : GOTO 20 Run this then change it to ... RND(0)*1024 ... and run it again. You find the '0' argument RANdom formula skips numbers in patterns, depending on the size of the multiplier. Although RND(0) generates different sequences, depending on the range of numbers you are trying to generate, you will never see certain numbers. The BEST method: 10 X = RND(0) : PRINT[clear/home] ; 20 POKE 1024 + RND(1)*1024, 81:GOTO 20 By using the RND(0) in a throw away call to the RND function, C-64 seeds the random generator with a value read from the "time of day" clock on the CIA chip. IBM's BASICA or QBASIC equivalent is RANDOMIZE(timer). The odds of reading the exact same number in two different runs of the program are astronomically huge against it. Then using RND(1) for the rest of the program it does not skip numbers. I'm not 100% certain why RND(0) times a number greater than 256 produces this skip, but it is consistent, and the skipped numbers are always the same depending on the value of the multiplier. This: RND(0)*257 will never return the number 128; RND(0)*259 skips 86 and 172, RND(0)*512 skips every other number, meaning all the odd numbers ... and so on ...
If you look at the C64 BASIC ROM listing at $E0A5 at www.unusedino.de/ec64/technical/misc/c64/romlisting.html#E097 , calling RND(0) fills the floating point mantissa with (MSB : : : LSB): CIA1 Timer A Lo : CIA1 TOD 1/10th sec : CIA1 Timer A Hi : CIA1 TOD Seconds The first byte (MSB) is fine and can have all 256 possible values‡, but the rest of the bytes have limited bit patterns: TOD 10TH always has bits 4-7 as '0', Timer A Hi only goes up to around 17000/256 = 66, and the TOD seconds can only have values $00 to $59 in BCD. ‡ technically, for one in the 66 values of the high byte, the range of Timer A Lo will be limited to the low bytes of the timeout value rather than all 256 possible values.
It seems that in VICE at least, the CIA1 TOD clock isn't running at all, so the second byte of the mantissa is always 0, making RND(0)*1024 will produce a numbers that always have '00' in its two least significant bits (the column stride of 4 in the test program).
@@csbruce The TOD clock has a latch function. The registers are all zeros until you read (or write) the tenths of a second. (The TOD clock _is_ running, but the registers are "frozen".) The C64 does not do this on startup, so the TOD clock only "starts" *after* you use RND for the first time! As a result, the "random seed" is always the same the first time you use RND(0) after turning the computer on. The solution is simple. If you POKE 56328,0 _before_ you use RND(0) for the first time, it "unlatches" the TOD registers. Then RND(0) will work as intended. (Confirmed by testing with VICE.) EDIT: Or PEEK it with A=PEEK(56328). Either a poke or a peek will unlatch the registers.
@15:1- That's a bug in the stack PC, if I ever saw one. Obviously BASIC is setting a PC limit to a line number if the number is > the current limit, but it isn't cleared. I don't ever recall running into that in the era, but it's an interesting observation. I would expect it to do this if you execute a programme that terminates prior to its max PC value, and I may have seen that happen, long ago.
Just want to add a bit on USR(). USR is not limited on numeric values. The interpreter calls the expression evaluation which may result in a string or numeric value. It depends on the calling context what is expected (otherwise a TYPE MISMATCH may occure). One may take a look on the examples of the german variant from c64-wiki.com (I have to take over the examples to the english site some time in future): www.c64-wiki.de/wiki/USR#Beispiele. Because the interpreter passes the control after the expression evaluation but before checking for the closing parenthesis to the USR code, you may add the interpretation of additional parameters in your function. You have only to ensure that the interpreters parser stays in front of the closing parenthesis and you are fine.
Hi sire, the STOP command is not like breakpoint. STOP IS breakpoint. When your program finishes or executes END, the BASIC is supposed to do some housekeeping on variables. And it should clean the internal execution stack. Contrary the STOP/CONTinue, when reaching STOP, the BASIC suspend execution and waits for your interaction with system. The variables and internal execution stack are not affected. So when you finish your debugging where you interrogate variables, you command CONT to resume the execution of the program from exactly the point it was stopped. During the STOP you can also change the values in variables. So the original purpose of STOP/CONT is to help programmer debug its program. Have a great day, -- Radek
Yes, that's exactly how I remember STOP/CONT being used: for debugging. Interesting, though, from the video that you can also use CONT after an END. It had never occurred to me to even try that (though it probably wouldn't be too useful), since STOP and CONT are so strongly paired with each other.
I did a fair bit of experimenting and as far as I can tell, END in Commodore BASIC does not do any clean-up of the stack or variables, so that's why I said that apart from the debugging-friendly aspect of printing the line number for STOP, they appear to be identical in this implementation. If anyone has any specific examples of how they differ, I'd love to see them.
@@8_Bit As you pointed out, functionally they're the same, other than the STOP command tells you what line number you stopped at. You can even use CONT after an END.
Well, it’s actually „kind of” breakpoint from the view that normal breakpoints are not put into the code literally, they are just markers set to particular memory locations.
"ON SGN(I)+2 GOTO ln1, ln2, ln3" makes also a great replacement for FORTRAN's original arithmetic-if statement ("IF (I) label1, label2, label3"), in case you need it. ("I" can be any arithmetic expression, as well, in both cases.)
@@gregor_man Spoken like a true FORTRAN 77 aficionado 😂😂😂😂. Seriously though, if used properly you are right. I was just saying that calculated GOTOs can be horrible to debug unless you are absolutely certain about which lines you can go to. Not so much the ON GOSUB mentioned here where you know the options are clear, but the equally legal, at least in Commodore BASIC 2.0 X=15*Y-12*Z: GOTO X,
@@Doug_in_NC :)) This kind of jumps could make the program more interesting. My "hobby" was creating complex procedures in one command line, during a debug process, maximum 80 characters, no jumps. Cultivates the mind. ;-)
I think I used all of these commands at some point while programming my C64/C128. I remember using the WAIT command as a "security verification" if you didn't press the correct key they program wouldn't continue to execute. I was a teenager that would rather write weird programs than play games.
I had a Commodore 64 in 1987 during college and a dot matrix printer. To print I basically had to write code....it was not easy as pushing a button or giving a voice command...although I did talk (or scream) at the C64 often....
wow, this takes me back 33 years and some knowledge I forgot I had in me came back up. I used to type programs from magazines and recognize most of these , except the CONT
I remember I actually did use the scientific notation feature at one point, for some math self-study thing I did. The LIST range and selective RUN start wasn't that obscure though I think. I used it all the time to edit my larger basic programs and it was invaluable.
The fact that RND(1) gives the same sequence every time after a reset is not solved by using RND(0) instead (because RND(0) is not really random). Instead, just add A=RND(-TI) as your first command to give it a random seed.
Last (well almost last I think) I did to my C64 was to transfer some of my BASIC programs to my Amiga 500, I made a userport connector that connected to my Amiga parallel port and did a basic program to read bytes from the floppy and send them to my Amiga 500. On my Amiga I did Pascal (Kickpascal I remember correctly) program to receive and store on floppy. I think I was about 14-15 at the time... It was an amazing experience. Lots of trial and errors haha... I think my final version was transfer 7 bits and use 1 bit for a sync "clock" and that worked the best... It was fun to program the C64 again and with a specific purpose, the Amiga I was quite comfortable with when I did it. I think I did a peek/poke though but some of it could been used by the wait routine... Can remember the actual purpose of it though but I think I did it because I just got an early C64 emulator and wanted to see how it worked without retyping everything again or I was just testing and seeing if I could get my C64 work together with my Amiga. Some of what you been demonstrating I do remember and I think I used, but some was news to me aswell! Great video!
Another quirky thing about C64 basic - if you put a line in your code with a remark and a shift L, (10 rem L), it will cause a syntax error on that line. you can put a ": rem L" at the end of each line to make your code REALLY annoying to attempt to read. It's not a useful/functional thing... but it was used as crappy copy protection - Artworx's Strip Poker used it to hide their basic code. There were a lot of fun things hidden in the C64. :)
The WAIT command basically is waiting for an interrupt signal. That's why the Eject button doesn't caUse WAIT to end. It only opens the door of the casette player, it doesn't send an interrupt to signal it's ready to send or receive data.
So true! I just replied that I didn’t know most of these but I loathe basic and especially Microsoft basic. I skipped it quickly in favor of assembler especially since we also had a PDP11 with Pascal and C on it that I was allowed to use (was from my dad’s company). M$ basic feels so spartan to any other basic.
@@rdoetjes A spartan BASIC is better for an 8 bit machine limited to 64 K RAM than a bloated one that eats more memory, this was a conscious decision by Commodore. The C64 BASIC is a CBM BASIC 4 reduced to the commands of version 2 but with all of the bug fixes that went into 4.
I used to use the LIST command with a range frequently. When your program would get so long that you couldn’t easily stop it and make corrections, i would list it with control and remember the range. It worked well for my 10 year old brain 😂
It is possible to have an IF in a DEF FN -- by using what could be called an "implied if" that utilizes the (typically) numerical value returned by boolean expressions. The trouble of course is that the "true" and "false" representations was largely implementation-dependent: 1. some BASICs would return 0 for false and -1 for true (e.g. TRS-80 BASIC on model I/III/4, IBM-PC BASIC), whereas some would return 0 for false and 1 for true (e.g. AppleSoft BASIC). 2. the boolean operators of NOT, AND and OR would introduce yet another wrinkle. In some implementations the return value would perform a bitwise operation (e.g. TRS-80 BASIC on model I/III/4, IBM-PC BASIC) on the (implied) integer operands, and others would only affect the least significant bit (e.g. Applesoft BASIC). Aside from the highly non-portable aspect, use of this "implied if" is generally *slower* than having an actual IF-THEN-ELSE statement, since this "implied if" involves using a multiplication for each of the conditions.
I remember, back in the day, starting programs with 10 A=RND(-TI) to get the C64 to randomize. All that time I could've just RND(0)'d instead. In my defence, I was 7 and a dumb 7 at that.
I once made a video game as a kid where the computer controlled grasshopper character flew around the screen controlled by a RND(1) generated number, thinking it would move around at random, as you tried to shoot it. After playing the game several times I realized it was doing the exact same pattern over and over again! Oops!
I noticed the Mapping the 64 book was written by Sheldon Leemon. I have not thought of him in years! I think he used to write a lot of articles in Compute! Back in the day.
Yes, it's an excellent book and he's a very good author. Even in this very reference-heavy book, his sense of humour comes through; he gets a couple nice jabs in on some design decisions of the C64.
@@8_Bit I keep thinking I either met him or spoke to him on the phone 25 or more years ago... Maybe on one of my group Phone Phreaking expeditions back in the mid eighties.
Another fascinating video I've been looking forward too. Thanks man. :-) Can't you do an Assembly programming in Turbo Macro Pro series for the beginner? Your previous vids 'bout Assembly helped me out a lot. But I think that you could be a very good teacher for a "complete" assembly course from the ground up. I'm reading Jim Butterfields' excellent, brilliant, book on machine language but it's a bit too much about, erm..., machine language in a Machine Language Monitor and less about programming in "full blown" Assembler.
This video has suddenly pushed me to learn C64 Assembly, and yesterday I've completed my first hello world 😁 I had a C64 as a kid but i was too young and I never really used it for coding. I started coding with C on a 386. Always skipped Assembly for some reason until now!
Re: USR vs SYS: you can call line reading routines from within a machine code program following an SYS command. I can't remember specifics but I remember writing commands back when I had a C64 as my main computer in the form of SYS49152,.
Yes, that's what I did with some of my routines that I wrote. It basically uses the same ROM routines that BASIC uses to read DATA statements to read the next number after the comma, and convert it into an integer. I don't remember specifics either because I no longer have my "Mapping the C64" book (lost in a house fire). If I could find that book again in PDF form, I could provide sample code.
Yes, but SYS is not a function. USR() is just nice if you have to give some value back in an expression context. With SYS you need to PEEK for the returning registers or doing some variable magic (predefined variables for return values or something like that).
The C64 has the same version of Commodore BASIC as the VIC20 before it. So strictly speaking this video should have been called "10 Rarely Used Commodore BASIC V2 Features"! 😁
I remember not understanding DEF FN in ZX Basic, but using BLITZ now it is super useful. I think in 8 bit basic the DEF FN was as you said just a mathematical equation. In NBASIC the List - list 100-200 etc was essential.
One of the videos about disk drives made me connect my usb floppy drive to android phone via USB OTG and write some files to a blue 3.5" floppy disk. It's actually safer than writing to a Pendrive these days, I mean who would be able to read that, they only know the "Save" button icon...
I wasn't a "professional" programmer but I used all of these regularly in small and large programs. Odd. Why do you think they weren't used much??? These are all useful and common commands.
I kind of blasted over BASIC into assembler once I hit its limits trying to build a dictionary. The text was space delimited and contained commas so INPUT# wouldn't work and GET# chewed through memory creating too much garbage - "h", "he", "hel", "hell", "hello" etc. USR only worked with numbers so straight to SYS to read the next word and write it to the screen and then push a Home and CR onto the input queue. 26 hours shrunk to 8. Full blown assembler processed all 6 disks (not just the one) in 20 minutes so at that point, BASIC just became a toy.
So I definitely read those when I read the manual (which was like an IT bible for me when I was 9), but didn't remember some of them. 1. Remember and have used. 2. Definitely remember and have used. 3. Don't remember, definitely haven't used. Possibly because when I was 9 I didn't even know what was that (and there was no Google to look for it). 4. Don't remember but possible used it. 5. Don't remember but most probably used it because I typed some LONG BASIC programs back then (to the point of getting OUT OF MEMORY errors). 6. Remember and have definitely used. 7. Remember, don't remember if I have used it or even if I understood it. 8. I remember I tried several tricks with random and pseudorandom. 9. I probably didn't understand its use. 10. I remember but did not remember such use. I might confuse it with some other machine/OS wait command. Thanks!
Thank you so much! This will definitely come in useful for some software I am programming. I can’t believe I hadn’t figured out that you can input multiple variables!
In BASIC logics, STOP should preserve runtime variables and their values, but END could deallocate those variables, so you could get error, then trying to peek variables after END.
This! Very useful. (198 is the index of the keyboard buffer. Setting it to zero resets the buffer, ignoring any previous contents. Waiting for it to change to 1 waits for a key press to happen. GET finally reads this as a string, which happens to be single character, as this will be the contents of the keyboard buffer up to the current index, which will be 1.)
Sagan Android - yes, GET reads a character from the default input device (the keyboard) and the WAIT construct will pause the program until a key press actually occurs. E.g., we display a menu with numbered items (1, 2,…) and want to wait for the user to pick a choice: 100 PRINT "PICK YOUR CHOICE (1..4)" 110 POKE 198,0:WAIT 198,1:GET K$ 120 REM now fork on this using ON GOTO 130 REM (ASC() provides the ASCII code, "1" = 49) 140 A=ASC(K$) 150 IF A>48 THEN ON A-48 GOTO 1000, 2000, 3000, 4000 160 REM invalid choice, we fall through out of range, retry 170 GOTO 110 (Extra bonus scores may be achieved for using POKEs to activate and deactivate the blinking cursor, while we wait.)
Here's a small menu demo running on an emulated PET 2001 (same BASIC, but using a different address for POKE & WAIT): www.masswerk.at/pet/?data=base64:MTAwIFBSSU5UIENIUiQoMTQ3KTsiQSBTSU1QTEUgTUVOVToiCjExMCBQUklOVDpQUklOVCAiICAiO0NIUiQoMTgpOyIxIjtDSFIkKDE0Nik7IiAgSVRFTSAxIgoxMjAgUFJJTlQ6UFJJTlQgIiAgIjtDSFIkKDE4KTsiMiI7Q0hSJCgxNDYpOyIgIElURU0gMiIKMTMwIFBSSU5UOlBSSU5UICIgICI7Q0hSJCgxOCk7IjMiO0NIUiQoMTQ2KTsiICBJVEVNIDMiCjE0MCBQUklOVDpQUklOVCAiICAiO0NIUiQoMTgpOyI0IjtDSFIkKDE0Nik7IiAgSVRFTSA0IgoxNTAgUFJJTlQ6UFJJTlQgIlBJQ0sgWU9VUiBDSE9JQ0UgKDEuLjQpIjpQUklOVAoxNjAgUkVNIEtCRC1JRFggUEVUOiAxNTgsIEM2NDogMTk4CjE3MCBQT0tFIDE1OCwwOldBSVQgMTU4LDE6R0VUIEskCjE4MCBPTiBBU0MoSyQpLTQ4IEdPVE8gNTAwLDYwMCw3MDAsODAwCjE5MCBHT1RPIDE3MAo1MDAgUFJJTlQgIj4gWU9VIFBJQ0tFRCBJVEVNIDEuIjpFTkQKNjAwIFBSSU5UICI+IFlPVSBQSUNLRUQgSVRFTSAyLiI6RU5ECjcwMCBQUklOVCAiPiBZT1UgUElDS0VEIElURU0gMy4iOkVORAo4MDAgUFJJTlQgIj4gWU9VIFBJQ0tFRCBJVEVNIDQuIjpFTkQ=&autorun=true
Oh, BTW: If you ever need to do some modulo operation to get binary values, use binary functions instead: For example don't use DEF FN LO(X) = X-INT(X/256)*256) use DEF FN LO(X) = X AND 255 Try thinking binary as often as possible, dividing values is a costly operation on all CPUs and functions like modulo are always performing a divide procedure internally. There is just one tiny nuisance be careful with negative values.
Unfortunately AND interprets X as a *signed* 16-bit value so effectively it's just a 15-bit positive value. Any value > 32767 results in an ?ILLEGAL QUANTITY ERROR which is a huge limitation.
Another very cool and useful video from the Famous Dancing Hand. :) I did a quick check to answer an earlier question.A user-defined function can call another user-defined one no problem. No reason it shouldn't be allowed, as long as the function being called is defined first. 10 DEF FN A(X) = X + 1 20 DEF FN B(X) = FN A(X) + 1 30 PRINT FN B(1) Run it and it prints "3". Good on touching on the USR function. Sometime I intend to study up on how floating point is implemented by the BASIC interpreter. (It hasn't been a priority.) I have a seriously hard time believing it's some extraordinarily difficult and untouchable thing like the computing masses have repeatedly been led to believe. As for WAIT, it doesn't help when the user manual and even the programmer's reference manual confront readers with shit talk like "for most programmers, this command should never be used." I didn't realize you had to be a certain kind of programmer to use a command. The important things to understand about WAIT are the concept of bitwise operations (AND and Exclusive OR in this case, and these concepts and that of 16-bit twos-complement signed numbers are useful when taking advantage of the relational, AND, OR and especially NOT operators) and it has to be something outside of your program that changes the contents at the memory address being WAITed on. In practice this will be one of two things: a control register (e.g. the raster interrupt bit on the VIC chip interrupt status register: bit 0 of 53273), or a memory address being written to by the routine running in the background every 1/60-second, such as the examples given in this video which involve the keyboard and Datasette.
In theory DEF FN might be defined recursively, like DEF FN FAK(X)=FN FAK(X-1)*X, but it won't work because the no possibility (to my knowledge) to setup an ending condition in a numeric expression. Finally it leads to an OUT OF MEMORY ERROR.
You can exploit dynamic variable scope to make some surprisingly useful stuff in DEF FN, though you may run afoul of FORMULA TOO COMPLEX or OUT OF MEMORY errors if you get too deep. This is typical of my date-processing BASIC programs: 1 DEF FNM(N) = Y - N * INT(Y/N):REM MODULUS, ASSUMES DIVIDEND IS Y 2 DEF FND(N) = FNM(N) = 0:REM DIVISIBLE? 3 DEF FNL(Y) = FND(4) AND ((NOT FND(100)) OR FND(400)):REM LEAP? Since FNL calls its parameter Y, FNM and therefore FND operate on it no matter what the global variable Y is set to, and FNL correctly returns whether or not its argument is a leap year. I have a further extension that actually returns the number of days in a month using a function without an array, with logic like 30-((m=1)or(m=3)...)+(2+fnl(y))*(m=2), but while you can barely fit it on a line with single-digit line number, the D▔ abbreviation for DEF, a single-letter function name, and no spaces, most Commodore BASICs blow up on that number of nested function calls.
ON GOTO / ON GOSUB was used extensively in all BASIC dialects. I used it a ton back in the 80s. It was roughly equivalent to a switch / case statement in C and was much nicer than a bunch of IF / ELSE statements. I also saw it used a great deal in various source listings, like in Compute! magazine and the like. So I don't think I'd call that one rarely used.
You have convinced me. I hated BASIC in the good old days when I built my first computer. Right now I have a small computer on the breadboard. Just six ICs, but it's already talking to a Terminal and runs at blistering 6 MHz without even getting warm. I will take the time and write some mor assembly code to test it and not simply install a BASIC interpreter. C never was a thing for 8 bit processors, but FORTH may be a good candidate.
I'm my own compiler right now. I'm adding a memory test to the menu. Anyway, a C compiler puts some demands on an 8 bit computer. At least two disk drives, better a hard disk, as much RAM as you can get and a fast processor as a bonus. Really expensive stuff before the C64 and becoming affordable after the 16 bit computers had taken over. I don't doubt that such compilers existed, but they probably were written when the 8 bit computers already were on the way out and the drives had become cheap enough. Today, I use cross assemblers or compilers and write my stuff comfortably on a PC, compile it there and test it on an emulator before porting it over to the real thing. But if I want to write some quick code on the not yet very sophisticated breadboard computer, something with such modest requirements as FORTH would be my choice. BASIC really never interested me. It's too slow, too limited and also too demanding on a small computer's sparse resources.
@@CDP1861 You didn't need two drives with the C compiler that I had. It would work with just a 1541 disk drive. Having said that, though... it was rather akin to running GEOS with a single disk drive. You could do it, but you couldn't do much with it because there would be so little space left on the disk. IIRC, they recommended a 1581 disk drive. (There was no copy protection, so you could copy the whole thing over to a 3.5" disk and have more than half of it available.) And actually, it was written in 1986 (remember that the C64 didn't stop production until 1992). So it was somewhere during the peak of the popularity of the 8-bit computers, not when they were "on their way out".
@@SpearM3064 Yes, there are C compilers out there, but I newer saw a compiler on a C64 producing good code which could not be written much better in assembly (for size and speed). A Forth environment was more appealing to me all the time (regarding the development environment and ease of programming - just in case you have arranged with Forth's cumbersome syntax) ;)
Can you use functions within functions? can LO(X) be =X-FUN HI(X)*256? ( I know I can just fire up an emulator and check but I thought others might like to know too. EDIT: I see that is does work and that you answered this below in one of the other comments. Neat!
At 3:15 on line 20, could you have used the first DEF FN in defining the next one? Something like: 20 DEF FN LO(X)= X-(FN HI(X))*256 Would that work? It's been 35+ years since I used DEF FN so at the very least I may have a syntax error there.
Yes, is possible (you may omit the parenthesis and write X-FNHI(X)*256. The problem is the execution time is slightly longer. Under some extreme circumstances the additional stack usage of this kind of nested calls may cause a FORMULA TOO COMPLEX condition (not the problem in most cases if at all). An execution penalty may arise from the fact every function is searched linearly like a simple variable (functions are located in the variable area). But if - as usual - a DEF FN comes early in the program it will be found fast. For code speed up (instead of saving space) it would be better to define HI and LO without referring other FN functions.
ON GOTO has the advantage over IF GOTO that it may fall through and continue in the same line. To replace IF A > 0 GOTO 99 : REM DEAD END you may write ON -(A > 0) GOTO 99 : REM HERE WE CONTINUE If one is looking for a possibility the squeeze the program further ...
But "cont" is a BASIC statement whether it's in a program or run directly. OK, Robin, now... as a request for my birthday today (Feb. 23), will you please make a video showing us what happens if you make a program with "cont" that you've run right after turning the computer on?
Using functions actually can speed up basic executing of that bit of logic. Reading joystick port addresses in a game is actually a lot faster when doing it this way as you don't have to include the logic in the main basic loop. Using functions you can actually speed up a basic game quite a lot by using FN for a lot of the game logic and keep the basic game loop small.
Not necessarily that much faster, since your function still had to PEEK the joystick ports, and if you wanted to filter out a specific bit, you had to perform a logical AND. However, in some cases it could be more efficient (or at least improve the readability of your code). For example: 10 PRINT CHR$(5);CHR$(147) 20 JY=56322:SC=1024 30 DEF FN UP(X)=-((PEEK(JY-X) AND 1)=0) 40 DEF FN DN(X)=-((PEEK(JY-X) AND 2)=0) 50 DEF FN LF(X)=-((PEEK(JY-X) AND 4)=0) 60 DEF FN RT(X)=-((PEEK(JY-X) AND 8)=0) 70 DEF FN FB(X)=-((PEEK(JY-X) AND 16)=0) Let's say you're moving a character around the screen, and its location is determined by its X,Y location. These functions would let you do something like this: 90 X=19:Y=12 100 POKE SC+X+40*Y,32 110 X=X-FN LF(2)+FN RT(2) 120 Y=Y-FN UP(2)+FN DN(2) 130 IF X39 THEN X=0 150 IF Y24 THEN Y=0 170 IF FN FB(2) THEN STOP 180 POKE SC+X+40*Y,42 190 GOTO 100 This simple program would let you move a white asterisk around the screen using a joystick in port #2 until you pressed the fire button. I'm just providing an example of what *lactobacillusprime* is talking about, which is using functions to read the joystick. There's _lots_ of ways this program could be optimized and/or improved. For example, there is a way to replace lines 110-160 with a single line of code. If I wanted to, I could compress the entire program into about 8 lines of code.
I remember on ZX Spectrum there was a randomize usr command that would give side audio bars and "saving" sounds as if the computer was saving some random stuff. I recall there was something similar on the C64 where you could either enter a SYS command or a USR[] and it would suddenly start outputting data to the cassette. So 1) Is my memory correct that there is a command on the C64 to do that and 2) What the hell was it saving??
Seriously!?!?? You mean I could have been writing functions in basic 35 years ago? Unbelievable.... my world just got flipped upside down. I know this sounds like a joke, but I am 100% serious. I would have bet any amount of money that this was not possible. That is how confident I was that this was not a thing. Jesus.... what else have I been wrong about literally my entire life?
Actually, other dialects, like IBM BASIC from the early 1970s, support multiple lines for USR functions and you could do full fledged functions with this. Sadly, MS BASIC (and by this Commodore BASIC) just features a rudimentary, single-command variants of this and everything you do has to fit in a single assignment, which also provides the return value. Seeing some listings of the more potent dialects is some of a revelation…
Awesome, sir. Instead of loading a game from tape or disk, why don't I just flip open that awesome manual and play with this BASIC stuff on my TV! Spent some hours reading the example code. Just as cool as pressing the fire bottom. :-)
ON GOTO/GOSUB would be something that a FORTRAN programmer would find familiar (as it is analogous to the "computed GOTO") One place where an ON GOTO/GOSUB could often get used is with a "pick-a-number" menu (i.e. a list of numbered options is displayed, and user enters the number representing the selection) -- the ON GOTO/GOSUB ends up being more concise than using a simple IF ladder.
2:25 I don't suppose you can do a PRINT FN A(1) on the command line. Oh wow, you can. I was expecting an ?Illegal Direct. Is FN actually useful? I suppose it might be faster than a GOSUB. 3:09 What happens if you DEF FN LO(X)=X-FN HI(X)*256? Does BASIC handle the nesting and reusing the same parameter name? 4:17 If you cursor around the screen during an INPUT and then return to the line with the "?", the prompt will be included in the INPUT. You could have entered your number on a blank line. I was assuming that you wout edit out the ?Redo From Start, but you kept it. 5:54 I remember lots of BASIC game listings in magazines using a couple of ON statements. 8:33 I often use numbers like 10e6 instead of 10000000.0 in code since the former is much more readable (10-million) and the latter is just "1" with an unknown number of zeroes. 10:00 I think that's a design flaw, since it causes commas to be disallowed as input to a statement like INPUT A$. 17:00 To avoid this, I always started games with A=RND(-TI). Maybe A=RND(-TI*RND(0)) would be slightly better. 23:46 The main reason USR() is rarely used is that you can only set it to point to one machine-language routine at a time, where SYS takes the address as an argument. 28:00 I assume WAIT was only included as an Easter-Egg delivery vehicle. 30:30 Interesting: if I type "patreon.com\8bitshowandtell" into my browser with the faulty backslash, it gets automatically translated into the proper slash character. If only MS-DOS had done a slightly better job of copying from Unix!
would it be useful if the value you pass with USR() gets used as an offset to a jump table? that way you can have different machine code routines without having to remember all the SYS commands, ayou just have to remember what routine was 0, 1, 2, etc
(2) Yes, it does. If you type the program below, it will print 100 and 16, as expected. 10 DEF FN HI(X)=INT(X/256) 20 DEF FN LO(X)=X-FN HI(X) 30 PRINT FN HI(4196),FN LO(4196) (3) and (6) That's the reason why I wrote my own custom input routine that allows you to set the length (up to 254 characters; normally INPUT is limited to two screen lines). My routine also does range checking (you can limit input to alphabetic, numeric, or alphanumeric characters by setting a flag). Although if you want the user to input multiple values at the same time, it's up to your program to parse the input. (7) I always used A=RND(-TI/π). (8) On the other hand, USR can also be used to _return_ a result from the machine language routine. With SYS, you _usually_ have to store the result in some memory location and use PEEK to retrieve it. (Unless you know how to use the built-in ROM routines to pass parameters to/from your routine.) (9) Not necessarily. WAIT can be used for other purposes. For example, memory location 198 includes the number of keys in the keyboard buffer. Look at the two lines below, which both do the same thing. (They both wait for a key to be pressed, and return that keypress in A$.) 10 GET A$:IF A$="" THEN 10 20 POKE 198,0:WAIT 198,1:GET A$
- C-64 BASIC, RND(0) and IBM BASICA, RND(TI) are nearly identical. Both seed the random number function from the system's "time of day" clock. It may have been QBASIC that added RANDOMIZE(timer) which achieved the same as your second, better function. - "WAIT" was a left-over from earlier "univac" teletype computing days. It was the fundamental INPUT where the program stops everything and waits for user input. The problem with it is ... it stops everything and waits for user input, or a change in state of a hardware register. It is about the equivalent of initiating a software interrupt. The only thing running on the C-64 (or IBM) during a WAIT command is hardware interrupts. It's the same with IBM BASIC. It is the only command in those languages where it was strongly suggested you figure out another way to accomplish the same goal. It didn't matter when the teletype stopped printing, but in modern software the program usually needs to handle other things like screen updating (and such) while "scanning" for user input.
8:33 I make the tokenizers I write accept underscores as digit separators to allow numbers to be more readable, e.g., 10_000_000.0. Grouping is also useful in other bases, e.g., $DEAD_BEEF, %1100_1101. It chafes me when credit-card entry forms don't allow spaces or dashes. What, I'm supposed to trust you with my credit-card information when you can't even parse separators?
@@proxy1035 It could be done, as long as there's not more than 256 offsets. (I can't imagine a jump table bigger than that anyway. Most of the programs I wrote had a dozen functions or less.) The code would look something as follows: START JSR $DBA7 ; convert the value in the USR function into an integer in memory locations 97-98 ($61-$62) LDY $62 ; Y register will contain the function number (0-255) LDA MSB,Y ; most significant byte of the location to be jumped to STA JUMP+1 LDA LSB,Y STA JUMP+2 JUMP JMP $xxyy ; $xxyy will be replaced by the actual vector of the function to be performed ... MSB .BYT xx xx xx xx xx xx xx xx LSB .BYT yy yy yy yy yy yy yy yy
15:22 can you add a direct link to the wiki sub-page you mentioned on basic commands? I get the impression you had a specific sub page in mind - maybe one that notes the oddities and quirks of some commands?
Sure, I just found it on the page for CONT: www.c64-wiki.com/wiki/CONT I haven't found a page that collects a bunch of the weird stuff together, that'd be fun to read.
Well... not quite. It's better to use a specific "seed" first, like A=RND(-50), before you start generating the game world. That way, it will generate the same sequence of numbers every time. RND(1) always generates the same sequence of numbers when you first turn the computer on, but what if someone ran another program before running your game? It might start in the middle of the sequence, or it might have been given a different seed (when I wrote a game, I always made sure that one of the first things it did was A=RND(-TI/π) to ensure that it would start with a unique seed every time I ran the program.) But other than that particular quid pro quo, you're right.
@@SpearM3064 In addition I would suggest to use the widely used phrase A=RND(-TI) to setup the sequence depending on the current timer. If not autostarted in an emulator the TI value gives enough entropy for a random seed.
Great Video, as always. Could you make a video about the "ext in" pin of the SID chip? I am really curious how to access the data that comed into the SID, and what possible applications are possible.
I just ordered one of those nice A/V breakout adapters with the Audio In. I plan on making a video about it once it arrives. But a disappointing spoiler: the audio input isn't available to the CPU in any form. As I currently understand it (I will research more while making the video) the audio just passes through the analog filters in the SID and then back to the analog output again. There is no analog to digital conversion, and no way of digitizing the audio, unfortunately.
@@8_Bit Wow, thank you. That are great news. I am really looking forward to that episode. I think I never heard that anyone actually used this feature of the SID.
@@pgodwin Thanks for the info. I watched the video (It's called "How Speech Synthesizers Work", circa minute 10). God, I love the sound of these old speech synthesizers. Futuristic and yet retro :)
Neat, I actually didn't know about half of those!
Hi
x16 BASIC V2! Whenever and however the incarnation is produced, I am really looking forward to writing code for it :)
But you did know about paperclips and Dremels.
I love your content!
I just love this channel. There is so much low quality content on UA-cam. It's so refreshing to have really interesting videos on here.
what do you mean- you don't like videos of make-up influencers reacting to the reaction of an unboxing of a professional VFX artist who never had a job in that field but is a bright star on reddit?
@@olik136 They put professional VFX artists in boxes now?
Nah you just gotta use that "don't recommend channel" button like crazy!
I like how he shows things and is willing to admit he screws up and keeps to topic mainly
BASIC V2: DEF FN...
Everybody: no one is ever going to use that!
Python: Hold my lambda
I used it on ZX-Spectrum (still do on emulator)
As a matter of curiosity: You can LIST all the individual basic lines you want, except for line 0! 'LIST 0' gives the same result as a simple 'LIST' command. :-)
Great video as usually!
26:15 I was glad to see you instinctively cushioned the release of the play button when pressing stop on the tape player!
Why do that?
@@eugenetswong to reduce the amount of stress in the button mechanism. Not that it's necessarily a common failure point, but if you can reduce the force of that sudden stop, why not?
I/we did on almost every cassette players as the spring actuated release was very annoying. At least in closed space. When we listened cassette outside, then we used ALL the spring force, without dampening. Thank you, I almost forgot it!
@@batlin that's great info. Thanks.
@@batlin however, wouldn't you by that increase stress to the actual tape?
the button mechanism can be replaced, the data on the tape (potentially) can't!
This was nice to see but I do remember seeing these being used in long BASIC listings in various computer magazines. (The German 64'er and the Sonderheft particularly). The multiple parameter INPUT was very often used with the disk drives, so INPUT#15,A,B$,C,D (IIRC!) would be used to check the disk error. Menus would use the ON A GOSUB for the various submenus, etc. Kinda makes me miss typing in long BASIC listings as a result. So thanks for this trip down memory lane :)
There's a potential issue with RND(0) depending on how you use it - if it's called at regular intervals, it will produce a limited subset of random numbers. I remember doing experiments like 10 POKE 1024+(RND(0)*1000),160:GOTO 10 and seeing that it never actually filled the whole screen with inverse-space characters. Instead, what I recall various books (or BASIC programs in REM statements? It's been a while) recommending is to use a single X = RND(-TI) statement to seed the pseudorandom number generator with the number of jiffies since the power was turned on, which is highly unpredictable, followed by RND(1).
The random seed value has always been a great way to create the same levels (procedural) over and over again with just one number. I remember the game Modem Wars using this. You entered a sequence of characters, this way you could make a word, name etc... and use that to generate the level. The game could then simply convert the letters to the ASCII values and use that as the seed for the random number generator. Minecraft still uses this technique to this day, you enter a number to generate the same world. Great stuff anyhow.
Yes, I love this concept too. I keep meaning to make a video about either Pitfall or River Raid or both, as they use the same concept to build large worlds in a tiny 4K cartridge on the lowly Atari 2600.
@@8_Bit A good one to do on this would be about Elite, the space game. It has a massive universe that was completely procedural for the C64 and other similar machines. That's probably the grand daddy of them all. I was always impressed with what they did in that game.
I didnt realise Pitfall was kind of procedurally generated but it does make perfect sense - from meory iirc negative numbers put into a random number generator would return the same results but positives wree truly random @@8_Bit
By the way in BASIC 7.0 (C128) the SYS command was extended to pass parameters to (unfortunately not back from) an assembly program. Parameter 2,3,4,5 are A, X, Y, status in this order, if specified.
In BASIC 2.0 the SYS command loads the A, Y, X, and STATUS from $030C-$030F (780-783) before calling the function and stores the A, Y, X and STATUS at the same location after the call.
Thanks for the tip at 23:49! I never met this handbook, I downloaded this in PDF, very very useful, although I still have my old guide books.
This was a perfect video, waked up joyfully my sleeping memories, I haven't used C64 professionally almost three decades.
A fair amount of those commands I've used running a BBS back in the day. Love your channel!
"WAIT" in most program languages is an evolutionary hold over from a time when computers mainly controlled mechanical devices and output to teletype for display. It's specific purpose was to halt all processing at a particular place in the program ("wait" = "freeze the program counter") until a specific condition was met, either in a manufacturing process or to wait for a user input. This is why it is effected only by hard-wired, electrical changes like pushing a button. Most manuals for the various languages that use the command will WARN you to avoid using it (except in specific applications) because it essentially stops the computer from doing anything else other than interrupt driven functions. In some really early (1950s and 60s) CPUs the waiting for an electrical change was all it could do and is the reason most CPUs were later designed with "maskable" and "non-maskable" interrupts with WAIT being put into the maskable category. "SLEEP" is similar, not part of C-64 BASIC, in that the CPU freezes all function for a specified number of clock cycles.
Back in the early days of Amazon you could enter quantities up to 10E300. Adding 2 different items to the cart and setting quantities of each to 10E300 would crash the system. Now it appears to be limited to 999.
I thought I might pass along that using DEF FN you can do IF/THEN, but you have to use boolean math. Boolean operators return 0 or -1 for false and true, respectively. Also, you can use variables defined in the program, not just the passed parameter, in your function. So, let's define S as a "switch" to control the value in DEF FN FOO(X)= X*2 *-(S10). Using a bit of addition and multiplication identity math we can get one side of the equation to zero out by the boolean returning 0 or -1. If S is 10 or less, it returns X times 2; otherwise, it returns X times 3. You can make this a bit simpler if your controlling variables are a 0 or 1. Instead of testing with a boolean operator, use AND. AND returns 0 if the two numbers don't match or the number if they do, so you don't have do the negation.
RND(-1) or other negative values can be used in games to program pseudo preprogrammed surroundings.
I once made a side scroller using BASIC together with assembler routines, the user could chose 4 different ways he wanted to go, they where generated with RND(-1), RND(-2), RND(-3) and RND(-4).
I used most of those commands when I got my Commodore VIC-20/64 by reading its BASIC user manual... those were the good old days.. though I didnt learn much with machine language, since it wasn't built in.
Great tips!
To make the USR function more useful, you could call the subroutine located in BASIC ROM that turns the floating point number into a 16-bit value. I believe that routine is called FACINX and is located at $b1aa. The 16-bit number is returned in the A and Y registers, hi and lo byte, respectively. I used it to fill the color RAM with a 4-bit value, in ML code, of course, and called from BASIC with the USR function.
A full episode focused on communicating between BASIC and ML would probably be a good idea!
Appendix A of the C64 Programmers Reference Guide has derived trig functions that can be implemented with DEF FN.
Some of those super-complicated functions freaked me out as a kid!
@@8_Bit does not have to do with this video exactly but I have a question.
In that menu that was brought up when you power cycled AKA and turned off then on in a bit The c64 AKA Commodore 64 what is it referring to and the option for extended life that's what I'm curious about anyone please comment thanks.
I watch each video of yours and I have to admit that your way of presenting all those things is so good. I would even say that your videos are quite good for beginners to start with real IT. I mean that you explain everything as to the people who doesn't know the basics while doing a showcase of what you talk about. I think about that stuff like memory addressing., how CPU works, all basic concepts in programming etc. It is soo goood and your approach is so much quality to me.
I agree. There were 2+ times, when I thought that he must have really prepared his lesson. I really liked how he demonstrated features in many ways to ensure that we got a good grasp of the feature.
I started programming on TRS-80 Basic, and during my younger years messed about with Apple 2 Basic, ZX80 Basic and plenty of others that in my middle age I just can't remember anymore.
At some point I have used all of these commands in some way, especially the ON..GOSUB (a very useful thing indeed).
Its been very interesting watching this video and realizing how many of these functions were available in Commodore Basic. Many thanks for the insight 👍
Hi, I learned a lot from this video, thank you very much! I wrote a BASIC game that I can improve now with the new knowledge of this video: First, I will exchange the RND(1) to RND(0) just to get ALWAYS a new random series of numbers each time the commodore powers up. Second, I will use the on-gosub command for the joystick query. And third, the wait command is very useful, each time I wait for the fire button of the joystick. It’s way better than doing it with if-then-loop. That’s very helpful, thank you very much and keep up your good work👍👍👍
I knew all this stuff, except the 9999 example. However, it's so relaxing to listen to your calming voice. Thx for being on UA-cam.
Another quirk of RND() - try these two lines:
10 PRINT [clear/home] ;
20 POKE 1024 + RND(1)*1024, 81 : GOTO 20
Run this then change it to ... RND(0)*1024 ... and run it again. You find the '0' argument RANdom formula skips numbers in patterns, depending on the size of the multiplier. Although RND(0) generates different sequences, depending on the range of numbers you are trying to generate, you will never see certain numbers.
The BEST method:
10 X = RND(0) : PRINT[clear/home] ;
20 POKE 1024 + RND(1)*1024, 81:GOTO 20
By using the RND(0) in a throw away call to the RND function, C-64 seeds the random generator with a value read from the "time of day" clock on the CIA chip. IBM's BASICA or QBASIC equivalent is RANDOMIZE(timer). The odds of reading the exact same number in two different runs of the program are astronomically huge against it. Then using RND(1) for the rest of the program it does not skip numbers. I'm not 100% certain why RND(0) times a number greater than 256 produces this skip, but it is consistent, and the skipped numbers are always the same depending on the value of the multiplier. This: RND(0)*257 will never return the number 128; RND(0)*259 skips 86 and 172, RND(0)*512 skips every other number, meaning all the odd numbers ... and so on ...
If you look at the C64 BASIC ROM listing at $E0A5 at www.unusedino.de/ec64/technical/misc/c64/romlisting.html#E097 , calling RND(0) fills the floating point mantissa with (MSB : : : LSB):
CIA1 Timer A Lo : CIA1 TOD 1/10th sec : CIA1 Timer A Hi : CIA1 TOD Seconds
The first byte (MSB) is fine and can have all 256 possible values‡, but the rest of the bytes have limited bit patterns: TOD 10TH always has bits 4-7 as '0', Timer A Hi only goes up to around 17000/256 = 66, and the TOD seconds can only have values $00 to $59 in BCD.
‡ technically, for one in the 66 values of the high byte, the range of Timer A Lo will be limited to the low bytes of the timeout value rather than all 256 possible values.
It seems that in VICE at least, the CIA1 TOD clock isn't running at all, so the second byte of the mantissa is always 0, making RND(0)*1024 will produce a numbers that always have '00' in its two least significant bits (the column stride of 4 in the test program).
@@csbruce The TOD clock has a latch function. The registers are all zeros until you read (or write) the tenths of a second. (The TOD clock _is_ running, but the registers are "frozen".) The C64 does not do this on startup, so the TOD clock only "starts" *after* you use RND for the first time! As a result, the "random seed" is always the same the first time you use RND(0) after turning the computer on. The solution is simple. If you POKE 56328,0 _before_ you use RND(0) for the first time, it "unlatches" the TOD registers. Then RND(0) will work as intended. (Confirmed by testing with VICE.) EDIT: Or PEEK it with A=PEEK(56328). Either a poke or a peek will unlatch the registers.
Ah, wow, nice research!
@@SpearM3064 But is VICE a valid test system? I think this needs confirmation on a real C64.
@15:1- That's a bug in the stack PC, if I ever saw one. Obviously BASIC is setting a PC limit to a line number if the number is > the current limit, but it isn't cleared. I don't ever recall running into that in the era, but it's an interesting observation. I would expect it to do this if you execute a programme that terminates prior to its max PC value, and I may have seen that happen, long ago.
Just want to add a bit on USR(). USR is not limited on numeric values. The interpreter calls the expression evaluation which may result in a string or numeric value. It depends on the calling context what is expected (otherwise a TYPE MISMATCH may occure). One may take a look on the examples of the german variant from c64-wiki.com (I have to take over the examples to the english site some time in future): www.c64-wiki.de/wiki/USR#Beispiele.
Because the interpreter passes the control after the expression evaluation but before checking for the closing parenthesis to the USR code, you may add the interpretation of additional parameters in your function. You have only to ensure that the interpreters parser stays in front of the closing parenthesis and you are fine.
I wish I had UA-cam when I was a kid. I'd have been so thrilled! OK, well, I still am thrilled. I need a new 64. 🙂
Member when you used to stay up late at night coding on the C64? I member.
Happened to me yesterday.
Hi sire,
the STOP command is not like breakpoint. STOP IS breakpoint.
When your program finishes or executes END, the BASIC is supposed to do some housekeeping on variables. And it should clean the internal execution stack.
Contrary the STOP/CONTinue,
when reaching STOP, the BASIC suspend execution and waits for your interaction with system. The variables and internal execution stack are not affected. So when you finish your debugging where you interrogate variables, you command CONT to resume the execution of the program from exactly the point it was stopped.
During the STOP you can also change the values in variables.
So the original purpose of STOP/CONT is to help programmer debug its program.
Have a great day,
-- Radek
Had I known that when I was 8 years old? I'd of saved myself so, so, so much time.
Yes, that's exactly how I remember STOP/CONT being used: for debugging. Interesting, though, from the video that you can also use CONT after an END. It had never occurred to me to even try that (though it probably wouldn't be too useful), since STOP and CONT are so strongly paired with each other.
I did a fair bit of experimenting and as far as I can tell, END in Commodore BASIC does not do any clean-up of the stack or variables, so that's why I said that apart from the debugging-friendly aspect of printing the line number for STOP, they appear to be identical in this implementation. If anyone has any specific examples of how they differ, I'd love to see them.
@@8_Bit As you pointed out, functionally they're the same, other than the STOP command tells you what line number you stopped at. You can even use CONT after an END.
Well, it’s actually „kind of” breakpoint from the view that normal breakpoints are not put into the code literally, they are just markers set to particular memory locations.
"ON SGN(I)+2 GOTO ln1, ln2, ln3" makes also a great replacement for FORTRAN's original arithmetic-if statement ("IF (I) label1, label2, label3"), in case you need it.
("I" can be any arithmetic expression, as well, in both cases.)
Calculated GOTO statements made for really unreadable and undebuggable code. Powerful, but so hard to find errors.
@@Doug_in_NC "Real Programmers aren't afraid to use GOTOs." And you can place comment lines before entry points if you need this.
@@gregor_man Spoken like a true FORTRAN 77 aficionado 😂😂😂😂. Seriously though, if used properly you are right. I was just saying that calculated GOTOs can be horrible to debug unless you are absolutely certain about which lines you can go to. Not so much the ON GOSUB mentioned here where you know the options are clear, but the equally legal, at least in Commodore BASIC 2.0 X=15*Y-12*Z: GOTO X,
@@Doug_in_NC :)) This kind of jumps could make the program more interesting. My "hobby" was creating complex procedures in one command line, during a debug process, maximum 80 characters, no jumps. Cultivates the mind. ;-)
I'll admit I had no idea how USR and WAIT worked before this.. Good walkthrough
Great Commodore BASIC video Robin! As always.
I think I used all of these commands at some point while programming my C64/C128. I remember using the WAIT command as a "security verification" if you didn't press the correct key they program wouldn't continue to execute. I was a teenager that would rather write weird programs than play games.
I had a Commodore 64 in 1987 during college and a dot matrix printer. To print I basically had to write code....it was not easy as pushing a button or giving a voice command...although I did talk (or scream) at the C64 often....
wow, this takes me back 33 years and some knowledge I forgot I had in me came back up. I used to type programs from magazines and recognize most of these , except the CONT
Absolutely beyond awed! Truly impressed!
Thanks for this real world example of some commodore basic (I only ever had BBC basic, but still relevant)
Good stuff. Thanks for keeping the C64 BASIC alive. Good memories.....
Why just memories? Please feel encouraged to grab a C64 (emulator) and get creative. :-)
I remember I actually did use the scientific notation feature at one point, for some math self-study thing I did.
The LIST range and selective RUN start wasn't that obscure though I think. I used it all the time to edit my larger basic programs and it was invaluable.
The fact that RND(1) gives the same sequence every time after a reset is not solved by using RND(0) instead (because RND(0) is not really random). Instead, just add A=RND(-TI) as your first command to give it a random seed.
Thanks, I needed some help with subroutines it's been years since working with c64 basic.
Last (well almost last I think) I did to my C64 was to transfer some of my BASIC programs to my Amiga 500, I made a userport connector that connected to my Amiga parallel port and did a basic program to read bytes from the floppy and send them to my Amiga 500. On my Amiga I did Pascal (Kickpascal I remember correctly) program to receive and store on floppy. I think I was about 14-15 at the time... It was an amazing experience. Lots of trial and errors haha... I think my final version was transfer 7 bits and use 1 bit for a sync "clock" and that worked the best... It was fun to program the C64 again and with a specific purpose, the Amiga I was quite comfortable with when I did it.
I think I did a peek/poke though but some of it could been used by the wait routine...
Can remember the actual purpose of it though but I think I did it because I just got an early C64 emulator and wanted to see how it worked without retyping everything again or I was just testing and seeing if I could get my C64 work together with my Amiga.
Some of what you been demonstrating I do remember and I think I used, but some was news to me aswell!
Great video!
Another quirky thing about C64 basic - if you put a line in your code with a remark and a shift L, (10 rem L), it will cause a syntax error on that line. you can put a ": rem L" at the end of each line to make your code REALLY annoying to attempt to read. It's not a useful/functional thing... but it was used as crappy copy protection - Artworx's Strip Poker used it to hide their basic code. There were a lot of fun things hidden in the C64. :)
Commodore Forever! My basement is filled with Commodore machines.
Awesome run-down of those old commands. I had forgotten most of them even existed in the C=64 BASIC!
The WAIT command basically is waiting for an interrupt signal. That's why the Eject button doesn't caUse WAIT to end. It only opens the door of the casette player, it doesn't send an interrupt to signal it's ready to send or receive data.
The limitations of the C64's DEF/FN really make me realise how I was spoiled by BBC BASIC.
So true! I just replied that I didn’t know most of these but I loathe basic and especially Microsoft basic. I skipped it quickly in favor of assembler especially since we also had a PDP11 with Pascal and C on it that I was allowed to use (was from my dad’s company). M$ basic feels so spartan to any other basic.
@@rdoetjes A spartan BASIC is better for an 8 bit machine limited to 64 K RAM than a bloated one that eats more memory, this was a conscious decision by Commodore. The C64 BASIC is a CBM BASIC 4 reduced to the commands of version 2 but with all of the bug fixes that went into 4.
I used to use the LIST command with a range frequently. When your program would get so long that you couldn’t easily stop it and make corrections, i would list it with control and remember the range. It worked well for my 10 year old brain 😂
Great video, Robin. Always wondered about these commands, particularly USR().
It is possible to have an IF in a DEF FN -- by using what could be called an "implied if" that utilizes the (typically) numerical value returned by boolean expressions. The trouble of course is that the "true" and "false" representations was largely implementation-dependent:
1. some BASICs would return 0 for false and -1 for true (e.g. TRS-80 BASIC on model I/III/4, IBM-PC BASIC), whereas some would return 0 for false and 1 for true (e.g. AppleSoft BASIC).
2. the boolean operators of NOT, AND and OR would introduce yet another wrinkle. In some implementations the return value would perform a bitwise operation (e.g. TRS-80 BASIC on model I/III/4, IBM-PC BASIC) on the (implied) integer operands, and others would only affect the least significant bit (e.g. Applesoft BASIC).
Aside from the highly non-portable aspect, use of this "implied if" is generally *slower* than having an actual IF-THEN-ELSE statement, since this "implied if" involves using a multiplication for each of the conditions.
I remember, back in the day, starting programs with 10 A=RND(-TI) to get the C64 to randomize. All that time I could've just RND(0)'d instead. In my defence, I was 7 and a dumb 7 at that.
You programmed a computer at age 7; no way you were a dumb 7. 😊
I once made a video game as a kid where the computer controlled grasshopper character flew around the screen controlled by a RND(1) generated number, thinking it would move around at random, as you tried to shoot it. After playing the game several times I realized it was doing the exact same pattern over and over again! Oops!
I noticed the Mapping the 64 book was written by Sheldon Leemon. I have not thought of him in years! I think he used to write a lot of articles in Compute! Back in the day.
Yes, it's an excellent book and he's a very good author. Even in this very reference-heavy book, his sense of humour comes through; he gets a couple nice jabs in on some design decisions of the C64.
@@8_Bit I keep thinking I either met him or spoke to him on the phone 25 or more years ago... Maybe on one of my group Phone Phreaking expeditions back in the mid eighties.
Another fascinating video I've been looking forward too. Thanks man. :-)
Can't you do an Assembly programming in Turbo Macro Pro series for the beginner? Your previous vids 'bout Assembly helped me out a lot. But I think that you could be a very good teacher for a "complete" assembly course from the ground up. I'm reading Jim Butterfields' excellent, brilliant, book on machine language but it's a bit too much about, erm..., machine language in a Machine Language Monitor and less about programming in "full blown" Assembler.
This video has suddenly pushed me to learn C64 Assembly, and yesterday I've completed my first hello world 😁
I had a C64 as a kid but i was too young and I never really used it for coding. I started coding with C on a 386. Always skipped Assembly for some reason until now!
(the fact that this video is about BASIC instead is irrelevant: brains are weird)
Re: USR vs SYS: you can call line reading routines from within a machine code program following an SYS command. I can't remember specifics but I remember writing commands back when I had a C64 as my main computer in the form of SYS49152,.
Yes, that's what I did with some of my routines that I wrote. It basically uses the same ROM routines that BASIC uses to read DATA statements to read the next number after the comma, and convert it into an integer. I don't remember specifics either because I no longer have my "Mapping the C64" book (lost in a house fire). If I could find that book again in PDF form, I could provide sample code.
Yes, but SYS is not a function. USR() is just nice if you have to give some value back in an expression context. With SYS you need to PEEK for the returning registers or doing some variable magic (predefined variables for return values or something like that).
The C64 has the same version of Commodore BASIC as the VIC20 before it. So strictly speaking this video should have been called "10 Rarely Used Commodore BASIC V2 Features"! 😁
I remember not understanding DEF FN in ZX Basic, but using BLITZ now it is super useful. I think in 8 bit basic the DEF FN was as you said just a mathematical equation. In NBASIC the List - list 100-200 etc was essential.
Fire those QA people if they can't understand scientific notation of numbers.
The QA people are only your canary in a coal mine. If they complain, you can be damn sure the users will as well.
Utterly, useless bunch of pseudo programmers. Waste of expensive office space.
One of the videos about disk drives made me connect my usb floppy drive to android phone via USB OTG and write some files to a blue 3.5" floppy disk. It's actually safer than writing to a Pendrive these days, I mean who would be able to read that, they only know the "Save" button icon...
I wasn't a "professional" programmer but I used all of these regularly in small and large programs. Odd. Why do you think they weren't used much??? These are all useful and common commands.
Exactly.
Man, used to use the list 30- method when writing text adventures on the TI 99/4A...
I kind of blasted over BASIC into assembler once I hit its limits trying to build a dictionary. The text was space delimited and contained commas so INPUT# wouldn't work and GET# chewed through memory creating too much garbage - "h", "he", "hel", "hell", "hello" etc. USR only worked with numbers so straight to SYS to read the next word and write it to the screen and then push a Home and CR onto the input queue. 26 hours shrunk to 8. Full blown assembler processed all 6 disks (not just the one) in 20 minutes so at that point, BASIC just became a toy.
Great video! You are clear and concise when showing instructions. Keep up the great work!
So I definitely read those when I read the manual (which was like an IT bible for me when I was 9), but didn't remember some of them.
1. Remember and have used.
2. Definitely remember and have used.
3. Don't remember, definitely haven't used. Possibly because when I was 9 I didn't even know what was that (and there was no Google to look for it).
4. Don't remember but possible used it.
5. Don't remember but most probably used it because I typed some LONG BASIC programs back then (to the point of getting OUT OF MEMORY errors).
6. Remember and have definitely used.
7. Remember, don't remember if I have used it or even if I understood it.
8. I remember I tried several tricks with random and pseudorandom.
9. I probably didn't understand its use.
10. I remember but did not remember such use. I might confuse it with some other machine/OS wait command.
Thanks!
You can also command RUN and line number to run the program skip that STOP or END line.
Thank you so much! This will definitely come in useful for some software I am programming. I can’t believe I hadn’t figured out that you can input multiple variables!
Interesting. I am just getting back into basic after 40 years
In BASIC logics, STOP should preserve runtime variables and their values, but END could deallocate those variables, so you could get error, then trying to peek variables after END.
I am a big fan of in my programs:
poke 198,0 : wait 198,1 : get a$
This! Very useful.
(198 is the index of the keyboard buffer. Setting it to zero resets the buffer, ignoring any previous contents. Waiting for it to change to 1 waits for a key press to happen. GET finally reads this as a string, which happens to be single character, as this will be the contents of the keyboard buffer up to the current index, which will be 1.)
What am I missing here? Is this like a single character INPUT where you need not press the Return key?
@@saganandroid4175 Yes. It fetches a single character from the current input device (by default, the keyboard).
Sagan Android - yes, GET reads a character from the default input device (the keyboard) and the WAIT construct will pause the program until a key press actually occurs.
E.g., we display a menu with numbered items (1, 2,…) and want to wait for the user to pick a choice:
100 PRINT "PICK YOUR CHOICE (1..4)"
110 POKE 198,0:WAIT 198,1:GET K$
120 REM now fork on this using ON GOTO
130 REM (ASC() provides the ASCII code, "1" = 49)
140 A=ASC(K$)
150 IF A>48 THEN ON A-48 GOTO 1000, 2000, 3000, 4000
160 REM invalid choice, we fall through out of range, retry
170 GOTO 110
(Extra bonus scores may be achieved for using POKEs to activate and deactivate the blinking cursor, while we wait.)
Here's a small menu demo running on an emulated PET 2001 (same BASIC, but using a different address for POKE & WAIT):
www.masswerk.at/pet/?data=base64:MTAwIFBSSU5UIENIUiQoMTQ3KTsiQSBTSU1QTEUgTUVOVToiCjExMCBQUklOVDpQUklOVCAiICAiO0NIUiQoMTgpOyIxIjtDSFIkKDE0Nik7IiAgSVRFTSAxIgoxMjAgUFJJTlQ6UFJJTlQgIiAgIjtDSFIkKDE4KTsiMiI7Q0hSJCgxNDYpOyIgIElURU0gMiIKMTMwIFBSSU5UOlBSSU5UICIgICI7Q0hSJCgxOCk7IjMiO0NIUiQoMTQ2KTsiICBJVEVNIDMiCjE0MCBQUklOVDpQUklOVCAiICAiO0NIUiQoMTgpOyI0IjtDSFIkKDE0Nik7IiAgSVRFTSA0IgoxNTAgUFJJTlQ6UFJJTlQgIlBJQ0sgWU9VUiBDSE9JQ0UgKDEuLjQpIjpQUklOVAoxNjAgUkVNIEtCRC1JRFggUEVUOiAxNTgsIEM2NDogMTk4CjE3MCBQT0tFIDE1OCwwOldBSVQgMTU4LDE6R0VUIEskCjE4MCBPTiBBU0MoSyQpLTQ4IEdPVE8gNTAwLDYwMCw3MDAsODAwCjE5MCBHT1RPIDE3MAo1MDAgUFJJTlQgIj4gWU9VIFBJQ0tFRCBJVEVNIDEuIjpFTkQKNjAwIFBSSU5UICI+IFlPVSBQSUNLRUQgSVRFTSAyLiI6RU5ECjcwMCBQUklOVCAiPiBZT1UgUElDS0VEIElURU0gMy4iOkVORAo4MDAgUFJJTlQgIj4gWU9VIFBJQ0tFRCBJVEVNIDQuIjpFTkQ=&autorun=true
WAIT was one I never remember knowing back then. Sure it is "WAIT 174" that waits for the Vblank
Alright, I give up. I finally subscribed. Thanks for the awesome content!
Oh, BTW:
If you ever need to do some modulo operation to get binary values, use binary functions instead:
For example don't use
DEF FN LO(X) = X-INT(X/256)*256)
use
DEF FN LO(X) = X AND 255
Try thinking binary as often as possible, dividing values is a costly operation on all CPUs and functions like modulo are always performing a divide procedure internally. There is just one tiny nuisance be careful with negative values.
Unfortunately AND interprets X as a *signed* 16-bit value so effectively it's just a 15-bit positive value. Any value > 32767 results in an ?ILLEGAL QUANTITY ERROR which is a huge limitation.
@@8_Bit You are right .. damned.
Another very cool and useful video from the Famous Dancing Hand. :)
I did a quick check to answer an earlier question.A user-defined function can call another user-defined one no problem. No reason it shouldn't be allowed, as long as the function being called is defined first.
10 DEF FN A(X) = X + 1
20 DEF FN B(X) = FN A(X) + 1
30 PRINT FN B(1)
Run it and it prints "3".
Good on touching on the USR function. Sometime I intend to study up on how floating point is implemented by the BASIC interpreter. (It hasn't been a priority.) I have a seriously hard time believing it's some extraordinarily difficult and untouchable thing like the computing masses have repeatedly been led to believe.
As for WAIT, it doesn't help when the user manual and even the programmer's reference manual confront readers with shit talk like "for most programmers, this command should never be used." I didn't realize you had to be a certain kind of programmer to use a command. The important things to understand about WAIT are the concept of bitwise operations (AND and Exclusive OR in this case, and these concepts and that of 16-bit twos-complement signed numbers are useful when taking advantage of the relational, AND, OR and especially NOT operators) and it has to be something outside of your program that changes the contents at the memory address being WAITed on. In practice this will be one of two things: a control register (e.g. the raster interrupt bit on the VIC chip interrupt status register: bit 0 of 53273), or a memory address being written to by the routine running in the background every 1/60-second, such as the examples given in this video which involve the keyboard and Datasette.
In theory DEF FN might be defined recursively, like DEF FN FAK(X)=FN FAK(X-1)*X, but it won't work because the no possibility (to my knowledge) to setup an ending condition in a numeric expression. Finally it leads to an OUT OF MEMORY ERROR.
ON GOTO, USR and RND(.) were new to me, mind blown 🤯✌🏼
You can exploit dynamic variable scope to make some surprisingly useful stuff in DEF FN, though you may run afoul of FORMULA TOO COMPLEX or OUT OF MEMORY errors if you get too deep. This is typical of my date-processing BASIC programs:
1 DEF FNM(N) = Y - N * INT(Y/N):REM MODULUS, ASSUMES DIVIDEND IS Y
2 DEF FND(N) = FNM(N) = 0:REM DIVISIBLE?
3 DEF FNL(Y) = FND(4) AND ((NOT FND(100)) OR FND(400)):REM LEAP?
Since FNL calls its parameter Y, FNM and therefore FND operate on it no matter what the global variable Y is set to, and FNL correctly returns whether or not its argument is a leap year. I have a further extension that actually returns the number of days in a month using a function without an array, with logic like 30-((m=1)or(m=3)...)+(2+fnl(y))*(m=2), but while you can barely fit it on a line with single-digit line number, the D▔ abbreviation for DEF, a single-letter function name, and no spaces, most Commodore BASICs blow up on that number of nested function calls.
ON GOTO / ON GOSUB was used extensively in all BASIC dialects. I used it a ton back in the 80s. It was roughly equivalent to a switch / case statement in C and was much nicer than a bunch of IF / ELSE statements. I also saw it used a great deal in various source listings, like in Compute! magazine and the like. So I don't think I'd call that one rarely used.
Wow, idk the wait command did all of that!
You have convinced me. I hated BASIC in the good old days when I built my first computer. Right now I have a small computer on the breadboard. Just six ICs, but it's already talking to a Terminal and runs at blistering 6 MHz without even getting warm. I will take the time and write some mor assembly code to test it and not simply install a BASIC interpreter. C never was a thing for 8 bit processors, but FORTH may be a good candidate.
"C never was a thing for 8-bit processors"... yes, it was. There was a C compiler for the Commodore 64.
I'm my own compiler right now. I'm adding a memory test to the menu.
Anyway, a C compiler puts some demands on an 8 bit computer. At least two disk drives, better a hard disk, as much RAM as you can get and a fast processor as a bonus. Really expensive stuff before the C64 and becoming affordable after the 16 bit computers had taken over. I don't doubt that such compilers existed, but they probably were written when the 8 bit computers already were on the way out and the drives had become cheap enough.
Today, I use cross assemblers or compilers and write my stuff comfortably on a PC, compile it there and test it on an emulator before porting it over to the real thing. But if I want to write some quick code on the not yet very sophisticated breadboard computer, something with such modest requirements as FORTH would be my choice. BASIC really never interested me. It's too slow, too limited and also too demanding on a small computer's sparse resources.
@@CDP1861 You didn't need two drives with the C compiler that I had. It would work with just a 1541 disk drive. Having said that, though... it was rather akin to running GEOS with a single disk drive. You could do it, but you couldn't do much with it because there would be so little space left on the disk. IIRC, they recommended a 1581 disk drive. (There was no copy protection, so you could copy the whole thing over to a 3.5" disk and have more than half of it available.)
And actually, it was written in 1986 (remember that the C64 didn't stop production until 1992). So it was somewhere during the peak of the popularity of the 8-bit computers, not when they were "on their way out".
@@SpearM3064 Yes, there are C compilers out there, but I newer saw a compiler on a C64 producing good code which could not be written much better in assembly (for size and speed). A Forth environment was more appealing to me all the time (regarding the development environment and ease of programming - just in case you have arranged with Forth's cumbersome syntax) ;)
I knew about the list, end, stop and cont commands, but they weren't used too often.
These tricks are so neat, I truly appreciate all your retrocational vids! C=
Can you use functions within functions? can LO(X) be =X-FUN HI(X)*256?
( I know I can just fire up an emulator and check but I thought others might like to know too. EDIT: I see that is does work and that you answered this below in one of the other comments. Neat!
MurderMostFowl I came here to suggest the same thing!
I really like your assembly videos !
I have used ON GOSUB/GOTO, INPUT variables, LIST, END, and RND() in past programs.
At 3:15 on line 20, could you have used the first DEF FN in defining the next one? Something like:
20 DEF FN LO(X)= X-(FN HI(X))*256
Would that work? It's been 35+ years since I used DEF FN so at the very least I may have a syntax error there.
Yes, is possible (you may omit the parenthesis and write X-FNHI(X)*256. The problem is the execution time is slightly longer.
Under some extreme circumstances the additional stack usage of this kind of nested calls may cause a FORMULA TOO COMPLEX condition (not the problem in most cases if at all). An execution penalty may arise from the fact every function is searched linearly like a simple variable (functions are located in the variable area). But if - as usual - a DEF FN comes early in the program it will be found fast. For code speed up (instead of saving space) it would be better to define HI and LO without referring other FN functions.
ON GOTO has the advantage over IF GOTO that it may fall through and continue in the same line. To replace
IF A > 0 GOTO 99 : REM DEAD END
you may write
ON -(A > 0) GOTO 99 : REM HERE WE CONTINUE
If one is looking for a possibility the squeeze the program further ...
But "cont" is a BASIC statement whether it's in a program or run directly. OK, Robin, now... as a request for my birthday today (Feb. 23), will you please make a video showing us what happens if you make a program with "cont" that you've run right after turning the computer on?
Using functions actually can speed up basic executing of that bit of logic. Reading joystick port addresses in a game is actually a lot faster when doing it this way as you don't have to include the logic in the main basic loop. Using functions you can actually speed up a basic game quite a lot by using FN for a lot of the game logic and keep the basic game loop small.
Not necessarily that much faster, since your function still had to PEEK the joystick ports, and if you wanted to filter out a specific bit, you had to perform a logical AND. However, in some cases it could be more efficient (or at least improve the readability of your code). For example:
10 PRINT CHR$(5);CHR$(147)
20 JY=56322:SC=1024
30 DEF FN UP(X)=-((PEEK(JY-X) AND 1)=0)
40 DEF FN DN(X)=-((PEEK(JY-X) AND 2)=0)
50 DEF FN LF(X)=-((PEEK(JY-X) AND 4)=0)
60 DEF FN RT(X)=-((PEEK(JY-X) AND 8)=0)
70 DEF FN FB(X)=-((PEEK(JY-X) AND 16)=0)
Let's say you're moving a character around the screen, and its location is determined by its X,Y location. These functions would let you do something like this:
90 X=19:Y=12
100 POKE SC+X+40*Y,32
110 X=X-FN LF(2)+FN RT(2)
120 Y=Y-FN UP(2)+FN DN(2)
130 IF X39 THEN X=0
150 IF Y24 THEN Y=0
170 IF FN FB(2) THEN STOP
180 POKE SC+X+40*Y,42
190 GOTO 100
This simple program would let you move a white asterisk around the screen using a joystick in port #2 until you pressed the fire button. I'm just providing an example of what *lactobacillusprime* is talking about, which is using functions to read the joystick. There's _lots_ of ways this program could be optimized and/or improved. For example, there is a way to replace lines 110-160 with a single line of code. If I wanted to, I could compress the entire program into about 8 lines of code.
I remember on ZX Spectrum there was a randomize usr command that would give side audio bars and "saving" sounds as if the computer was saving some random stuff. I recall there was something similar on the C64 where you could either enter a SYS command or a USR[] and it would suddenly start outputting data to the cassette. So 1) Is my memory correct that there is a command on the C64 to do that and 2) What the hell was it saving??
Seriously!?!?? You mean I could have been writing functions in basic 35 years ago? Unbelievable.... my world just got flipped upside down. I know this sounds like a joke, but I am 100% serious. I would have bet any amount of money that this was not possible. That is how confident I was that this was not a thing. Jesus.... what else have I been wrong about literally my entire life?
Actually, other dialects, like IBM BASIC from the early 1970s, support multiple lines for USR functions and you could do full fledged functions with this. Sadly, MS BASIC (and by this Commodore BASIC) just features a rudimentary, single-command variants of this and everything you do has to fit in a single assignment, which also provides the return value.
Seeing some listings of the more potent dialects is some of a revelation…
i use all of theose frequently because i bothered to read the manual
You could use the pokes as part of your basic usr(1) call you have more usr(1) calls
Awesome, sir. Instead of loading a game from tape or disk, why don't I just flip open that awesome manual and play with this BASIC stuff on my TV! Spent some hours reading the example code. Just as cool as pressing the fire bottom. :-)
I remember the use of WAIT198,1
PRESS ANY KEY TO CONTINUE
ON GOTO/GOSUB would be something that a FORTRAN programmer would find familiar (as it is analogous to the "computed GOTO")
One place where an ON GOTO/GOSUB could often get used is with a "pick-a-number" menu (i.e. a list of numbered options is displayed, and user enters the number representing the selection) -- the ON GOTO/GOSUB ends up being more concise than using a simple IF ladder.
2:25 I don't suppose you can do a PRINT FN A(1) on the command line. Oh wow, you can. I was expecting an ?Illegal Direct. Is FN actually useful? I suppose it might be faster than a GOSUB.
3:09 What happens if you DEF FN LO(X)=X-FN HI(X)*256? Does BASIC handle the nesting and reusing the same parameter name?
4:17 If you cursor around the screen during an INPUT and then return to the line with the "?", the prompt will be included in the INPUT. You could have entered your number on a blank line. I was assuming that you wout edit out the ?Redo From Start, but you kept it.
5:54 I remember lots of BASIC game listings in magazines using a couple of ON statements.
8:33 I often use numbers like 10e6 instead of 10000000.0 in code since the former is much more readable (10-million) and the latter is just "1" with an unknown number of zeroes.
10:00 I think that's a design flaw, since it causes commas to be disallowed as input to a statement like INPUT A$.
17:00 To avoid this, I always started games with A=RND(-TI). Maybe A=RND(-TI*RND(0)) would be slightly better.
23:46 The main reason USR() is rarely used is that you can only set it to point to one machine-language routine at a time, where SYS takes the address as an argument.
28:00 I assume WAIT was only included as an Easter-Egg delivery vehicle.
30:30 Interesting: if I type "patreon.com\8bitshowandtell" into my browser with the faulty backslash, it gets automatically translated into the proper slash character. If only MS-DOS had done a slightly better job of copying from Unix!
would it be useful if the value you pass with USR() gets used as an offset to a jump table? that way you can have different machine code routines without having to remember all the SYS commands, ayou just have to remember what routine was 0, 1, 2, etc
(2) Yes, it does. If you type the program below, it will print 100 and 16, as expected.
10 DEF FN HI(X)=INT(X/256)
20 DEF FN LO(X)=X-FN HI(X)
30 PRINT FN HI(4196),FN LO(4196)
(3) and (6) That's the reason why I wrote my own custom input routine that allows you to set the length (up to 254 characters; normally INPUT is limited to two screen lines). My routine also does range checking (you can limit input to alphabetic, numeric, or alphanumeric characters by setting a flag). Although if you want the user to input multiple values at the same time, it's up to your program to parse the input.
(7) I always used A=RND(-TI/π).
(8) On the other hand, USR can also be used to _return_ a result from the machine language routine. With SYS, you _usually_ have to store the result in some memory location and use PEEK to retrieve it. (Unless you know how to use the built-in ROM routines to pass parameters to/from your routine.)
(9) Not necessarily. WAIT can be used for other purposes. For example, memory location 198 includes the number of keys in the keyboard buffer. Look at the two lines below, which both do the same thing. (They both wait for a key to be pressed, and return that keypress in A$.)
10 GET A$:IF A$="" THEN 10
20 POKE 198,0:WAIT 198,1:GET A$
- C-64 BASIC, RND(0) and IBM BASICA, RND(TI) are nearly identical. Both seed the random number function from the system's "time of day" clock. It may have been QBASIC that added RANDOMIZE(timer) which achieved the same as your second, better function.
- "WAIT" was a left-over from earlier "univac" teletype computing days. It was the fundamental INPUT where the program stops everything and waits for user input. The problem with it is ... it stops everything and waits for user input, or a change in state of a hardware register. It is about the equivalent of initiating a software interrupt. The only thing running on the C-64 (or IBM) during a WAIT command is hardware interrupts. It's the same with IBM BASIC. It is the only command in those languages where it was strongly suggested you figure out another way to accomplish the same goal. It didn't matter when the teletype stopped printing, but in modern software the program usually needs to handle other things like screen updating (and such) while "scanning" for user input.
8:33 I make the tokenizers I write accept underscores as digit separators to allow numbers to be more readable, e.g., 10_000_000.0. Grouping is also useful in other bases, e.g., $DEAD_BEEF, %1100_1101. It chafes me when credit-card entry forms don't allow spaces or dashes. What, I'm supposed to trust you with my credit-card information when you can't even parse separators?
@@proxy1035 It could be done, as long as there's not more than 256 offsets. (I can't imagine a jump table bigger than that anyway. Most of the programs I wrote had a dozen functions or less.) The code would look something as follows:
START JSR $DBA7 ; convert the value in the USR function into an integer in memory locations 97-98 ($61-$62)
LDY $62 ; Y register will contain the function number (0-255)
LDA MSB,Y ; most significant byte of the location to be jumped to
STA JUMP+1
LDA LSB,Y
STA JUMP+2
JUMP JMP $xxyy ; $xxyy will be replaced by the actual vector of the function to be performed
...
MSB .BYT xx xx xx xx xx xx xx xx
LSB .BYT yy yy yy yy yy yy yy yy
15:22 can you add a direct link to the wiki sub-page you mentioned on basic commands? I get the impression you had a specific sub page in mind - maybe one that notes the oddities and quirks of some commands?
Sure, I just found it on the page for CONT: www.c64-wiki.com/wiki/CONT
I haven't found a page that collects a bunch of the weird stuff together, that'd be fun to read.
Awesome! really well explained.👍
Just about all my programs would start with a simple random dice function:
DEF FNR(D)=INT(RND(1)*D)+1
You have an excellent presentation and speaking voice
RND(1) would be a good way to seed a procedurally generated game that appears the same for everyone, but could occupy very little space
Well... not quite. It's better to use a specific "seed" first, like A=RND(-50), before you start generating the game world. That way, it will generate the same sequence of numbers every time. RND(1) always generates the same sequence of numbers when you first turn the computer on, but what if someone ran another program before running your game? It might start in the middle of the sequence, or it might have been given a different seed (when I wrote a game, I always made sure that one of the first things it did was A=RND(-TI/π) to ensure that it would start with a unique seed every time I ran the program.) But other than that particular quid pro quo, you're right.
@@SpearM3064 Excellent reply. Thank you!
@@SpearM3064 In addition I would suggest to use the widely used phrase A=RND(-TI) to setup the sequence depending on the current timer. If not autostarted in an emulator the TI value gives enough entropy for a random seed.
Great Video, as always.
Could you make a video about the "ext in" pin of the SID chip?
I am really curious how to access the data that comed into the SID, and what possible applications are possible.
I just ordered one of those nice A/V breakout adapters with the Audio In. I plan on making a video about it once it arrives. But a disappointing spoiler: the audio input isn't available to the CPU in any form. As I currently understand it (I will research more while making the video) the audio just passes through the analog filters in the SID and then back to the analog output again. There is no analog to digital conversion, and no way of digitizing the audio, unfortunately.
@@8_Bit
Wow, thank you. That are great news.
I am really looking forward to that episode.
I think I never heard that anyone actually used this feature of the SID.
@@Zentauri77 I think one of text to voice cartridges used it to pass it's audio through. 8bitguy did a video with one I think.
@@pgodwin
Thanks for the info. I watched the video (It's called "How Speech Synthesizers Work", circa minute 10).
God, I love the sound of these old speech synthesizers.
Futuristic and yet retro :)