10 A=26493:B=66:C=13:GOSUB40 20 A=291227:B=B+2:C=C+3:GOSUB40 30 END 40 I=RND(-A):FOR I=1 TO 4:PRINT CHR$(B+RND(C));:NEXT:RETURN
In my Optimizing Color BASIC series, one of the things I learned was how much faster it was to use HEX values instead of decimal values. For example:
Even though the second statement is one character more to parse, it is still much faster since it’s easier for the interpreter to calculate base-16 values than base-10. This was even the case for zero:
Somewhere in the comments, CoCoSDC creator Darren Atkinson let me know about using just a period for zero. I wrote tested that in part 9.
Recently (March 28), Carlos Comacho tagged me in the Facebook CoCo group about something he found on a NEC PC-6001 Z-80 computer testing various ways to set something to zero. Here is the screenshot he shared:
Using a decimal 0 and HEX &H0 were tested, as well as using VAL(“”). Huh?
In the CoCo 3 BASIC quick reference guide, I find this entry:
I am aware of using this to convert a STRING to a number, such as when using LINE INPUT:
10 LINE INPUT "ENTER YOUR AGE:";A$ 20 A=VAL(A$) 30 IF A=42 THEN PRINT "ULTIMATE AGE!"
It also handles decimal values, such as A=VAL(“12.34”). And I guess I knew that if you passed it an empty string, it would return zero because pressing ENTER on a LINE INPUT prompt that then went into VAL would return 0…
Let’s do nothing!
Of course I had to benchmark this and see what CoCo did with it! Here’s the current version of my BASIC benchmark test:
0 REM BENCH0.BAS 5 DIM TE,TM,B,A,TT 10 FORA=0TO3:TIMER=0:TM=TIMER 20 FORB=0TO1000 30 Z=0 70 NEXT 80 TE=TIMER-TM:PRINTA,TE 90 TT=TT+TE:NEXT:PRINTTT/A:END
Running this produces a value of 189. Next we try HEX &H0:
0 REM BENCHX0.BAS 5 DIM TE,TM,B,A,TT 10 FORA=0TO3:TIMER=0:TM=TIMER 20 FORB=0TO1000 30 Z=&H0 70 NEXT 80 TE=TIMER-TM:PRINTA,TE 90 TT=TT+TE:NEXT:PRINTTT/A:END
That shows 174. Now let’s do the one with the period:
0 REM BENCHDOT.BAS 5 DIM TE,TM,B,A,TT 10 FORA=0TO3:TIMER=0:TM=TIMER 20 FORB=0TO1000 30 Z=. 70 NEXT 80 TE=TIMER-TM:PRINTA,TE 90 TT=TT+TE:NEXT:PRINTTT/A:END
That one gives me 147 – the fastest so far! And lastly, the silly VAL(“”) empty quote thing:
0 REM BENCHVAL.BAS 5 DIM TE,TM,B,A,TT 10 FORA=0TO3:TIMER=0:TM=TIMER 20 FORB=0TO1000 30 Z=VAL("") 70 NEXT 80 TE=TIMER-TM:PRINTA,TE 90 TT=TT+TE:NEXT:PRINTTT/A:END
This one gives me 212, even slower than decimal 0.
It looks like, on Color BASIC at least, the VAL(“”) trick is not useful, but I appreciate Carlos letting me know about it.
Have you seen this on any other flavor of BASIC? Let me know in the comments.
Until next time…
In a previous article, I wrote a BASIC program that simulated pulling marbles out of a bag. I described my thought process, including how I initially thought I would have to randomize the virtual marbles in the virtual bag. I realized this would be unnecessary, but still offered a demonstration of a simple way to randomly shuffle an array of items, as one might do with a deck of playing cards:
NOTE: I had a bug on line 40 and was only counting from 0 to 24 (25 letters) instead of 0 to 25 (26 letters of the alphabet). Fixed here:
10 REM shuffle.bas 20 REM CREATE ALPHABET ARRAY 30 DIM A$(25) 40 FOR A=0 TO 25 50 A$(A)=CHR$(65+A) 60 NEXT 70 REM DISPLAY ARRAY 80 GOSUB 190 90 REM SWAP EACH ONE RANDOMLY 100 FOR A=0 TO 25 110 S=RND(26)-1 120 SV$=A$(S) 130 A$(S)=A$(A) 140 A$(A)=SV$ 150 NEXT 160 REM DISPLAY ARRAY 170 GOSUB 200 180 END 190 REM DISPLAY ARRAY 200 FOR A=0 TO 25 210 PRINT A$(A); 220 NEXT 230 PRINT 240 RETURN
That would display the 26 letters of the alphabet, then shuffle them and display the results. My shuffle routine went through each position (0 to 25) and swapped it with another random position (0 to 25).
However, adding to the flaw in the simulation that article was about, there was another flaw in my shuffling examples.
In a comment, James Jones added:
Actually, to get a random shuffle with all permutations equally likely, you have to do something like this:
FOR i:= 1 to n-1
i.e. at each step you swap a(i) with a(j) where j is a randomly chosen number between i and n inclusive.James Jones
His program logic uses ‘n’ as the number of elements we are shuffling, which is 26 for my example.
The for/next loop of ‘i’ will count from 1 to n-1, so 1 to 25 in this example.
‘j’ will be the randomly selected target element to swap with the element at ‘i’. It is chosen using the random number generator. I it looks like his rnd(26) would return 0-25, so the result of j for each count of 1 to n-1 would be
i = 1 -> j = 1 + rnd(26 + 1 - 1) -> j = 1 + rnd(26) -> j = 1 to 26 i = 2 -> j = 2 + rnd(26 + 1 - 2) -> j = 2 + rnd(25) -> j = 2 to 26 i = 3 -> j = 3 + rnd(26 + 1 - 3) -> j = 3 + rnd(24) -> j = 3 to 26 ... i = 23 -> j = 23 + rnd(26 + 1 - 23) -> j = 23 + rnd(4) -> j = 23 to 26 i = 24 -> j = 24 + rnd(26 + 1 - 24) -> j = 24 + rnd(3) -> j = 24 to 26 i = 25 -> j = 25 + rnd(26 + 1 - 25) -> j = 25 + rnd(2) -> j = 25 to 26
It then uses a temporary variable to swap the position of a(i) with a(j).
My original version randomly swapped each position with any of the 26 positions. James’ code swaps each position with only positions after it.
With apologies to James for down-porting his logic to Microsoft BASIC, here is my original example updated to take this approach. To keep it as close to my original example as possible, I’ll adjust my array to use 1-26 (instead of 0-25). Color BASIC arrays are base-0, but for this example we’ll just ignore the zero. I’ll try to bold the changes from the original.
10 REM jjshuffle.bas 20 REM CREATE ALPHABET ARRAY 25 N=26 30 DIM A$(N) 40 FOR A=1 TO N 50 A$(A)=CHR$(64+A) 60 NEXT 70 REM DISPLAY ARRAY 80 GOSUB 190 90 REM SWAP EACH ONE RANDOMLY 100 FOR I=1 TO N-1 110 J=I+INT(RND(N+1-I)-1) 120 TEMP$=A$(I) 130 A$(I)=A$(J) 140 A$(J)=TEMP$ 150 NEXT 160 REM DISPLAY ARRAY 170 GOSUB 200 180 END 190 REM DISPLAY ARRAY 200 FOR A=1 TO N 210 PRINT A$(A); 220 NEXT 230 PRINT 240 RETURN
And to convert this back to my first example using base-0 arrays:
10 REM shuffle2.bas 20 REM CREATE ALPHABET ARRAY 30 DIM A$(25) 40 FOR A=0 TO 24 50 A$(A)=CHR$(65+A) 60 NEXT 70 REM DISPLAY ARRAY 80 GOSUB 190 90 REM SWAP EACH ONE RANDOMLY 100 FOR A=0 TO 25 110 S=A+RND(26-A)-1 115 PRINT A,S 120 SV$=A$(S) 130 A$(S)=A$(A) 140 A$(A)=SV$ 150 NEXT 160 REM DISPLAY ARRAY 170 GOSUB 200 180 END 190 REM DISPLAY ARRAY 200 FOR A=0 TO 25 210 PRINT A$(A); 220 NEXT 230 PRINT 240 RETURN
Any other random thoughts?
Until next time…
Today I will present Art Flexser‘s solution to this marble puzzle, as well as a corrected version of mine.
First I will correct my version of the “pulling marbles out of a bag” puzzle recently presented by Steve Ostrom. I now understand the rules to be:
- You have a bag full of the following marbles: 5 red, 4 blue, 3 white, and 3 black.
- You have four marble trays, one for each color.
- You begin by pulling a marble out of the bag and placing it in the appropriate tray.
- Repeat until one of the trays has three of the marbles for that color.
- Track which color made it to three first.
- Do this 100,000 times (for our statistics).
- At the end, there should be some number of times each color of marbles was completed before any other color, in that turn.
Losing my marbles
I rewrote my visual simulator to represent the bag of marbles (the top line of groups of four color blocks) and each tray (four more lines, one for each color).
As my simulation runs, marbles will be taken from the top row (the bag) and placed in the appropriate tray.
When a tray is full (it has three of the same color marble), the round stops, and the total “completed” count for that color is incremented.
Repeat for 100,000 iterations. The current iteration count is displayed in the top right corner of the screen.
During this process, the statistics for each tray are displayed. It looks like this:
Here is my commented and non-optimized Color BASIC program:
10 ' marble2.bas 20 ' 30 ' INITIALIZE STATS 40 ' 50 RT=0:BT=0:WT=0:KT=0 60 CLS 70 PRINT "MARBLE BAG:" 80 ' 90 ' DO THIS 100,000 TIMES 100 ' 110 FOR TC=1 TO 10000 120 PRINT@25,TC 130 ' 140 ' DISPLAY MARBLES 150 ' 5 RED=191, 4 BLUE=175, 3 WHITE=207, 3 BLACK=128 160 ' 170 PRINT@32,STRING$(5,191);STRING$(4,175);STRING$(3,207);STRING$(3,128) 180 ' 190 ' RESET MARBLE TRAYS 200 ' 210 RL=1132:BL=RL+32:WL=RL+64:KL=RL+96 220 PRINT @96, "RED TRAY :" 230 PRINT "BLUE TRAY :" 240 PRINT "WHITE TRAY:" 250 PRINT "BLACK TRAY:" 260 ' 270 ' RESET MARBLE COUNTS 280 ' 290 RC=0:BC=0:WC=0:KC=0 300 ' 310 ' GRAB MARBLES 320 ' 330 MC=0 340 ' 350 ' RANDOM MARBLE LOCATTION 360 ' 370 ML=1024+32+RND(15)-1 380 ' 390 ' GRAB MARBLE 400 ' 410 V=PEEK(ML) 420 ' 430 ' IF MARBLE USED, TRY AGAIN 440 ' 450 IF V=0 THEN 370 460 ' 470 ' MARK MARBLE REMOVED 480 ' 490 POKE ML,0 500 ' 510 ' PUT MARBLE IN TRAY 520 ' 530 IF V=191 THEN POKE RL+RC,V:RC=RC+1:GOTO 600 540 IF V=175 THEN POKE BL+BC,V:BC=BC+1:GOTO 600 550 IF V=207 THEN POKE WL+WC,V:WC=WC+1:GOTO 600 560 IF V=128 THEN POKE KL+KC,V:KC=KC+1:GOTO 600 570 ' 580 ' CHECK FOR 3 IN A TRAY 590 ' 600 IF RC=3 THEN RT=RT+1:GOTO 680 610 IF BC=3 THEN BT=BT+1:GOTO 680 620 IF WC=3 THEN WT=WT+1:GOTO 680 630 IF KC=3 THEN KT=KT+1:GOTO 680 640 ' 650 ' NOT FULL YET, CONTINUE GRABBING MARBLES 660 ' 670 MC=MC+1:IF MC<15 THEN 370 680 ' 690 ' PRINT COUNT AND PERCENTAGE 700 ' 710 PRINT@256,"RED :";RT,RT/TC 720 PRINT "BLUE :";BT,BT/TC 730 PRINT "WHITE:";WT,WT/TC 740 PRINT "BLACK:";KT,KT/TC 750 ' 760 ' REPEAT 770 ' 780 NEXT 790 ' 800 ' REPORT RESULTS 810 ' 820 PRINT "TIME:";TIMER/60 830 END 840 ' 850 ' VARIABLES 860 ' 870 ' TC = TOTAL COUNT (RUNS) 880 ' 890 ' RL/BL/WL/KL = DEST. TRAY LOCATIONS 900 ' 910 ' TOTAL TIMES EACH TRAY FILLED: 920 ' RT = RED TOTAL 930 ' BT = BLUE TOTAL 940 ' WT = WHITE TOTAL 950 ' KT = BLACK TOTAL 960 ' 970 ' RC/BC/WC/KC = CURRENT TRAY COUNT 980 ' 990 ' MC = MARBLES PULLED FROM BAG 1000 ' 1010 ' ML = MARBLE LOCATION (PEEK) 1020 ' 1030 ' V = VALUE OF MARBLE PEEKED
It’s going to take a long time for the CoCo to do 100,000 iterations, but luckily I heard from a guy who did it a faster way…
ADOS creator Art Flexser wrote in with his take on this puzzle. (And I tried really hard not to fan-boy over getting an e-mail from him. His RS-DOS replacement is legendary.)
Hi, Al. I found Steve Ostrom’s marbles problem of interest, and thought it would make an interesting programming exercise for myself. (I haven’t written a program in ages.)
I have described my understanding of the problem in the program’s initial comments. I hope it corresponds to Steve’s intent and to your (revised) interpretation.
I wrote the program under QBASIC-64 so that numerical results would be obtainable much faster. (Downloadable for free from www.qb64.net). I had to change two lines (lines 92 and 140) for CoCo compatibility since the RND function is defined differently on the two platforms. See the comments for the necessary changes. The listing I’ve attached is the QBASIC version. The output is most readable in 80-column mode.
I did not obtain the same probabilities as Steve did. I got .514, .281, .102, and .102 (based on a million trials) for red, blue, white, and black. I have no idea why there is a discrepancy. Perhaps you or Steve (whose email address I don’t have) could send me a copy of his program so I could investigate. Or, maybe your own revised program will be ready soon, so we can see if your results agree with me, with Steve, or with neither.
The speed difference between the two platforms is pretty amazing. The program runs about 200,000 times faster under QBASIC-64 on my 3.4 GHz Windows 10 (64-bit) PC than under CoCo Basic. So, I set NP, (in line 95) the number of trials between results printouts, for a million under QBASIC and five for the CoCo (simulated by the Mocha java emulator) and got results about every 2 seconds either way.
Fantastic! Let’s take a look at what he submitted:
10 'A BAG CONTAINS 15 MARBLES, CONSISTING OF 5 RED, 4 BLUE, 3 WHITE AND 3 BLACK 20 'A TRIAL IS DEFINED AS DRAWING ONE MARBLE AT A TIME OUT OF THE BAG, UNTIL 30 'THREE MARBLES OF THE SAME COLOR HAVE BEEN DRAWN, NOT NECESSARILY IN A ROW. 40 'DRAWING IS DONE WITHOUT REPLACEMENT. 50 'THE PROGRAM REPRESENTS THE 4 COLORS BY THE DIGITS 1-4. 60 'WE WISH TO FIND THE PROBABILITIES THAT EACH OF THE 4 COLORS "WINS" A TRIAL BY 70 'SIMULATION OF A LARGE NUMBER OF TRIALS. 75 'THE MARBLES REMAINING IN THE BAG ARE REPRESENTED BY A STRING VARIABLE 80 M0$ = "111112222333444" 'INITIAL STATE OF THE BAG, WITH 15 MARBLES 90 CT(1) = 0: CT(2) = 0: CT(3) = 0: CT(4) = 0 'INITIALIZE WIN COUNTS FOR THE 4 COLORS 92 RANDOMIZE TIMER 'SEED RANDOM NUMBER GENERATOR USING TIMER VALUE 93 'FOR COCO, CHANGE ABOVE LINE TO X=RND(-TIMER) * 95 NP = 1000000 100 FOR I = 1 TO NP 'PRINT RESULTS AFTER A NUMBER NP OF TRIALS HAS OCCURRED 110 'INITIALIZE COUNTS OF HOW MANY MARBLES OF EACH COLOR HAVE BEEN DRAWN THIS TRIAL 120 R(1) = 0: R(2) = 0: R(3) = 0: R(4) = 0 130 MB$ = M0$: NM = LEN(MB$) 'NM = NUMBER OF MARBLES REMAINING IN BAG 135 'GRAB A RANDOM MARBLE FROM THE BAG 140 P = INT(RND * NM) + 1 145 'FOR COCO, CHANGE ABOVE LINE TO P=RND(NM) * 150 'P IS THE RANDOM POSITION NUMBER SELECTED WITHIN THE BAG CONTENTS STRING 160 CL$ = MID$(MB$, P, 1) 'CL$, THE COLOR OF THE SELECTED MARBLE, IS "1", "2", "3", OR "4" 170 C = VAL(CL$) 'CONVERT TO AN INTEGER FROM 1-4 180 R(C) = R(C) + 1 'R(C) IS HOW MANY MARBLES OF THIS COLOR HAVE BEEN DRAWN SO FAR 190 'IF WE HAVE 3 MARBLES DRAWN OF THIS COLOR, INCREMENT WINS COUNTER FOR THIS COLOR 200 'AND BEGIN A NEW TRIAL 210 IF R(C) = 3 THEN CT(C) = CT(C) + 1: GOTO 260 220 'ADJUST BAG'S CONTENT TO REFLECT THE CHOSEN MARBLE'S REMOVAL 230 MB$ = LEFT$(MB$, P - 1) + RIGHT$(MB$, NM - P) 240 NM = NM - 1 250 GOTO 140 'CONTINUE TRIAL, DRAW ANOTHER MARBLE RANDOMLY 260 NEXT I 270 'CALCULATE PROBABILITIES AND PRINT RESULTS SO FAR 280 TT = CT(1) + CT(2) + CT(3) + CT(4) 'TOTAL NUMBER OF TRIALS SO FAR 290 PR(1) = CT(1) / TT: PR(2) = CT(2) / TT: PR(3) = CT(3) / TT: PR(4) = CT(4) / TT 295 PRINT " RED BLUE WHITE BLACK TOTAL TRIALS" 300 PRINT CT(1); CT(2); CT(3); CT(4); " "; TT; " FREQUENCIES" 310 PRINT PR(1); PR(2); PR(3); PR(4); " PROBABILITIES": PRINT 320 GOTO 100
His approach uses a string containing characters representing each of the four marble types. I copied it in QB64 to try it out. I was immediately impressed that, even though it looks like an old-school DOS text program, it allowed me to resize the window so I could see the entire program at once:
With a press of F5, the program is compiled into an executable, and then ran:
It was blasting through a million iterations every few seconds, and was instantly showing results: Red at 51%, blue at 28%, white at 10% and black at 10%.
While this was running, I decided to see if it would run in PC-BASIC as well:
I let it run for awhile, but it didn’t print anything. It seems PC-BASIC is much slower than QB64, which is likely because QB64 is some kind of compiler that turns the BASIC into an executable. When I worked for Radio Shack (1988-1991 or so), I remember benchmarking a CoCo 3 BASIC against some Tandy 1000 BASICs and the CoCo was faster than everything but the 286 in turbo mode. Maybe that is the type of machine PC-BASIC is trying to simulate ;-)
And, of course, since his code supports it, I wanted to see it running on a (virtual) CoCo. I made the three changes his comments suggest (for how RND works, and reducing the number of iterations to 5 per cycle instead of a million):
Per his note, the CoCo version is updated every 5 times through, while the QB64 version is doing a million.
After a bit, we see the pattern develop:
Red is at 51%, blue at 26%, white at 11% and black at 10%. Even with far fewer iterations (5 versus a million each time through), it is already very close to the QB64 version that reported red at 51%, blue 28%, white 10% and black 10%.
Meanwhile, an hour later, my CoCo version completed 100,000 iterations:
My final results were red at 50%, blue at 29%, white at 10% and black 10%. This is very close to Art’s 51%/28%/10%/10% results, but it does not match the results from Steve’s simulation:
I ran 100,000 iterations (took most of the night) and here are the rounded percentages I found. Red = 42.5%, blue = 27.5%, white and black are both 15.0%.Steve Ostrom
Is this just a difference in the patterns of the psuedo random number generator? I’ll share my results on Facebook and see what Steve says.
NOTE: The time value displayed at the end is meaningless. I forgot to take into consideration that the TIMER value rolls over at 65535 (16-bits). At a tick every 60th of a second (on an American NTSC CoCo using the 60Hz screen interrupt), that’s about 18 minutes. This took well longer than that, so it rolled over several times. I’ll have to add a bit more code so it can properly estimate the total time taken.
Until next time…
If you want to play with BASIC on a modern computer, here are two options I have found.
PC-BASIC is an implementation of the old GW-BASIC that came with MS-DOS. It allows you to enter commands directly (PRINT “HELLO WORLD”) just like the old days, and also write full programs with line numbers. You can load and save them just like you’d expect. This one should be familiar to those who have used Microsoft BASIC on other systems.
I found PC-BASIC awhile ago when I was working on my SirSound project. I was trying to find out how Microsoft supported multi-voice music on their BASICs that supported it. I have since used it for various quick-and-dirty experiments, even in my day job (most recently, to draw out a user interface for an embedded device I am working on).
Special thanks to Art Flexser for letting me know about this one.
This is an implementation of QuickBasic from the MS-DOS days. It allows writing a more modern version of BASIC, and compiling it down to an executable.
I have not used QB64 yet, beyond making a simple “Hello World” program, but it looks promising.
If you know of any other cross-platform BASICs (Windows, Mac and Linux) I should try, please mention them in the comments. Thanks!
Until next time…
See also: part 1
- 2020-03-31 – Corrected the spelling of Art’s last name. Sorry about that!
Well, you can disregard my previous article. I misunderstood the process Steve Ostrom was describing. I have a follow-up planned, but wanted to share Steve’s response when I explained what I thought the process was:
… the probability puzzle is a little more complex. Here is the idea: Put 4 trays in front of you, one for each color. Pull one marble and place it in the proper tray. Pull a second marble. Place in its tray. Continue pulling marbles until one of the trays contains 3 marbles. Stop there. That’s iteration #1. Replace all the marbles into the bag. Start again. Continue 100,000 times !! Now calculate the odds for each tray that it will be the tray that gets to 3 first.Steve Ostrom
That’s a bummer, because I let mine run all weekend and got these results:
If my simulation had been correct, when a bag has five red, four blue, three white and three black marbles, the odds of pulling out three of the same color would have been:
- Red – 62.7% of the matches
- Blue – 24.7% of the matches
- White / Black – 6.2% of the matches
What I forgot to do is include a count of the number of times to pull marbles out of the bag! Without that, I can’t even show the results of my incorrect simulation.
I will be writing a “correct” simulation and sharing the results soon*.
* soon [so͞on] ADVERBOxford Dictionary definition of “soon”
in or after a short time.
Okay, fine. Hopefully soon. Soon for me, anyway. Maybe tomorrow. Or next week.
Math and Art
Others have responded on this and I plan to share their efforts and results as well. One of the most notable was seeing the legendary Art Flexser join in. Mr. Flesxer was responsible for the most popular DISK BASIC replacement the Color Computer ever had – ADOS. Back then I wanted ADOS so bad, but the thought of replacing a ROM chip seemed beyond my skills. “Wish I knew then, what I know now!” For those unfamiliar, ADOS is described in the CoCo FAQ as follows:
A-DOS was developed by Art Flexser. It came in three versions, ADOS for the CoCo 1 and 2, and ADOS 3 & Extended ADOS 3 for the CoCo 3. It was 100% compatible with RS-DOS if you didn’t need to patch Disk BASIC, and added features to RS-DOS, noteably 40 and 80 track drive support. ADOS came on a disk, and could be loaded into the CoCo, or you could customize ADOS, program an EPROM, and use the EPROM as your disk ROM, therefore booting your CoCo with ADOS. This was a neat, because many users then set their CoCos to boot with the 80 column screen. It also ran the CoCo at double-speed, even during disk and printer I/O, featured auto line numbering, arrow scroll through listings, auto edit of errors, macros, etc. Extended ADOS 3 added things like parellel printer output (assuming you had the right hardware), wildcard filenames, and a RAMdisk. This was arguably the most popular modified RS-DOS used with the CoCo.Description of Art Flexser’s ADOS from the Color Computer FAQ at CoCopedia.com
But I digress… Art implemented this puzzle using a modern basic, QB64. According to the website:
QB64 is a modern extended BASIC programming language that retains QBasic/QuickBASIC 4.5 compatibility and compiles native binaries for Windows, Linux, and macOS.https://www.qb64.org/portal/
I will be taking a look at what he did, soon, and include his results with whatever I come up with and see if we all get the same results as Steve.
NOTE: I got the objective of the marble puzzle wrong, but I’d already written this version. I’ll post a follow-up with the actual puzzle later.
Recently in the Color Computer Facebook group, Steve Ostrom wrote a BASIC program to solve a probability puzzle. He wrote:
You have 15 marbles in a bag. 5 are red, 4 are blue, 3 are white and 3 are black. You pull out one marble at a time, without putting it back, until you have 3 marbles of the same color. What are the odds of getting 3 red marbles, of getting 3 blue marbles, of getting 3 white marbles or of getting 3 black marbles.Steve Ostrom (March 21, 2020 on the CoCo Facebook Group)
I mentioned this in my article about the BASIC RND command recently, but did not address the actual puzzle Steve presented. Today, I will try.
If my understanding of the question is correct, you have a bag with 15 marbles:
- 5 red marbles
- 4 blue marbles
- 3 white marbles
- 3 black marbles
You pull three marbles out of the bag. What are the odds that all three are red, blue, white or black? Since there are more red marbles than any other, grabbing three red marbles must be more likely than any other color. Since there are more blue marbles than white or black marbles, grabbing three blue ones must be more likely than white or black. And, with white and black marbles having the same count, they should have the same probability.
There’s probably a very simple math formula to solve this, but it’s more more fun to write a program to actually try it!
Simulating a Bag of Marbles
I remember making some card programs in BASIC as a teenage. You could create an array representing all the cards, and then shuffle them using the RND random function. For example:
10 REM shuffle.bas 20 REM CREATE ALPHABET ARRAY 30 DIM A$(25) 40 FOR A=0 TO 24 50 A$(A)=CHR$(65+A) 60 NEXT 70 REM DISPLAY ARRAY 80 GOSUB 190 90 REM SWAP EACH ONE RANDOMLY 100 FOR A=0 TO 25 110 S=RND(26)-1 120 SV$=A$(S) 130 A$(S)=A$(A) 140 A$(A)=SV$ 150 NEXT 160 REM DISPLAY ARRAY 170 GOSUB 200 180 END 190 REM DISPLAY ARRAY 200 FOR A=0 TO 25 210 PRINT A$(A); 220 NEXT 230 PRINT 240 RETURN
Starting at line 30, this program creates a string array of 26 items, each containing a letter of the alphabet:
In line 80, it calls a subroutine that will PRINT out the array.
Starting at line 100, it shuffles the array by going through each entry and swapping it with a randomly selected entry.
In line 170, it calls the subroutine to display the array again.
My first thought was to create an array of marbles and then shuffle them. The program would then randomly pull one out of the virtual bag (array) to see what it got.
But then I realized that would be silly and wasteful since we are dealing with randomly selecting an item.
For cards, you need to shuffle them because you get the “next out” card (the one on the top of the deck, unless you are cheating and bottom dealing). But, if you fan open a deck and say “pick a card, any card” the deck doesn’t have to be shuffled since you are randomly picking one. For a magic trick, though, we want the deck to be shuffled because if they were all in order, one could easily look at the remaining cards and determine which one was missing.
But I digress…
If we used a non-sorted array of alphabetical letters:
…and we are going to randomly select one, the order of the items in the array should not matter. We might randomly select 16, 4 and 22. Or 1, 9 and 14. Or 26, 2 and 7.
I don’t need to randomize the marbles in a marble bag at all.
With that out of the way, I next thought about a way to visualize the process of pulling random marbles out of a bag. Since we are dealing with colors, and I am doing this on a Color Computer, I thought maybe I’d use the screen and have color characters represent each of the marble colors.
10 CLS 20 REM DISPLAY MARBLES 30 REM 5 RED=191, 4 BLUE=175, 3 WHITE=207, 3 BLACK=128 40 PRINT@0,STRING$(5,191);STRING$(4,175);STRING$(3,207);STRING$(3,128)
I could then use PEEK to randomly select one of the colors on the screen. Since I need to pick three, and can’t pick the same one twice, I could use POKE to reset the one I Just picked to some value representing “already picked.”
And, to track which ones I picked, I would also POKE the value to another spot on the screen. I could then visually see the process run as a marble was “picked” from the top row (changed to a different character) and “moved” to somewhere else (the color block appearing in a new location).
Then, after picking three marbles, I could look at what I picked and see if they all matched. If they did, I could increment a counter for each color. If all three were red, I would increment a Red Count variable. If all three were black, I’d increment a Black Count variable.
Along with four individual counters (for red, blue, white and black), I’d also have a Total Count. I could get statistics by dividing the individual counts into that total. For example, if there were 100 total counts, and the Blue Count was 15, then picking three blue ones happened 15 out of 100 times – 15%.
I could then reset the marbles and do it again, and let this program run as long as it took for a pattern to emerge.
According to Steve, here is what I should expect:
I ran 100,000 iterations (took most of the night) and here are the rounded percentages I found. Red = 42.5%, blue = 27.5%, white and black are both 15.0%.Steve Ostrom
Keeping my Optimizing Color BASIC series in mind, I am going to present to you an “easy to read but slower to run” version of what I came up with. It is heavily comment, and thus heavily slowed down.
10 ' marble.bas 20 ' 30 ' INITIALIZE STATS 40 ' 50 RC=0:BC=0:WC=0:KC=0:TC=0 60 CLS 70 ' 80 ' DISPLAY MARBLES 90 ' 5 RED=191, 4 BLUE=175, 3 WHITE=207, 3 BLACK=128 100 ' 110 PRINT@0,STRING$(5,191);STRING$(4,175);STRING$(3,207);STRING$(3,128) 120 ' 130 ' GRAB THREE MARBLES 140 ' 150 FOR MD=1088 TO 1090 160 ' 170 ' RANDOM MARBLE LOCATION 180 ' 190 ML=1024+RND(15)-1 200 ' 210 ' GRAB MARBLE 220 ' 230 V=PEEK(ML) 240 ' 250 ' IF MARBLE USED, TRY AGAIN 260 ' 270 IF V=0 THEN 190 280 ' 290 ' MARK MARBLE REMOVED 300 ' 310 POKE ML,0 320 ' 330 ' PUT MARBLE IN HAND 340 ' 350 POKE MD,V 360 NEXT 370 ' 380 ' CHECK FIRST GRAB 390 ' 400 V=PEEK(1088) 410 ' 420 ' DOES IT MATCH NEXT TWO? 430 ' 440 IF V=PEEK(1089) AND V=PEEK(1090) THEN 500 450 ' 460 ' NO, START OVER 470 ' 480 GOTO 110 490 ' 500 ' ALL THREE THE SAME! 510 ' 520 ' IF RED, INC RED COUNT 530 ' 540 IF V=191 THEN RC=RC+1 550 ' 560 ' IF BLUE, INC BLUE COUNT 570 ' 580 IF V=175 THEN BC=BC+1 590 ' 600 ' IF WHITE, INC WHITE COUNT 610 ' 620 IF V=207 THEN WC=WC+1 630 ' 640 ' IF BLACK, INC BLACK COUNT 650 ' 660 IF V=128 THEN KC=KC+1 670 ' 680 ' INC TOTAL COUNT 690 ' 700 TC=TC+1 710 ' 720 ' PRINT COUNT AND PERCENTAGE 730 ' 740 PRINT @128,"RED :";RC,RC/TC 750 PRINT "BLUE :";BC,BC/TC 760 PRINT "WHITE:";WC,WC/TC 770 PRINT "BLACK:";KC,KC/TC 780 GOTO 110
When it runs, it looks like this:
The top line represents the avialable marbles in the bag. As one is chosen, I POKE it to 0, which appears as an inverted @ character.
Below are the three picked marbles. It looks like I forgot to erase the “chosen” line between cycles, so it’s showing three marbles when above only two have actually been selected. This does not impact the outcome, since I choose three, which overwrites the previous three, before looking to see if they match.
As you can see, I am quite a bit off from Steve’s 42% / 22% / 15% / 15% results. I need to let it run much longer.
Or, perhaps I just need to optimize this program and dramatically speed it up.
This sounds like a follow-up article.
Until next time…
Random thought of the day…
Recently, Steve Ostrom posted to the Facebook CoCo group a puzzle he was trying to work out:
It’s been a few years since I turned on my Coco, even though it’s still all set up. I came across an interesting probability problem a few days ago, so I fired up the Coco and wrote a program to calculate the approximate answers using a random number generator. I’ll work on the real probability calculations maybe when I get even more bored. So, here’s the problem: You have 15 marbles in a bag. 5 are red, 4 are blue, 3 are white and 3 are black. You pull out one marble at a time, without putting it back, until you have 3 marbles of the same color. What are the odds of getting 3 red marbles, of getting 3 blue marbles, of getting 3 white marbles or of getting 3 black marbles. I ran 100,000 iterations (took most of the night) and here are the rounded percentages I found. Red = 42.5%, blue = 27.5%, white and black are both 15.0%. Anyone else want to try? :-) Three cheers for the Coco and the ability to write quick programs !!Steve Ostrom (March 21, 2020 on the CoCo Facebook Group)
This started a discussion on the CoCo Mailing List about just how random the RND() function is in Color BASIC. This reminds me of one of my earliest computer encounters back around 1979-1980.
I was living in Mequite, Texas (near Dallas) and my next door neighbor’s mom worked for Texas Instruments. They had plenty of TI stuff at home (watches and such) as well as a TI-99 home computer. Due to the year, it must have been the original TI-99/4.
His mom has typed in a BASIC program that played a card game. My friend Kris explained that he knew all the cards it would pick because it did the same ones each time. My memory seems to recall him trying to explain that the computer did not do random numbers, so his mother programmed the card sequence. Looking back, maybe what he was explaining was that it did the same numbers each time.
He was, of course, very good at this card game because of this.
Years later, I was shown an amazing magic trick as a friend talked to me over the phone and had me type in things into my CoCo and he told me what the result would be. Turn on a CoCo (or load an emulator, even the online JS Mocha or Xroar versions), and type this in:
FOR A=1 TO 10:PRINT RND(0):NEXT
What do you get if you try that on a real CoCo 1, 2 or 3?
If I recall, the RND function was meant to be used like this:
I am quite sure he just had me type “PRINT RND(100)” and he could tell me the results each time.
The pseudo random number generator in Color BASIC will do the same sequence every time, so after a power up, any game that relied on RND would be as predictable as my friend’s TI-99/4 card game was.
You can “seed” the random number generator by giving it a negative number:
As you can see, the random number generator will reset and generate the same sequence of numbers based on the negative value you give it.
Ever play Mine Sweeper on Windows? It had an option to enter a number and replay the same game. I am guessing this just seeded the Microsoft random number generator in the same way, and allowed the randomly generated playfield to be the same each time you gave it the same seed number.
Seems we could do that on the CoCo too and have a random game that you could let others try, using the same sequence of randomness.
I did not understand this when I was first getting in to the CoCo, but I knew you were supposed to seed the generator doing something like:
In Extended Color BASIC, TIMER was an incrementing variable that started at 0 on power up, and then counted up 60 times a second (the screen refresh interrupt time) until it rolled over at 65535 (about 18 minutes).
Since the time it took to turn on a computer, load a program and type run would vary, using the negative value of the timer gave a different random seed each time. I suppose if you had a BOOT rom that would automatically start up and run something (like RGB-DOS’s AUTOEXEC.BAS), maybe it would be the same amount of time each time, but beyond that, this was a good way to make the predictable RND function a bit less predictable.
As suggested on the mailing list, you can “see” the randomness by plotting out the values, such as doing something like this:
10 PMODE 4,1:SCREEN 1,1:PCLS 20 PSET(RND(256)-1,RND(192)-1):GOTO 20
Or, to make that a tad faster, use HEX in that line 20 loop and add the double speed poke (for the CoCo 3, POKE 65497,0 is even faster):
10 POKE 65495,0:PMODE 4,1:SCREEN 1,1:PCLS 20 PSET(RND(&H100)-&H1,RND(&HC0)-&H1):GOTO 20
…then let it run for awhile. If it was truly random, eventually it should set every single dot on the screen. But, pseudo random number generators have patterns. There is a level of predictability in them which has been used by hackers to exploit “secure” Internet connections for many years.
Interesting. I never would have thought to try that back in the 1980s.
And, lastly, benchmarks! How fast is decimal versus hex for plotting on the screen?
10 PMODE 4,1:SCREEN 1,1:PCLS 15 TIMER=0:FOR A=1 TO 1000 20 PSET(RND(256)-1,RND(192)-1):NEXT 30 PRINT TIMER/60
10 PMODE 4,1:SCREEN 1,1:PCLS 15 TIMER=0:FOR A=1 TO 1000 20 PSET(RND(&H100)-&H1,RND(&HC0)-&H1):NEXT 30 PRINT TIMER/60
The decimal version reports 23.58 seconds. The hex version reports 20.1 seconds. Changing the numbers to variables can make it a tad faster:
10 PMODE 4,1:SCREEN 1,1:PCLS 11 X=256:Y=192 15 TIMER=0:FOR A=1 TO 1000 20 PSET(RND(X)-&H1,RND(Y)-&H1):NEXT 30 PRINT TIMER/60
19.23 seconds. I wonder if the &H1 is slower than a variable?
10 PMODE 4,1:SCREEN 1,1:PCLS 11 X=256:Y=192:O=1 15 TIMER=0:FOR A=1 TO 1000 20 PSET(RND(X)-O,RND(Y)-O):NEXT 30 PRINT TIMER/60
Hey hey! 18.61 seconds! I sure wish I knew all this back when I was writing BASIC programs in the 80s…
Oh, and FOR/NEXT is faster than a GOTO since it does not have to scan through lines to find where it is going. I think if I was going to let this run for a few hours, I might do this:
10 PMODE 4,1:SCREEN 1,1:PCLS 11 X=256:Y=192:O=1 15 FOR A=1 TO 2 STEP 0 20 PSET(RND(X)-O,RND(Y)-O):NEXT
That should be marginally faster than the GOTO 20 was.
I’ll tell you how it turns out tomorrow.
Well, it looks like RND does hit every location from 0 to 255. Here is what my screen looked like after running overnight:
I guess I need a better test to show the frequency that each value comes up. Sorta like Steve’s original project of randomly pulling colored marbles from a bag.
Until next time…
- 2020-04-01 – Added image of the Color BASIC Unravelled book section where the Easter egg is located.
Cathe Cita responded and provided a link to this cool article about the Microsoft BASIC ROM easter eggs:
I got curious, and wrote this:
10 REM MS-EGG.BAS 20 P$="ABCDEFGHIJKLMNOPQRSTUVWXYZ......!" 30 FOR L=&HBFEF TO &HBFE6 STEP -1 40 V=PEEK(L) AND &H3F 50 C$=MID$(P$,V,1) 60 PRINT HEX$(L);" ";HEX$(PEEK(L));" -> ";HEX$(V),C$ 70 NEXT
Now I’ve finally seen it with my own eyes.
Line 20 is just enough of the PETASCII characters that would get POKEd to the screen memory on the Commodore for our hidden message. (A is POKE x, 1 … B is POKE x,2, etc.)
Line 30 loops through the memory locations in the Color BASIC ROM where the easter egg is stored, just after a numeric table. And stored in reverse.
Like 40 uses PEEK to get the byte at that location, then uses AND to mask off the top 2 bits, which were added (“randomly?”) to obfuscate the message so you couldn’t see in the ROM binary.
Line 50 gets the corresponding character from the P$ that matches the value of the PEEK’d byte.
Line 60 prints out the memory location, then the original value stored there, then the “mask off the top two bits” value, and then the character it represents.
For those curious, here is what those bytes look like in the ROM. This screen shot is from the Color BASIC Unravelled book.
Until next time…
See also: part 1
My original “nicer to read, slower to run” version took 25.28 seconds to run. Here are some things that can speed it up.
RENUMber by 1
Renumbering the program by 1 can save program memory (since “GOTO 77” takes less space than “GOTO 1500”, and if the program is large enough, you get a slight speed increase since it’s also less work to parse shorter line numbers. But this program is too small for this to matter.
Remove comments / avoid comments at the end of lines
Removing comments, in general, is a good way to make a program smaller (and thus, a bit faster since BASIC will have less lines to skip). But, as I found out, comments at the end of lines are a real problem, since BASIC cannot just skip the entire line like it can if the line starts with a comment:
10 X=42:REM SET X TO THE ANSWER.
10 REM SET X TO THE ANSWER.
The second version is faster, since even though BASIC has to scan an extra line, it can immediately skip it, while the first example must be parsed, character-by-character, to find the end of that line. But for this example, this did not help. It would have, if there were more GOTOs to earlier line numbers, since they would have to parse through a few lines with end comments, which would be slower. Since our looping was done with a FOR/NEXT, we didn’t have to do that.
Remove unnecessary spaces
Spaces are great for readability, but bad for code size and speed. There are only a few times when a space is needed, such as when BASIC can’t tell if the next character is a keyword or part of a variable:
10 IF A=B THEN 100
BASIC must have the space between the variable and the keyword THEN, else the parser isn’t sure if the variable is “B” or “BT” followed by the characters HEN. BUT, if it’s a number, it can figure it out:
Removing all other spaces reduces the amount of code BASIC has to parse through. My program had plenty of spaces. Removing them in this tiny program only reduced it to 25.15 seconds, but it can have a bigger impact on larger programs.
This one is easy. The less lines BASIC has to scan through, the quicker it will run. But, there are exceptions to this. Any time BASIC has to do more work at the end of a line, such as checking an ELSE, if that condition is not satisfied, BASIC has to scan forward character-by-character to get to the end of the line:
10 IF X=42 THEN 100 ELSE PRINT "I DON'T KNOW THE ANSWER"
Above, if X is 42, the ELSE code will not be ran. BUT, before the program can find 100, it has to scan through all the rest of the bytes in that line to find the end of it, then it continues scanning a line at a time looking for 100. Based on line lengths, sometime it might be faster to do:
10 IF X=42 THEN 100
20 IF X<>42 THEN PRINT "I DON'T KNOW THE ANSWER"
Combining lines in the program reduced the speed slightly to 25.13 seconds. The more code, the more you save by doing this (and it makes the program smaller, since every line number takes up space).
NEXT without variable
Unless you are doing really weird stuff with FOR/NEXT loops, it’s faster to leave off the variable in the NEXT. For example:
10 FOR A=1 TO 10:NEXT A
…will be slower than:
10 FOR A=1 TO 10:NEXT
NEXT without a variable will go to the most recent FOR anyway, so this works fine:
10 FOR A=1 TO 10
20 FOR B=1 TO 20
30 PRINT A,B
For this program, just removing the variables from NEXT A and NEXT Z increased the speed to 24.66 seconds.
Use HEX instead of decimal
It takes much more work to convert base-10 (i.e., “normal”) decimal numbers than it does to convert base-16 (hex) values. Every time a number is encountered, time can be saved by simply changing the number to a HEX — even though a hex digit has to start with “&H”. It will make the program larger, since 15 takes less bytes than &HF. For this example, it reduced time down to 21.66 seconds!
If you have to PRINT a generated string more than once, you are better off pre-rendering that string first so it only gets generated one time. This includes using things like STRING$, CHR$, etc. For example:
10 PRINT CHR$(128);"HELLO";CHR$(128)
Each time BASIC has to print that, it allocates a new string a few times and copies things over. See my string theory article for more details. For this program, I use STRING$() to generate a repeating block of characters. But, since it’s in a loop to print the block, it generates the same string over and over for each line of the block. Instead of this:
10 FOR A=1 TO 10
20 PRINT STRING$(10,128)
30 NEXT A
…I could generate that string ahead of time and just print it in the loop:
10 FOR A=1 TO 10
20 PRINT LN$
30 NEXT A
In this program, doing this trick reduces the time to 20.98 seconds! String are slow, you see…
The time savings so far…
Combining all of these (the ones that matter, at least), reduces time taken down to 18.45 seconds – saving 6.83 seconds overall.
Here is what it looks like, with the stuff outside of the benchmark timing loop left alone:
0 REM scale.bas 10 SW=32/4 ' SCALE WIDTH 20 SH=16/3 ' SCALE HEIGHT 30 SM=.1 ' SCALE INC/DEC 40 S=.5 ' SCALE FACTOR 70 TM=TIMER:FOR Z=1 TO 100 80 W=INT(SW*S):H=INT(SH*S):P=&HF-INT(W/&H2)+(&H7-INT(H/&H2))*&H20:LN$=STRING$(W,&HAF):CLS:FORA=&H1 TOH:PRINT@P+A*&H20,LN$:NEXT:S=S+SM:IFH<&H1 ORH>&HF THEN SM=-SM:S=S+(SM*&H2) 170 NEXT 180 ' 60=NTSC 50=PAL 190 PRINT:PRINT (TIMER-TM)/60;"SECONDS"
If we’d done any GOTO/GOSUBing to earlier lines, that would have to scan through those lines with ending REMarks, so cleaning all that up would be useful. If this was a larger program, RENUMbering by 1s might also squeak out a bit more speed.
But wait! There’s more!
I originally wrote this follow-up the evening that I wrote the original article. However, several folks have contributed their own optimizations. Let’s see what they did…
First, we go to the comments of the original article…
George Phillips – inner loop optimization
My current best effort is going full strength reduction on the inner loop. Replace lines 120 to 170 with:
The STRING$ requires Extended Color Basic which starts at a baseline of 20.4 seconds and those changes get it down to 13.George Phillips
George pre-rendered the string, removed spaces, and vastly sped things up by simplifying the math. On Xroar, it reports 16.15 – faster than any of my efforts. Nice job!
I took his updated code and converted the integers to HEX and that dropped it to 15.1 seconds! Even faster.
Jason Pittman – hex and start row calculation
Interestingly, changing the blue square value on line 130 to hex (“&HAF”) saves about 11.5% for me (27.55 to 24.33). Then, moving the row start calculation out of the print statement takes it down to 22.87.
120 FOR A=32 TO H*32 STEP 32Jason Pittman
140 NEXT A
Ah, the magic of hex! Jason did a similar trick with the row calculation. I tried Jason’s updates, and it was 20.28 on my Xroar. George is still in the lead.
Adam – DIM, reductions and variable substituion
So I was able to cut out 5 seconds doing the following:
– DIM all variables
– Adding variables for constants (2, 32, etc.) for lines 130, 160.
– Removing the math in lines 10, 20.
– Removing the variables after NEXT.
– Removing all remarks.
No change to the original algorithm or PRINT line.
Coco3 (6309): Original code = 27.15s. Optimized code 22.13s.
With high speed poke: 11.05s. :D
I’m intrigued with the post that mentioned using HEX values. I may have to try that.Adam
Ah! Using a variable rather than a constant can be significantly faster. I didn’t think about that, even though I’ve previously benchmarked and tested that. He adds:
I just put the STRING$ statement into a variable as suggested by George Phillips. That took out another 2 seconds! I also ran RENUM 1,,1. Run time is down from 27.2 s to 20.1 s.
It looks like George has gotten us all thinking. I don’t have Adam’s code to test, but I am going to experiment with replacing the constants with variables and see what that does.
Ciaran Anscomb – uh… not really sure!
The author of Xroar chimes in:
So I cheated and read the comments on your page and combined some of those techniques (and replace /2 with *1/2, forgot about that…).
13.1s? Not quite double speed, but not bad :)
0 REM scale.bas
1 GOSUB100:TM=TIMER:FORZ=1TO M:CLS:A$=STRING$(E-INT(W*B),L)+STRING$(W,C):PRINT@(8-INT(H*B))*L,””;:FORA=1TOH:PRINTA$:NEXT:IFH<1ORH>=&H10 THENQ=-Q:R=-R
3 ‘ 60=NTSC 50=PAL
4 T=TIMER:PRINT:PRINT (T-TM)/60;”SECONDS”
110 I=32/4 ‘ SCALE WIDTH
120 J=16/3 ‘ SCALE HEIGHT
130 D=.1 ‘ SCALE INC/DEC
140 S=.5 ‘ SCALE FACTOR
Ciaran reorganized the code, placing initialization at the end. This is a habbit I need to get into since GOTO/GOSUB to an earlier line number have to start at the FIRST line of the program and scan forward EVERY TIME. Code that only needs to run once, if placed at the top, slows down every GOTO to an earlier line number.
I just ran this and got 13.13 seconds!
There is alot to dissect here. Numbers are replaced with variables. Stuff is pre-calculated. And he’s using a variable for the end of a FOR/NEXT, for some reason. I’m going to have to dig in to that one.
Twice as fast, indeed!
I feel like there were a few more responses, but I can’t locate them at the moment, so maybe there will be a second attempt article down the line.
Until next time, kudos to all of you for giving me new things to consider — especially Ciaran with that amazing improvement. Very impressive.