9 byte version. Hi. I just sent a detailed explanation yesterday about my 11 byte version (with full legal opcodes) to you Robin, but since yesterday I further optimized it into 9 bytes: .C:2f00 68 PLA .C:2f01 6e 04 dc ROR $dc04 .C:2f04 69 87 ADC #$87 .C:2f06 20 17 e7 JSR $e717 Some notes: The location of the code is important. That last JSR will push the "$2f, $08" sequence to the stack. I jumped into the KERNEL routine in a way that I skip the first PHA there. This way the last PLA before the RTS will remove the "$08" from the stack. The original sys routine around $e12f pushes the address $e1, $46 to the stack (return address to the sys command). With he first PLA I remove the 0x46 from the stack and using it also as a constant value to initialize the addition (adc #$87 will give you 205 or 206 this way). After jumping into the JSR the stack looks like this: $e1, $2f, $08. (Leftmost is the top of the stack). Inside the JSR $08 will be removed by pla before RTS so RTS will return back to the original SYS just before it calls our routine. ($e130).
8 BYTES - using color ram as random source ---------------------------------------- Slight modification to Peter´s version: Using the colorram address under the cursor ($F3/$F4) to randomly change carry, the ZP access saves one byte. The colorram has no upper nibble - the four upper bits are floating and give random values when read! Tried on VICE, would be nice to know if it works on real hardware :) adc ($f3),y ;2b pla ;1b adc #$87 ;2b jsr $e717 ;3b
Thank you for keeping Commodore alive. Being 45 and in a stressful grown up world full of doubt, it’s so nice to relax with a bottle of wine and come back to my 80s youth. That’s always been my happiest place. I’m so pleased people like you exist. 🙏🏼🙌🏼🙏🏼🙌🏼
I agree. These things take us back to a more golden age, I would say. Though, I try to stay fixed on the good things out there to keep my head in a positive place. Watching things like this is good for my soul.
I completely agree, the early 80's were an exciting time for the first home computers. In the UK computers like the Sinclair ZX81, ZX Spectrum, Commodore 64 and BBC Micro were the most popular. It's great to grab a drink and fire-up those old computers plugged into time appropriate CRT's (thanks to Ebay) and remember those times fondly. Happy times!
@@G1itcher thats correct. Computers of 80s had a standing and were built to keep running for long years..not like today's technology which is developed to self-destroy in at most 3 or 4 years to make people always consume more.
@@G1itcher I began the rest of my life from PET, Apple][e and C64. It always has been my playground. I am now playing with Arduino. Small cost and footprint. Tinkerland, robust. They even have cloud programming and simulators (drag-drop resistors, sensors, LEDs, et c.) similar to C64 User port. Keep having fun! Never grow up! Pleased to meet you, all.
Hey! Just wanted to say, that in my country C64 and Amigas were still quite popular in the early 2000's. As a child back then, the C64 was my first computer, even though some of my friends already had a modern PC. Unfortunately, my C64 from back then didn't last. I managed to buy well preserved C64C last friday. Your videos are very informative and really inspiring! It just makes me to have some productive time with my Commodore like I've never had before. :D Thanks!
22:46 oh that one is clever, I was trying to figure out what was left to do at this point and hadn't thought of this 24:30 always something to learn from things that don't work, glad you included it
Having gone through a couple books and having watched a couple other videos on ML, I could follow along and knew what most of the stuff was doing. (yay!) The extra knowledge doesn't diminish my amazement with what people can squeeze out of this machine with the relative handful of instructions and registers, if anything, I have even more respect. Some good stuff shown here and I can't wait to learn more!
I think I found a 11-byte-solution, which should work after a coldstart or reset: loop: jsr $e0be txa sbc $25 jsr $ffd2 bcc loop explanation: - calling subroutine $e0be of the BASIC RND() routine creates a pseudo-random value while the carry flag is also set randomly - the routine always returns with X-register = 139 (this value is set at address $e0f2 of the RND() routine) - txa transferes the value in X to the accumulator - after a reset the value at zeropage address $25 is always 189 (this is set at address $be0d when printing the " BYTES FREE" value using a subroutine call at $e43a) - sbc performs the following subtraction: 139-189-carryflag, which results in 205 or 206 The machine code has 11 bytes: 20 BE E0 8A E5 25 20 D2 FF 90 F5 Greetings Michael
This seems like a great solution! I think the SBC $25 could just be SBC #189 or even ADC #66, couldn't it? The real magic is discovering that $E0BE always returns a constant number in X, that's great!
@@8_BitYes, immediate addressing mode is the better option. I was too fixated on finding a zeropage solution and have not seen this much easier solution. :-)
Nice video, I really like 10 print maze, also did a video about it myself. People still develop different versions of this one-liner (orthogonal version) and make it smaller and smaller... and it is amazing how much amusement we get from this fascinating small piece of code.
Thanks Robin. I really like these in-depth "step-by-step" episodes. I do not pick up many details on my first viewing. But these are the kind I come back to several times because I learn something on each repeat!
This is a completely on-brand episode! 24:00 I had a moment of false hope for your divide-by-two approach, by using equivalent petscii values 109/110 instead of 205/206, but alas, it's still odd/even, not even/odd.
In the ml2 you could save 2 bytes by doing LDA #$80 STA #$d40f STA #$d412 Considering the character printing routine is slow. halving the frequency won't change much. In ML3 the zeropage is filled with random OS junk. So if you don't care about having exactly 128 1s to 128 0s (which true randomness won't do anyway) you can save a byte using lda 00,x or even (00,x)
Great video - I really like the way you go from the BASIC solution and iterate through the ML alternatives, including the "if it wasn't for bad luck" attempt too. I've tried but cannot optimise it any smaller. Cheers.
(not only) for Linux users, works best in xterm: while :; do printf "\e[44;36;1m\e#6" && for i in {1..40}; do printf "\\$((57+77*($RANDOM % 2)))" && sleep 0.01; done; echo; done Not sure if other terminal emulators support double-width characters or DEC sequences for them. Slightly off-topic 😸
I made a 10 byter that uses values in stack as randomness and the illegal opcode RLA to lower the range of numbers. Couldn´t get the classic maze pattern to work, but two other ones that look kind of good: byte $2f, $f3, $01 ;RLA {adr}:={adr}rol A:=A and {adr} absolute jsr $e716 sbc #$1A ;$1a for random waves, $16 for random grid bcc loop
Interesting. I had an 11 byte one, that used "-" and "|" 👍 Edit: I tried your code, but am I doing something wrong here? .C:0900 2F F3 01 RLA $01F3 .C:0903 20 16 E7 JSR $E716 .C:0906 E5 1A SBC $1A .C:0908 50 F6 BCC $0900 (tried with BVC as well). I just get a cursor.
@@PSL1969 I assembled to $080d, maybe it´s that. And I run on VICE emulator by injecting into memory. However, Peter Szabo ^^ made a 9 byte version that displays the original - that´s black magic to get this idea :)
@@PSL1969 Hi, I just got an 8-byter to work. Posted it in the pinned comment. The print routine writes a color-ram address to ZP, where you can read it out with a 2-bytes command. The read will give a random value, because the colorram is connected with only 4-bits instead of eight.
Huh, my optimizations mostly revolved around figuring out how the heck to get the thing to autorun and ... actually work and not crash. The most compact form of autorun I figured out stuck the code on the stack, so it was really touchy preventing other stuff from accidentally overwriting it eventually. (A BASIC stub with SYS to execute the machine code took up way too many bytes.)
Yes, making it either RUNnable or auto-running is pretty much its own topic which I just avoided here. I think I fretted enough over it in the video about 10 PRINT Orthogonal :)
Just for fun I cobbled up the 10 Print program on the Atari. You really only have to change the 205.5 to 6.5 and away it goes. The speedup tricks don't work on the Atari due to differences in the Basics, but it's pretty close. The forward and back slashes are ATASCII characters so they look nearly identical to the C64's PETSCII characters.
Love this episode. Imagine somebody (more likely thousands of somebodys) spending time to optimize windows 10 code or rewrite it in pure machine code. Whole system on several 3.5 inch floppies again.....
Re: 17:00 Random ROM: you could use a sliding window to not scan over the same 255 bytes again, i.e. inspect the first 255 bytes and then in a loop add the next byte, but remove the first byte that goes out of the window. that should be a lot faster.
You could speed that RAM scanner up by a lot with a sliding window. First, compute the cumulative score for $E000-$E0FF, then for each subsequent byte, you only have to add its score and subtract the score of the previous byte. 2 operations instead of 256. So for example, you start with the score from $E000-$E0FF, then you can easily find the score from $E001-$E100 by just subtracting score($E000) and adding score($E100).
I like the adc #0 instead of a jcc. Haha! of course you could get rid of the lda though. :) Every time I see 10print running, I want to write a maze solver for it. :) Perhaps the solver should try every entrance until it finds the longest (most interesting) path. Optimizing for speed doesn't seem hard to me. Write straight to the screen RAM with an indexed address. Don't even try to scroll, just limit the number of iterations. Ignore the cursor, just pick the starting address to avoid the next prompt. (I'm assuming that the C64 text mode maps RAM to screen without any gaps.) EDIT: I'm also assuming you can ignore color RAM, wherever that is. I'm an Atari guy, our mode 0 is monochrome. :-J The reason I bring it up is really for my own sake. I've often thought "I don't want to rewrite that" without realising the only reason "that" is complex is because it's featureful. Only recently have I learned that taking a different approach, changing the requirements a little, can make huge simplifications. I've been limiting myself unnecessarily. :)
Tell ya what, if Robin makes a video optimizing for speed, I'll submit a faster maze draw routine (which I'm sure clever people could even further optimize).
Write a version writing directly to screen memory at $0400. You will need to also write color ram too and handle scrolling to and last position wrote to the screen. Should be much faster than kernal rountines. Won't be smaller.
I'm really enjoying how well you explain things in your videos. Thanks for making those for us. One question I have is what is the deal with using characters 205 and 206? I find using characters such as 182 and 184 to look much more like a traditional maze. If you wanted to use your 11 Byte method you could use characters 182 and 183 which I personnaly still find looks more like a traditional maze. Just curious as to the appeal of using diagonal lines. Thanks
You can optimize the ROM searcher program as well. After summing the LSBs of $F000-$F0FF, you can sum $F001-$F100 by subtracting the LSB at $F000 and adding the LSB at $F100, etc.
A really fast implementation would scroll more than a full screen for each screen refresh, so you would get the same result if you store the character directly to screen memory, simply looping over it, and you would get an "optimised" version with just a few extra instructions.
Hello, from British Columbia! 6:30 Wow! I can't believe that I understood all/most of the explanation of the rotating and the carry and the kernel routine. 18:14 If I understand you, the raster register and CIA are changing in real time, whereas the $F002 technique is a static list of numbers, right?
This routine can be used to generate mazes for a game. Perhaps it can be a 16 screens in a 4x4 grid which can scroll in all directions (but a "wall" would have to be drawn around it). One might start at the bottom left and make your way to the top right. Sprites can be used to make tentacled Cthulu things going about randomly looking for you; perhaps there can be a few rocks here and there in the maze which your character can pick up and throw at them to stop them for a time so you can go past them. It's a silly idea, but why not : )
For games an LFSR (Linear-Feedback-Shift-Register) routine works better. It's random, but allows you to go back and forth different rooms or scrolling panes while maintaining the previously random generated data. ;)
I wonder if that 10 Print program is useful for finding more of these 'pleasing shapes' for making games with randomized levels. They still have some structure in them without a tailor-made randomizer for each level.
Just as a side note..the jiffy clock is useful for random numbers, but in order to do so, it needs the human element...if you check it after a pause for input of some sort. then usually the variation in delay is enough to be 'sufficiently' random for many purposes. Of course, in an application like this where there's no outside input at all, it's pretty much useless. (and if you're one of those who is, pun intended, inhumanly fast/consistant with your keypress speed, that also defeats it).
Thank you for remembering us decimal guys sometimes. 13:58 Pseudo-random: What if instead of starting the indexed reading from ROM, maybe a screen RAM address on the left edge of the screen? There will be a feedback effect.
Hey Robin, Really enjoying the exploration into simple assembly code programs! Question, I'm pretty sure that you covered the use of the Assembler (commands, differences between the REU version and the one you used today.) you are using fairly extensively in another video. Do you remember offhand which video?
Hi Jeff, nice to hear from you! I've got a playlist of the assembly videos here, and you're probably thinking of the first two videos in the list: ua-cam.com/play/PLvW2ZMbxgP9z9Un4LXivII_D1Hh5gZ7r9.html
Didn't realise that you needed to recount the low bits incase the ROM changes when incrementing the address. 😼 I think I spotted a repeating club shape formed by an "e" shaped head on two perpendicular dominoes. That might be avoidable by switching through address windows, or finding one larger than the screen size. Would it be practical to do an initial count, then { decrement if a low bit is leaving the address window, and increment when one enters the address window } while scanning for a larger window ?
1:30 Is Turbo Macro Pro free to redistribute? 5:12 (At this point in watching the video): Is this the final version? The main loop could be shortened to: - LDA $D41B : AND #1 : ADC #205 : JSR $FFD2 : BCC - after putting a CLC in the initialization code. 8:15 It'd be fun to "POKE" the characters on the screen (do the screen codes have a similar pattern?) with STA (ptr),Y and then go back to the start of the screen RAM instead of scrolling. That would show how fast Machine Language is compared to BASIC. In BASIC, you could PRINT "{HOME}" : FOR I=1 TO 999 : print char : NEXT. 9:55 Okay, this looks familiar! 11:31 You could also read the CHROUT documentation: .CS=error, .CC=success. 12:56 (At this point in watching the video): You could also use the low byte of the CIA IRQ countdown timer. This is effectively what +RND(.) is doing (except that's using the high bit). 21:25 Why are you using ROR instead of LDA : AND #1? ROR will write the rotated result back out to the timer register, which will presumably change the range of the countdown. 21:59 When you were doing that Transactor memory-scan program, you found some areas apparently in the I/O space that seemed to show continuously changing random values. Would one of those locations be usable?
I don't know if Turbo Macro Pro is truly free to distribute. TMP, and its predecessor Turbo Assembler, were both commercial products of a German company called Omikron in the 1980s. Both programs have been modified and distributed by probably dozens of different scene groups over the last 30-whatever years without any challenge that I'm aware of; this version by Style has been "re-sourced" and re-assembled with many bug fixes and new features. For example, the REU support wasn't in the original at all.
re: CHROUT .CS=error, .CC=success, I actually went looking for that in the C64 Programmer's Reference Guide while preparing for this episode, as I thought there was a success/error indicator. However, on page 278, the description of CHROUT, says "Error returns: 0 (See READST)" and doesn't mention carry at all. Any ideas on where this carry functionality is documented?
re: using ROR instead of LDA : AND #1, it was part of that (unsuccessful) multiply-by-two approach I show at the end and since it didn't seem to negatively affect the apparent randomness in this application, it stuck around in this version. But yes, it very likely is messing with the countdown. It just didn't seem to make the bits that end up in carry any less "random-ish" which is all I was concerned about.
@@8_Bit you might be in luck, as computer programs (or 1:1 'programs for data processing'), were added after 1985, which means they did not have any copyright before (but the source was protected!). But dont quote me, im not a lawyer!
@@8_Bit: C64 PRG page 271: "ERROR RETURNS: A return from a KERNAL routine with the CARRY set indicates that an error was encountered in processing. The accumulator will contain the number of the error."
8 BYTES ---------------------------------------- Using the colorram address under the cursor ($F3/$F4) to randomly change carry, the ZP access saves one byte. The colorram has no upper nibble - the four upper bits are floating and give random values when read! Modified version of Peter Szabo. adc ($f3),y ;2b pla ;1b adc #$87 ;2b jsr $e717 ;3b
Sadly it only works with a poke 780,128 before the sys. The original routine resets the A register from this memory address at each iteration. (0 by default I guess)
@@peterszabo3584 Thanx for the info. I only tried it on VICE. It works out of the box there. When I get some more time, I´ll try to tinker it on a real C64.
what it you use a lookup table for the characters and then use the lower bit of the sound oscillator as the index offset? would that be quicker/smaller?
A bit off topic, but anyway I wanted to ask. What's the smallest possible code, in basic, to program a TRUE random generator on the C64? I remember back in the days when I tried to create a D&D character generator program, using the standard rnd command, the program would always give back the same values from run to run, if you get my point. The only documentation I had available at the time (the standard C64 manual) didn't give any clues to this as far as I can remember. I can see some things that would more or less allow for a 'true' rnd generator based on timings, as explained by you in this video. How would you write a simple basic program that would allow for TRUE rnd generation without any specific inputs and such?
Short answer: You don't get true random numbers out of a computer without specialized hardware. Your choices are: * Do some math involving a seed value. Start with the same seed, get the same random values. Try to randomize the seed. * Do something that involves hardware. A box to detect radiation counts and feeds that back to the computer, for instance.
I wonder if reading unmapped memory is random enough? Like if there's nothing in the expansion port, and you read a IO2 address? I suspect the data bus has enough capacitance to simply hold the last value. Not so random after all I guess. Or what about the upper nybble of color RAM?
On the Z80, the R register was the 16-bit address of the Dynamic RAM address being refreshed. When we needed a random number on the Z80, we just loaded A with the R register and AND with the mask for the bits we wanted from that. But I think that's clever, you pre-surveyed that section of ROM to get your result. Does not the 6502 have a RAM refresh register?
I enjoy the smart alec self deprecation :) What if you pointed it at video memory for randomness? You made it a really interesting video with the boundary pushing approach.
In regards to the 204/205 bad luck? how about putting character 206 into 204? then you could use that alternate technique - but how expensive would it be to replace the char? alternatively replace character 0 and 1 then do away with any addition? can you 'offset' the pointer to the start of character ROM? perhaps shift it up so 205 is 0?
I'm not sure about this, but my intuition says that the pointer to the charset would need to point to the start of a page (xx00), in which case offsetting it by one wouldn't work. Also, since the goal was to optimize for size the preparation needed to move the charset pointer adds more bytes than it saves. But, if optimizing for speed, redefining a character is fine as an initial setup cost if it saves cycles by tightening the loop.
@@JeremyNasmith - I was reading and you can change the pointer to where the character glyphs start but I am unaware if it's restricted to the start of a page - it's a VICII register that's set so I don't know if that hints at any indication. Well if the pointer isn't fixed to the start of a page? you could get rid of combining the random value with the 205/206 and print 0 or 1 directly - but you sound like you know it'd still be more expensive :P
Robin, do you know why the 'Restore' button has to be tapped quickly for the 'Run/Stop Restore' break function to work? In other words, if you just press Run/Stop and the Restore button normally the break won't happen. Is this a function of how the Restore button is read or is that button actually somehow physically different from the others so that it ONLY works if it's tapped quickly? Always wondered about that.
The RESTORE key is certainly very different from the rest of the keys on the keyboard. It's not part of the standard keyboard matrix that's scanned via the PIO lines of CIA1 but is instead connected to the NMI line. (It goes through a 555 circuit that debounces it.) So unlike any other key, where the computer knows about it when it decides to scan the keyboard, pressing RESTORE immediately halts whatever the computer is doing and jumps to the NMI routine. (That routine scans the keyboard to see if RUN/STOP is being held down as this routine executes.) I'm not clear on why you're having issues with what sounds like different lengths of key press, though. I wonder if it could be a problem related to the debouncing of that switch? It would be interesting to put an oscilloscope on the relevant signals on a C64 known to be working properly and on yours and see if there's any difference.
In addition to what Curt said, I've heard that many (mostly breadbin?) C64s have the incorrect value of capacitor (or resistor?) on the Restore key line and so they need a sharper thwack to get the signal through?
@@8_Bit Yep, the C64 I had back in the day was indeed the old breadbin style and I remember on several occasions trying to just hold the Run/Stop and Restore keys like a normal combination (such as Shift-Return or whatever) and it did not have the desired effect. Had to thwack it hard and quick to do the deed. I had no idea that other C64 versions didn't need that sort of thwacking. I just figured it was designed that way to prevent accidental resets, etc. Interesting!
@@JustWasted3HoursHere Yeah, it seems like it's by design to me too, though some people say it was the incorrect value. I'm not sure what proof they have of this, maybe just that it doesn't match the schematics included in the C64 Programmer's Reference Guide? Well, those schematics have several errors, so I don't see it as fully authoritative. And even if the schematic says something else, it's possible they deliberately changed the value during production.
I was recently brushing up on ML and using random numbers, when this video came to mind! I'd like to generate a two-byte random number, but as you noted, using the SID is not suitable without introducing a delay. There is an example in Compute!'s "Programing the Commodore 64" of a routine at $E0BE that produces 4 bytes, but this appears to be slower than calling RND() in BASIC! Maybe I'm doing something wrong, but do you have a suggestion on how to generate two random bytes without introducing a delay?
If you set the SID frequency to $FFFF then you only have to wait 17 cycles to guarantee it changes, so I'd suggest just grabbing a byte, do some initialization or whatever (you can probably think of something useful your code could be doing) and then grab the second byte. Even if you just waste the 17 cycles, it's probably not all that bad. If that's not acceptable, then I'd suggest using a large-ish software LFSR, maybe 16 bits or more. But that would probably take 17 cycles or more anyway to generate the next number in the sequence.
@@8_Bit These are great suggestions -- thank you! I was goofing around and cheated: I moved the LDA $D41B instruction to a subroutine with an NOP before the RTS -- the total time to call it with JSR is (i think) 16 cycles, which is good enough for my academic exercise.
Cute. But how small can you golf a program which outputs a maze where there is guaranteed exactly one route between any two locations? No walls that completely wall off one area from another. No loops.
Also, one thing you could do would be to make an inner loop in which you ROR 8 times, thus using all the bits in the byte (wherever you get it from). You could also try this in a machine with only - say - 16k of RAM and pull a byte from an address where there’s no RAM, ROM, or hardware and see if it reads “noise” values. So, maybe a VIC-20. Also, what happens if you try to read the joystick and none is connected? Or read the cassette input? Are bits other than bit 0 any more/less “random” than bit 0? Will this work on a C128?
For small (256 bytes) machine code programs which includes some "randomness" this still amazes me - ua-cam.com/video/sWblpsLZ-O8/v-deo.html . The code and how it works could make for an interesting video.
Another kind of maze can be made with 10 A=RND(1)*4:PRINTCHR$(A+171-(A>1)*5);:GOTO10 Not quite as elegant a one-liner, as the four characters involved are not all adjacent in the PETSCII table.
That's a lot slower than I was expecting a machine-language program to be. And you say most of that is CHROUT being the bottleneck? That's... actually not surprising now that I think about what all has to be done between reading an ASCII code and actually getting it onto the screen. In particular, the process of checking for every possible control character and converting what's left from ASCII to a character code probably requires a laundry list of compares (I wonder if 205/206 was picked over 109/110 initially because it somehow bypasses some of them), all of which could be done away with when you know you're only going to by outputting one of two characters. Even the value of register 646 could possibly be stored permanently in a CPU register since you only need to retrieve it at the beginning.
As well as the character to screen code conversion process and scrolling, there's also the overhead of figuring out which channels have been opened for output and sending the character to each one. There's really a fair amount of overhead in that CHROUT routine as compared to simpler microcomputer ROMs where the commonly used routine simply prints to the screen and that's it.
@@Curt_Sampson You'd think the part that outputs to the screen specifically would be a subroutine that coders could just JSR to directly and cut out the middleman. I remember _Mapping the C64_ mentioning that some Kernal routines work like that. EDIT: OK, yeah, it turns out there is, at $E716. Glad I went to the trouble of converting that whole book to searchable text now.
@@stevethepocket Yup, and nice work on digging that up. The only thing to check there is whether the code that delays after a newline when you're holding down CTRL is part of the generic routine or the $E716 routine.
@@Curt_Sampson Already tried it and yes it is. But I think csbruce was right about the scrolling being the real bottleneck because I didn't notice any difference (and I can't imagine scrolling the entire screen would be something you could easily speed up, nor would you really want to). I'd have to record the footage and go over it frame by frame to see if the initial screen fill is any faster; they both go by too fast to really tell.
Hi. What happens if you eliminate the “LDA #$80” line in the initialization section? Since you still have 0xFF in the accumulator, bit 7 is still set, which is what you want in 0xD412. Would the extra ‘1’ bits (bits 0-6) cause a problem here?
does anyone know how to get back to bank 0 in TMP w/ REU after you've switched to another bank? I've looked through the docs, and there doesn't seem to be a way to do it. There's a bank switching option, but it only allows you to switch to banks 2-7 for whatever reason.
I just tried it and you're right. That seems pretty bizarre; not sure if that's a bug or oversight or something by design that I don't understand the reason for. A potential work-around is to exit to basic with back arrow 1, type new, load"TMP+REU*",8,1 then sys32768 to re-enter. You should be back in source bank 0 and your code should still be there. Worked for me just now, anyway.
@@8_Bit Thanks for the response! So that didn't quite work. I got an "out of memory" error. (I'm guessing I must've overwritten something I shouldn't have in my code). Luckily I took your advice in one of your other videos and I have a reset button at the ready, and after resetting, I was able to run TMP again and get my code back on bank 0. Wheww. Thanks for your help!
This episode's song has a video here: ua-cam.com/video/lHOxmXCSqAs/v-deo.html Some of the other songs are on that channel too. Thanks for the interest, I added the link to the video description now too, which I often forget to do.
BBC BASIC Version 10REPEAT:PRINTCHR$(47+INT(0.5+RND(1))*45);:UNTILFALSE The entire program comes in at 32 bytes because REPEAT, PRINT, CHR$(, INT(, RND(, UNTIL and FALSE come out as byte each as they are tokenised. BBC Assembler Version 10 FOR N%=0 TO 3 STEP 3 20 P%=&2000 30 [ 40 OPT N% 50 .loop 60 jsr &AF51 ; &AF80 for BASIC 1 65 lda &2A 70 ror A 80 lda #47 90 bcs print 100 adc #45 110 .print 120 jsr &FFEE 130 jmp loop 140 ] 150 NEXT CALL &2000 Routine AF51 is the BASIC RND variable function. RND without braces or an argument returns a random signed 32 bit integer in zero page 2A, 2B, 2C and 2D (The Integer Work Area). We just take the lowest byte. Routine FFEE is OSWRCH i.e. OS write character. Its the operating system equivalent of PRINT CHR$(A).
The BBC Micro, apart from Mode 7 (Teletext mode), does not have character mapped screen memory so you have to use the OS to write characters. Mode 7 has different characters in the character set to \ and / and modes 3 and 6 have 25 line screens and not 32 line screens and space them out with scan lines which are not backed by memory. So best to use one of the other screen modes.
My optimised version. Rather than using the \ and / characters which, in ASCII, are quite far apart, I've defined a couple of user characters 128, and 129. 10 VDU23,128,&80,&40,&20,&10,8,4,2,1 20 VDU23,129,1,2,4,8,&10,&20,&40,&80 30 FOR N%=0 TO 3 STEP 3 40 P%=&2000 50 [ 60 OPT N% 70 .loop 80 jsr &AF51 ; &AF80 for BASIC 1 90 ror &2A 100 lda #128 110 adc #0 120 jsr &FFEE 130 jmp loop 140 ] 150 NEXT Since the $ as a prefix is already used in BBC BASIC (As a way of storing strings in memory, e.g. $1234="Hello world"), then & is used at the 'HEX' indicator. Also the OSWRCH routine, unlike the C64 equivalent, leaves carry in an unknown state, hence jmp.
Okay I got it smaller! 12 bytes... 10 VDU23,128,&80,&40,&20,&10,8,4,2,1 20 VDU23,129,1,2,4,8,&10,&20,&40,&80 30 FOR N%=0 TO 3 STEP 3 40 P%=&2000 50 [ 60 OPT N% 70 .loop 80 jsr &AF51 90 lda #&81 100 and &2A 120 jsr &FFEE 130 jmp loop 140 ] 150 NEXT It basically ANDs the random value with &81 which results in either &00, &01, &80 or &81. This is a cheat. 0 is the null character which does nothing. 1 means send next character to printer (so it doesn't appear on the screen). The other two are characters 128 and 129 i.e. our maze characters.
9 byte version.
Hi. I just sent a detailed explanation yesterday about my 11 byte version (with full legal opcodes) to you Robin,
but since yesterday I further optimized it into 9 bytes:
.C:2f00 68 PLA
.C:2f01 6e 04 dc ROR $dc04
.C:2f04 69 87 ADC #$87
.C:2f06 20 17 e7 JSR $e717
Some notes:
The location of the code is important. That last JSR will push the "$2f, $08" sequence to the stack. I jumped into the KERNEL routine in a way that I skip the first PHA there.
This way the last PLA before the RTS will remove the "$08" from the stack.
The original sys routine around $e12f pushes the address $e1, $46 to the stack (return address to the sys command).
With he first PLA I remove the 0x46 from the stack and using it also as a constant value to initialize the addition (adc #$87 will give you 205 or 206 this way).
After jumping into the JSR the stack looks like this: $e1, $2f, $08. (Leftmost is the top of the stack). Inside the JSR $08 will be removed by pla before RTS so RTS will return
back to the original SYS just before it calls our routine. ($e130).
Fantastic work!!
@@8_Bit Thanks. I really love your videos, inspiring and thorough content! Keep it up!
That´s genious - that´s really really good!
Wow, that's amazing trickery. I think we have a winner 🥇😎👍
8 BYTES - using color ram as random source
----------------------------------------
Slight modification to Peter´s version:
Using the colorram address under the cursor ($F3/$F4) to randomly change carry, the ZP access saves one byte.
The colorram has no upper nibble - the four upper bits are floating and give random values when read!
Tried on VICE, would be nice to know if it works on real hardware :)
adc ($f3),y ;2b
pla ;1b
adc #$87 ;2b
jsr $e717 ;3b
Thank you for keeping Commodore alive. Being 45 and in a stressful grown up world full of doubt, it’s so nice to relax with a bottle of wine and come back to my 80s youth. That’s always been my happiest place. I’m so pleased people like you exist. 🙏🏼🙌🏼🙏🏼🙌🏼
I agree. These things take us back to a more golden age, I would say. Though, I try to stay fixed on the good things out there to keep my head in a positive place. Watching things like this is good for my soul.
I completely agree, the early 80's were an exciting time for the first home computers. In the UK computers like the Sinclair ZX81, ZX Spectrum, Commodore 64 and BBC Micro were the most popular. It's great to grab a drink and fire-up those old computers plugged into time appropriate CRT's (thanks to Ebay) and remember those times fondly. Happy times!
I'm 34 and I think computers were better in the 80s!
@@G1itcher thats correct. Computers of 80s had a standing and were built to keep running for long years..not like today's technology which is developed to self-destroy in at most 3 or 4 years to make people always consume more.
@@G1itcher I began the rest of my life from PET, Apple][e and C64. It always has been my playground. I am now playing with Arduino. Small cost and footprint. Tinkerland, robust. They even have cloud programming and simulators (drag-drop resistors, sensors, LEDs, et c.) similar to C64 User port. Keep having fun! Never grow up! Pleased to meet you, all.
Hey! Just wanted to say, that in my country C64 and Amigas were still quite popular in the early 2000's. As a child back then, the C64 was my first computer, even though some of my friends already had a modern PC. Unfortunately, my C64 from back then didn't last. I managed to buy well preserved C64C last friday. Your videos are very informative and really inspiring! It just makes me to have some productive time with my Commodore like I've never had before. :D Thanks!
22:46 oh that one is clever, I was trying to figure out what was left to do at this point and hadn't thought of this
24:30 always something to learn from things that don't work, glad you included it
Having gone through a couple books and having watched a couple other videos on ML, I could follow along and knew what most of the stuff was doing. (yay!) The extra knowledge doesn't diminish my amazement with what people can squeeze out of this machine with the relative handful of instructions and registers, if anything, I have even more respect. Some good stuff shown here and I can't wait to learn more!
I think I found a 11-byte-solution, which should work after a coldstart or reset:
loop: jsr $e0be
txa
sbc $25
jsr $ffd2
bcc loop
explanation:
- calling subroutine $e0be of the BASIC RND() routine creates a pseudo-random value while the carry flag is also set randomly
- the routine always returns with X-register = 139 (this value is set at address $e0f2 of the RND() routine)
- txa transferes the value in X to the accumulator
- after a reset the value at zeropage address $25 is always 189 (this is set at address $be0d when printing the " BYTES FREE" value using a subroutine call at $e43a)
- sbc performs the following subtraction: 139-189-carryflag, which results in 205 or 206
The machine code has 11 bytes: 20 BE E0 8A E5 25 20 D2 FF 90 F5
Greetings
Michael
Interesting!
Nice, I will give this a try!
This seems like a great solution! I think the SBC $25 could just be SBC #189 or even ADC #66, couldn't it? The real magic is discovering that $E0BE always returns a constant number in X, that's great!
@@8_BitYes, immediate addressing mode is the better option. I was too fixated on finding a zeropage solution and have not seen this much easier solution. :-)
WOW, respect! That´s damn smart!
Nice video, I really like 10 print maze, also did a video about it myself. People still develop different versions of this one-liner (orthogonal version) and make it smaller and smaller... and it is amazing how much amusement we get from this fascinating small piece of code.
Thanks Robin. I really like these in-depth "step-by-step" episodes. I do not pick up many details on my first viewing. But these are the kind I come back to several times because I learn something on each repeat!
This is a completely on-brand episode!
24:00 I had a moment of false hope for your divide-by-two approach, by using equivalent petscii values 109/110 instead of 205/206, but alas, it's still odd/even, not even/odd.
Loved the optimizations and your step by step walk through. It's like we're 16 again :)
In the ml2 you could save 2 bytes by doing
LDA #$80
STA #$d40f
STA #$d412
Considering the character printing routine is slow. halving the frequency won't change much.
In ML3 the zeropage is filled with random OS junk. So if you don't care about having exactly 128 1s to 128 0s (which true randomness won't do anyway) you can save a byte using lda 00,x or even (00,x)
That outro tune is very soothing ;)
awesome. And love the maze behind the patron list in the outro. Classic Robin subtle brilliance. :)
"behind" is funny.
Great video - I really like the way you go from the BASIC solution and iterate through the ML alternatives, including the "if it wasn't for bad luck" attempt too. I've tried but cannot optimise it any smaller. Cheers.
(not only) for Linux users, works best in xterm:
while :; do printf "\e[44;36;1m\e#6" && for i in {1..40}; do printf "\\$((57+77*($RANDOM % 2)))" && sleep 0.01; done; echo; done
Not sure if other terminal emulators support double-width characters or DEC sequences for them. Slightly off-topic 😸
now x86 asm 😂
I made a 10 byter that uses values in stack as randomness and the illegal opcode RLA to lower the range of numbers.
Couldn´t get the classic maze pattern to work, but two other ones that look kind of good:
byte $2f, $f3, $01 ;RLA {adr}:={adr}rol A:=A and {adr} absolute
jsr $e716
sbc #$1A ;$1a for random waves, $16 for random grid
bcc loop
Interesting. I had an 11 byte one, that used "-" and "|" 👍
Edit: I tried your code, but am I doing something wrong here?
.C:0900 2F F3 01 RLA $01F3
.C:0903 20 16 E7 JSR $E716
.C:0906 E5 1A SBC $1A
.C:0908 50 F6 BCC $0900 (tried with BVC as well).
I just get a cursor.
@@PSL1969 I assembled to $080d, maybe it´s that. And I run on VICE emulator by injecting into memory.
However, Peter Szabo ^^ made a 9 byte version that displays the original - that´s black magic to get this idea :)
@@paulkocyla1343 I'll try that 👍 9 byte that's crazy. I'll have to check that out as well.
@@PSL1969 Hi, I just got an 8-byter to work. Posted it in the pinned comment.
The print routine writes a color-ram address to ZP, where you can read it out with a 2-bytes command.
The read will give a random value, because the colorram is connected with only 4-bits instead of eight.
@@paulkocyla1343 Nice, I will check it out. This is getting crazy! :)
Huh, my optimizations mostly revolved around figuring out how the heck to get the thing to autorun and ... actually work and not crash. The most compact form of autorun I figured out stuck the code on the stack, so it was really touchy preventing other stuff from accidentally overwriting it eventually.
(A BASIC stub with SYS to execute the machine code took up way too many bytes.)
Yes, making it either RUNnable or auto-running is pretty much its own topic which I just avoided here. I think I fretted enough over it in the video about 10 PRINT Orthogonal :)
Great video as always! I also like your "learning with music" stuff. Something me and my family did when i was younger! I like it a lot!
There is a book about this program. It is called 10 PRINT ... (in fact the title is the entire program).
Love to see this play with random in machine code. I have been doing this a few years ago with ‘random runner’. Thanx for sharing Robin. 👍
Just for fun I cobbled up the 10 Print program on the Atari. You really only have to change the 205.5 to 6.5 and away it goes. The speedup tricks don't work on the Atari due to differences in the Basics, but it's pretty close. The forward and back slashes are ATASCII characters so they look nearly identical to the C64's PETSCII characters.
Love this episode. Imagine somebody (more likely thousands of somebodys) spending time to optimize windows 10 code or rewrite it in pure machine code. Whole system on several 3.5 inch floppies again.....
Re: 17:00 Random ROM: you could use a sliding window to not scan over the same 255 bytes again, i.e. inspect the first 255 bytes and then in a loop add the next byte, but remove the first byte that goes out of the window. that should be a lot faster.
Hey Robin, great episode! 😎 Cool to see all the attempts, and how you explained it all was great! 👍👍👍
Thanks Peter for your great ideas!
@@8_Bit I only had them because of your own great ideas :) And a great challenge to everyone, to improve upon it 👍👍👍
You could speed that RAM scanner up by a lot with a sliding window. First, compute the cumulative score for $E000-$E0FF, then for each subsequent byte, you only have to add its score and subtract the score of the previous byte. 2 operations instead of 256.
So for example, you start with the score from $E000-$E0FF, then you can easily find the score from $E001-$E100 by just subtracting score($E000) and adding score($E100).
I like the adc #0 instead of a jcc. Haha! of course you could get rid of the lda though. :)
Every time I see 10print running, I want to write a maze solver for it. :) Perhaps the solver should try every entrance until it finds the longest (most interesting) path.
Optimizing for speed doesn't seem hard to me. Write straight to the screen RAM with an indexed address. Don't even try to scroll, just limit the number of iterations. Ignore the cursor, just pick the starting address to avoid the next prompt. (I'm assuming that the C64 text mode maps RAM to screen without any gaps.) EDIT: I'm also assuming you can ignore color RAM, wherever that is. I'm an Atari guy, our mode 0 is monochrome. :-J
The reason I bring it up is really for my own sake. I've often thought "I don't want to rewrite that" without realising the only reason "that" is complex is because it's featureful. Only recently have I learned that taking a different approach, changing the requirements a little, can make huge simplifications. I've been limiting myself unnecessarily. :)
Again a great video with lots of information. Again learned a lot from your video..
Would be cool to see a episode of this where you optimize for speed
very unlikely: the routine at $ffD2 has a lot of stuff to do!
@@MrRobbyvent Pretty sure we could make a special-purpose print routine that's faster than $FFD2
@@DavidYoud I'm pretty sure you could'nt
Tell ya what, if Robin makes a video optimizing for speed, I'll submit a faster maze draw routine (which I'm sure clever people could even further optimize).
UA-cam ate my post with a link, but check out disassembly of $E716, lots of unnecessary processing to be removed
Well done, you could poke the char in the video ram and make the last row hidden and do a softscroll pixel row by row :-)
That would be super fast.
Write a version writing directly to screen memory at $0400. You will need to also write color ram too and handle scrolling to and last position wrote to the screen. Should be much faster than kernal rountines. Won't be smaller.
I'm really enjoying how well you explain things in your videos. Thanks for making those for us. One question I have is what is the deal with using characters 205 and 206? I find using characters such as 182 and 184 to look much more like a traditional maze. If you wanted to use your 11 Byte method you could use characters 182 and 183 which I personnaly still find looks more like a traditional maze. Just curious as to the appeal of using diagonal lines. Thanks
I tried your suggestion and you are so right, it really *does* look more like a real maze using those two chars.
"Since last year!" Instant like.
Gotta use that dad joke every year.
You can optimize the ROM searcher program as well. After summing the LSBs of $F000-$F0FF, you can sum $F001-$F100 by subtracting the LSB at $F000 and adding the LSB at $F100, etc.
A really fast implementation would scroll more than a full screen for each screen refresh, so you would get the same result if you store the character directly to screen memory, simply looping over it, and you would get an "optimised" version with just a few extra instructions.
Hello, from British Columbia!
6:30 Wow! I can't believe that I understood all/most of the explanation of the rotating and the carry and the kernel routine.
18:14 If I understand you, the raster register and CIA are changing in real time, whereas the $F002 technique is a static list of numbers, right?
love this that you do the assembly editing and run on real C64, and great that we can follow along using the disk file :) hope for more like this
This routine can be used to generate mazes for a game. Perhaps it can be a 16 screens in a 4x4 grid which can scroll in all directions (but a "wall" would have to be drawn around it). One might start at the bottom left and make your way to the top right. Sprites can be used to make tentacled Cthulu things going about randomly looking for you; perhaps there can be a few rocks here and there in the maze which your character can pick up and throw at them to stop them for a time so you can go past them. It's a silly idea, but why not : )
For games an LFSR (Linear-Feedback-Shift-Register) routine works better. It's random, but allows you to go back and forth different rooms or scrolling panes while maintaining the previously random generated data. ;)
I wonder if that 10 Print program is useful for finding more of these 'pleasing shapes' for making games with randomized levels. They still have some structure in them without a tailor-made randomizer for each level.
20:57 I knew it!! I knew the CIA was involved!,!
Just as a side note..the jiffy clock is useful for random numbers, but in order to do so, it needs the human element...if you check it after a pause for input of some sort. then usually the variation in delay is enough to be 'sufficiently' random for many purposes. Of course, in an application like this where there's no outside input at all, it's pretty much useless. (and if you're one of those who is, pun intended, inhumanly fast/consistant with your keypress speed, that also defeats it).
That´s a great brain-squeezer. Your solution looks pretty optimal, I don´t think it can be done any smaller.
But maybe there´s a voodoo trick 😀.
Loved the implementation that used the SID as a source of randomness.
Love the end song!
Always proving there is more than one way to do something. Excellent vid. Cheers
Love it! How about getting it to run as fast as you can by rewriting the system library?
Thank you for remembering us decimal guys sometimes. 13:58 Pseudo-random: What if instead of starting the indexed reading from ROM, maybe a screen RAM address on the left edge of the screen? There will be a feedback effect.
Unmatched wisdom, as always. Top notch!
Hey Robin, Really enjoying the exploration into simple assembly code programs! Question, I'm pretty sure that you covered the use of the Assembler (commands, differences between the REU version and the one you used today.) you are using fairly extensively in another video. Do you remember offhand which video?
Hi Jeff, nice to hear from you! I've got a playlist of the assembly videos here, and you're probably thinking of the first two videos in the list: ua-cam.com/play/PLvW2ZMbxgP9z9Un4LXivII_D1Hh5gZ7r9.html
OBC! :)
Didn't realise that you needed to recount the low bits incase the ROM changes when incrementing the address. 😼
I think I spotted a repeating club shape formed by an "e" shaped head on two perpendicular dominoes. That might be avoidable by switching through address windows, or finding one larger than the screen size.
Would it be practical to do an initial count, then { decrement if a low bit is leaving the address window, and increment when one enters the address window } while scanning for a larger window ?
11:34 Int Disable and Print Char during Int code... very scary.
1:30 Is Turbo Macro Pro free to redistribute?
5:12 (At this point in watching the video): Is this the final version? The main loop could be shortened to: - LDA $D41B : AND #1 : ADC #205 : JSR $FFD2 : BCC - after putting a CLC in the initialization code.
8:15 It'd be fun to "POKE" the characters on the screen (do the screen codes have a similar pattern?) with STA (ptr),Y and then go back to the start of the screen RAM instead of scrolling. That would show how fast Machine Language is compared to BASIC.
In BASIC, you could PRINT "{HOME}" : FOR I=1 TO 999 : print char : NEXT.
9:55 Okay, this looks familiar!
11:31 You could also read the CHROUT documentation: .CS=error, .CC=success.
12:56 (At this point in watching the video): You could also use the low byte of the CIA IRQ countdown timer. This is effectively what +RND(.) is doing (except that's using the high bit).
21:25 Why are you using ROR instead of LDA : AND #1? ROR will write the rotated result back out to the timer register, which will presumably change the range of the countdown.
21:59 When you were doing that Transactor memory-scan program, you found some areas apparently in the I/O space that seemed to show continuously changing random values. Would one of those locations be usable?
I don't know if Turbo Macro Pro is truly free to distribute. TMP, and its predecessor Turbo Assembler, were both commercial products of a German company called Omikron in the 1980s. Both programs have been modified and distributed by probably dozens of different scene groups over the last 30-whatever years without any challenge that I'm aware of; this version by Style has been "re-sourced" and re-assembled with many bug fixes and new features. For example, the REU support wasn't in the original at all.
re: CHROUT .CS=error, .CC=success, I actually went looking for that in the C64 Programmer's Reference Guide while preparing for this episode, as I thought there was a success/error indicator. However, on page 278, the description of CHROUT, says "Error returns: 0 (See READST)" and doesn't mention carry at all. Any ideas on where this carry functionality is documented?
re: using ROR instead of LDA : AND #1, it was part of that (unsuccessful) multiply-by-two approach I show at the end and since it didn't seem to negatively affect the apparent randomness in this application, it stuck around in this version. But yes, it very likely is messing with the countdown. It just didn't seem to make the bits that end up in carry any less "random-ish" which is all I was concerned about.
@@8_Bit you might be in luck, as computer programs (or 1:1 'programs for data processing'), were added after 1985, which means they did not have any copyright before (but the source was protected!). But dont quote me, im not a lawyer!
@@8_Bit: C64 PRG page 271: "ERROR RETURNS: A return from a KERNAL routine with the CARRY set indicates that an error was encountered in processing. The accumulator will contain the number of the error."
I wish this type of skills and optimizations applied to modern operating systems...
8 BYTES
----------------------------------------
Using the colorram address under the cursor ($F3/$F4) to randomly change carry, the ZP access saves one byte.
The colorram has no upper nibble - the four upper bits are floating and give random values when read!
Modified version of Peter Szabo.
adc ($f3),y ;2b
pla ;1b
adc #$87 ;2b
jsr $e717 ;3b
Sadly it only works with a poke 780,128 before the sys. The original routine resets the A register from this memory address at each iteration. (0 by default I guess)
@@peterszabo3584 Thanx for the info. I only tried it on VICE. It works out of the box there.
When I get some more time, I´ll try to tinker it on a real C64.
@@paulkocyla1343 I also tried on VICE and it did not work after reset. But in the pinned post I added a modification that will solve the issue.
Cool, thank you
Nice video, like it, thanks for sharing :)
what it you use a lookup table for the characters and then use the lower bit of the sound oscillator as the index offset? would that be quicker/smaller?
Impressive tutorial, thank you.
A bit off topic, but anyway I wanted to ask. What's the smallest possible code, in basic, to program a TRUE random generator on the C64?
I remember back in the days when I tried to create a D&D character generator program, using the standard rnd command, the program would always give back the same values from run to run, if you get my point. The only documentation I had available at the time (the standard C64 manual) didn't give any clues to this as far as I can remember. I can see some things that would more or less allow for a 'true' rnd generator based on timings, as explained by you in this video.
How would you write a simple basic program that would allow for TRUE rnd generation without any specific inputs and such?
Short answer:
You don't get true random numbers out of a computer without specialized hardware.
Your choices are:
* Do some math involving a seed value. Start with the same seed, get the same random values. Try to randomize the seed.
* Do something that involves hardware. A box to detect radiation counts and feeds that back to the computer, for instance.
I wonder if reading unmapped memory is random enough? Like if there's nothing in the expansion port, and you read a IO2 address? I suspect the data bus has enough capacitance to simply hold the last value. Not so random after all I guess. Or what about the upper nybble of color RAM?
On the Z80, the R register was the 16-bit address of the Dynamic RAM address being refreshed. When we needed a random number on the Z80, we just loaded A with the R register and AND with the mask for the bits we wanted from that. But I think that's clever, you pre-surveyed that section of ROM to get your result. Does not the 6502 have a RAM refresh register?
Nope, only A, X, Y, P, S and the instruction pointer.
I enjoy the smart alec self deprecation :) What if you pointed it at video memory for randomness?
You made it a really interesting video with the boundary pushing approach.
In regards to the 204/205 bad luck? how about putting character 206 into 204? then you could use that alternate technique - but how expensive would it be to replace the char? alternatively replace character 0 and 1 then do away with any addition? can you 'offset' the pointer to the start of character ROM? perhaps shift it up so 205 is 0?
I'm not sure about this, but my intuition says that the pointer to the charset would need to point to the start of a page (xx00), in which case offsetting it by one wouldn't work. Also, since the goal was to optimize for size the preparation needed to move the charset pointer adds more bytes than it saves. But, if optimizing for speed, redefining a character is fine as an initial setup cost if it saves cycles by tightening the loop.
@@JeremyNasmith - I was reading and you can change the pointer to where the character glyphs start but I am unaware if it's restricted to the start of a page - it's a VICII register that's set so I don't know if that hints at any indication.
Well if the pointer isn't fixed to the start of a page? you could get rid of combining the random value with the 205/206 and print 0 or 1 directly - but you sound like you know it'd still be more expensive :P
A friend of mine used to joke that a true test for randomness is to sample if a man is spitting on the sidewalk in Tulsa on that cycle.
Well, I know about machine language because it’s quite an impressive way to build and compile faster.
Thanks for the great video.
Love it
Robin, do you know why the 'Restore' button has to be tapped quickly for the 'Run/Stop Restore' break function to work? In other words, if you just press Run/Stop and the Restore button normally the break won't happen. Is this a function of how the Restore button is read or is that button actually somehow physically different from the others so that it ONLY works if it's tapped quickly? Always wondered about that.
The RESTORE key is certainly very different from the rest of the keys on the keyboard. It's not part of the standard keyboard matrix that's scanned via the PIO lines of CIA1 but is instead connected to the NMI line. (It goes through a 555 circuit that debounces it.) So unlike any other key, where the computer knows about it when it decides to scan the keyboard, pressing RESTORE immediately halts whatever the computer is doing and jumps to the NMI routine. (That routine scans the keyboard to see if RUN/STOP is being held down as this routine executes.)
I'm not clear on why you're having issues with what sounds like different lengths of key press, though. I wonder if it could be a problem related to the debouncing of that switch? It would be interesting to put an oscilloscope on the relevant signals on a C64 known to be working properly and on yours and see if there's any difference.
In addition to what Curt said, I've heard that many (mostly breadbin?) C64s have the incorrect value of capacitor (or resistor?) on the Restore key line and so they need a sharper thwack to get the signal through?
@@8_Bit Yep, the C64 I had back in the day was indeed the old breadbin style and I remember on several occasions trying to just hold the Run/Stop and Restore keys like a normal combination (such as Shift-Return or whatever) and it did not have the desired effect. Had to thwack it hard and quick to do the deed. I had no idea that other C64 versions didn't need that sort of thwacking. I just figured it was designed that way to prevent accidental resets, etc. Interesting!
@@Curt_Sampson I guess Commodore modified the motherboard slightly in later versions to fix this issue?
@@JustWasted3HoursHere Yeah, it seems like it's by design to me too, though some people say it was the incorrect value. I'm not sure what proof they have of this, maybe just that it doesn't match the schematics included in the C64 Programmer's Reference Guide? Well, those schematics have several errors, so I don't see it as fully authoritative. And even if the schematic says something else, it's possible they deliberately changed the value during production.
21:46 Wouldn't rotating the CIA timer low byte mess up stability of the system?
I was recently brushing up on ML and using random numbers, when this video came to mind! I'd like to generate a two-byte random number, but as you noted, using the SID is not suitable without introducing a delay. There is an example in Compute!'s "Programing the Commodore 64" of a routine at $E0BE that produces 4 bytes, but this appears to be slower than calling RND() in BASIC! Maybe I'm doing something wrong, but do you have a suggestion on how to generate two random bytes without introducing a delay?
If you set the SID frequency to $FFFF then you only have to wait 17 cycles to guarantee it changes, so I'd suggest just grabbing a byte, do some initialization or whatever (you can probably think of something useful your code could be doing) and then grab the second byte. Even if you just waste the 17 cycles, it's probably not all that bad.
If that's not acceptable, then I'd suggest using a large-ish software LFSR, maybe 16 bits or more. But that would probably take 17 cycles or more anyway to generate the next number in the sequence.
@@8_Bit These are great suggestions -- thank you! I was goofing around and cheated: I moved the LDA $D41B instruction to a subroutine with an NOP before the RTS -- the total time to call it with JSR is (i think) 16 cycles, which is good enough for my academic exercise.
Cute. But how small can you golf a program which outputs a maze where there is guaranteed exactly one route between any two locations? No walls that completely wall off one area from another. No loops.
For the basic version, try this: 0 PRINT CHR$(RND(.)+I);:GOTO (set i to 205.5 then type goto 0). Notice INT() has been removed and 205.5 is a variable
Also, one thing you could do would be to make an inner loop in which you ROR 8 times, thus using all the bits in the byte (wherever you get it from). You could also try this in a machine with only - say - 16k of RAM and pull a byte from an address where there’s no RAM, ROM, or hardware and see if it reads “noise” values. So, maybe a VIC-20. Also, what happens if you try to read the joystick and none is connected? Or read the cassette input? Are bits other than bit 0 any more/less “random” than bit 0? Will this work on a C128?
For small (256 bytes) machine code programs which includes some "randomness" this still amazes me - ua-cam.com/video/sWblpsLZ-O8/v-deo.html . The code and how it works could make for an interesting video.
Another kind of maze can be made with
10 A=RND(1)*4:PRINTCHR$(A+171-(A>1)*5);:GOTO10
Not quite as elegant a one-liner, as the four characters involved are not all adjacent in the PETSCII table.
How do you make sure basic doesn't stomp on the memory used by the assembler or you asm program?
Hello
Does anyone recognise the Disk Emulator / SD Card adaptor that Robin is using ?
I do! :)
It's the uIEC/SD, sold by RETRO Innovations.
Do all the Kernals act the same for CHROUT? (Jiffydos, Keernal v1,2,3 etc) Can you call the BASIC RND function and save any bytes? Kinda slow?
i think it would be nice to have this in graphics mode without the character routine
Hi, one though would be to search in rom for part of the code with a subroutine return just after and just called subroutines?
205/206 dec is 1100 1101 bin. Would NOTing the two LSBs reduce cycles of transferring bytes?
That's a lot slower than I was expecting a machine-language program to be. And you say most of that is CHROUT being the bottleneck? That's... actually not surprising now that I think about what all has to be done between reading an ASCII code and actually getting it onto the screen. In particular, the process of checking for every possible control character and converting what's left from ASCII to a character code probably requires a laundry list of compares (I wonder if 205/206 was picked over 109/110 initially because it somehow bypasses some of them), all of which could be done away with when you know you're only going to by outputting one of two characters. Even the value of register 646 could possibly be stored permanently in a CPU register since you only need to retrieve it at the beginning.
The bulk of the overall CHROUT time is probably spent scrolling the screen.
As well as the character to screen code conversion process and scrolling, there's also the overhead of figuring out which channels have been opened for output and sending the character to each one. There's really a fair amount of overhead in that CHROUT routine as compared to simpler microcomputer ROMs where the commonly used routine simply prints to the screen and that's it.
@@Curt_Sampson You'd think the part that outputs to the screen specifically would be a subroutine that coders could just JSR to directly and cut out the middleman. I remember _Mapping the C64_ mentioning that some Kernal routines work like that.
EDIT: OK, yeah, it turns out there is, at $E716. Glad I went to the trouble of converting that whole book to searchable text now.
@@stevethepocket Yup, and nice work on digging that up. The only thing to check there is whether the code that delays after a newline when you're holding down CTRL is part of the generic routine or the $E716 routine.
@@Curt_Sampson Already tried it and yes it is. But I think csbruce was right about the scrolling being the real bottleneck because I didn't notice any difference (and I can't imagine scrolling the entire screen would be something you could easily speed up, nor would you really want to). I'd have to record the footage and go over it frame by frame to see if the initial screen fill is any faster; they both go by too fast to really tell.
Do you know 10 print racer revamp?
Lovely little game
How would one get the output to scroll smoothly?
What about bit 0 from the pot pin in the jjoystick port. That mest be kind of random?
Has it really been that long ? Not since last year ? I've forgotten whatever the program was about.
Anyone up for making this to a vertical scroll with a sweet SID tune?
Call me dumb, but after this assembles to machine code, isn't it 14 bytes? I'm going to noodle over this. Not sure I know enough to do anything
Hi. What happens if you eliminate the “LDA #$80” line in the initialization section? Since you still have 0xFF in the accumulator, bit 7 is still set, which is what you want in 0xD412. Would the extra ‘1’ bits (bits 0-6) cause a problem here?
You can't read the raster register during badlines.
That is simply "a maze thing"
...
sorry
I LOVE C64 👍🥂🎩
does anyone know how to get back to bank 0 in TMP w/ REU after you've switched to another bank? I've looked through the docs, and there doesn't seem to be a way to do it. There's a bank switching option, but it only allows you to switch to banks 2-7 for whatever reason.
I just tried it and you're right. That seems pretty bizarre; not sure if that's a bug or oversight or something by design that I don't understand the reason for. A potential work-around is to exit to basic with back arrow 1, type new, load"TMP+REU*",8,1 then sys32768 to re-enter. You should be back in source bank 0 and your code should still be there. Worked for me just now, anyway.
@@8_Bit Thanks for the response! So that didn't quite work. I got an "out of memory" error. (I'm guessing I must've overwritten something I shouldn't have in my code). Luckily I took your advice in one of your other videos and I have a reset button at the ready, and after resetting, I was able to run TMP again and get my code back on bank 0. Wheww. Thanks for your help!
@@nathanjohnson9715 Did you do the NEW command before the LOAD? Capitalized this time to stand out more :)
@@8_Bit ha, nope, I sure didn't. Worked beautifully once I actually followed instructions.
I knew this thing is coming. What's the next power play from Robin? 10 PRINT in IC form?
Now do it with the Super CPU 👍😁👍
Are the end songs available somewhere to listen to?
This episode's song has a video here: ua-cam.com/video/lHOxmXCSqAs/v-deo.html Some of the other songs are on that channel too. Thanks for the interest, I added the link to the video description now too, which I often forget to do.
@@8_Bit very cool, thanks; subscribed
On the Commander X16, the page of memory starting at address $C000 looks pretty "random" while viewing its contents in the monitor. The pattern generated from the following code looks fairly good, too...
_Standard version..._
.,8000 A0 00 LDY #$00
.,8002 B9 00 C0 LDA $C000,Y
.,8005 29 01 AND #$01
.,8007 69 CD ADC #$CD
.,8009 20 D2 FF JSR $FFD2
.,800c C8 INY
.,800d 20 E4 FF JSR $FFE4
.,8010 F0 F0 BEQ $8002
.,8012 60 RTS
_Slanted version..._
.,8000 A0 00 LDY #$00
.,8002 A2 CD LDX #$CD
.,8004 B9 00 C0 LDA $C000,Y
.,8007 C8 INY
.,8008 C9 55 CMP #$55
.,800a 90 01 BCC $800D
.,800c E8 INX
.,800d 8A TXA
.,800e 20 D2 FF JSR $FFD2
.,8011 20 E4 FF JSR $FFE4
.,8014 F0 EC BEQ $8002
.,8016 60 RTS
can the rnd() not be based on DES64
7:24 Eighty or forty characters per line?
@Mr Guru Ah, it scrolls two lines at a time, therefore eighty characters per scroll event. Got it.
I bet you'd get a significant speed-up by inlining the print subroutine. All that pushing and popping state from stack, yuck.
BBC BASIC Version
10REPEAT:PRINTCHR$(47+INT(0.5+RND(1))*45);:UNTILFALSE
The entire program comes in at 32 bytes because REPEAT, PRINT, CHR$(, INT(, RND(, UNTIL and FALSE come out as byte each as they are tokenised.
BBC Assembler Version
10 FOR N%=0 TO 3 STEP 3
20 P%=&2000
30 [
40 OPT N%
50 .loop
60 jsr &AF51 ; &AF80 for BASIC 1
65 lda &2A
70 ror A
80 lda #47
90 bcs print
100 adc #45
110 .print
120 jsr &FFEE
130 jmp loop
140 ]
150 NEXT
CALL &2000
Routine AF51 is the BASIC RND variable function. RND without braces or an argument returns a random signed 32 bit integer in zero page 2A, 2B, 2C and 2D (The Integer Work Area). We just take the lowest byte. Routine FFEE is OSWRCH i.e. OS write character. Its the operating system equivalent of PRINT CHR$(A).
The BBC Micro, apart from Mode 7 (Teletext mode), does not have character mapped screen memory so you have to use the OS to write characters. Mode 7 has different characters in the character set to \ and / and modes 3 and 6 have 25 line screens and not 32 line screens and space them out with scan lines which are not backed by memory. So best to use one of the other screen modes.
My optimised version. Rather than using the \ and / characters which, in ASCII, are quite far apart, I've defined a couple of user characters 128, and 129.
10 VDU23,128,&80,&40,&20,&10,8,4,2,1
20 VDU23,129,1,2,4,8,&10,&20,&40,&80
30 FOR N%=0 TO 3 STEP 3
40 P%=&2000
50 [
60 OPT N%
70 .loop
80 jsr &AF51 ; &AF80 for BASIC 1
90 ror &2A
100 lda #128
110 adc #0
120 jsr &FFEE
130 jmp loop
140 ]
150 NEXT
Since the $ as a prefix is already used in BBC BASIC (As a way of storing strings in memory, e.g. $1234="Hello world"), then & is used at the 'HEX' indicator. Also the OSWRCH routine, unlike the C64 equivalent, leaves carry in an unknown state, hence jmp.
Okay I got it smaller! 12 bytes...
10 VDU23,128,&80,&40,&20,&10,8,4,2,1
20 VDU23,129,1,2,4,8,&10,&20,&40,&80
30 FOR N%=0 TO 3 STEP 3
40 P%=&2000
50 [
60 OPT N%
70 .loop
80 jsr &AF51
90 lda #&81
100 and &2A
120 jsr &FFEE
130 jmp loop
140 ]
150 NEXT
It basically ANDs the random value with &81 which results in either &00, &01, &80 or &81. This is a cheat. 0 is the null character which does nothing. 1 means send next character to printer (so it doesn't appear on the screen). The other two are characters 128 and 129 i.e. our maze characters.