(Oops, I know your name is "Steve". I don't know why I typed "Steven".)

On Tue, Feb 13, 2024 at 12:53 AM B 9 <[email protected]> wrote:

> Thanks for the walkthrough, Steven! While adding in the fast counters
> wouldn't hurt (probably), I don't think Dice Box needs algorithmic
> improvement now that I understand how it works.
>
> First, it's pretty clever to get more randomness from the users at each
> dice roll (`ZX`), not kludgy at all.
>
> Second, you used the extra randomness in the right way. My first instinct
> would have been to reseed the Random Number Generator (RNG) as Anderson
> suggests — using `POKE` or with `RND(-`_x_``)` — but that would have
> actually made the dice rolls *less* uniformly distributed because it
> depends upon the distribution of your time measurements. (Seeding an RNG
> should only be done once in a program.)
>
> My second instinct would have also been wrong: modify the result from
> `RND(1)` algebraically or using modulo arithmetic. Again, that would skew
> the distribution unless extremely carefully programmed. You simply used the
> time interval to skip ahead in the RNG by a few steps, which is perfectly
> valid and leverages Bill Gates' hard work of building a mathematically
> correct RNG.
>
> The only algorithmic improvement I can think of would be to use Lloyd's
> method of calling RND(1) in a loop with `INKEY$()` on the splash screen.
> That would (perhaps) help as it increments the RNG by about 80 steps per
> second, giving more possible starting states for the first die roll than
> measurements in seconds.
>
> In fact, you could skip measuring seconds all together if you replaced
> `INPUT$(1)` with a loop like,
>
>    Q=RND(1): IF INKEY$="" GOTO 10
>
> —B
>
> P.S. If you do start fixing up Dice Box, one feature which I'd personally
> use would be the option for a "simple mode" instead of picking a "system".
> I imagine a prompt where one could type "d20" to roll a twenty-sided die,
> "6d6" to roll 6 six-sided dice, or "d1" to flip a coin. (Hitting enter
> would roll the same dice again until something different is typed. Q to
> quit.) Or maybe it would be menu driven so one could pass the Model T to a
> player and ask them to press a key to roll the dice. The exact time they
> hit the key determines the result.
>
>
> On Mon, Feb 12, 2024 at 1:07 PM Steve Baker <[email protected]>
> wrote:
>
>> Greetings again!
>>
>> Thanks for your note (below, last week) about my RPG dice roller program
>> and for the insightful follow-up about the Model T randomizer capabilities!
>> I am not a particularly well-versed programmer and undertook the dice
>> project to (a) goof around with the Model T, (b) familiarize myself with
>> how to code it, and (c) to have something cool to take with me on game
>> nights! Through that lens, I had a lot of fun and was able to accomplish
>> what I set out to do.
>>
>> As you suspected, I did indeed find my inspiration for the randomizer
>> function from the excellent "Programming Tips, Peeks, and Pokes” book by
>> Tony Anderson; the specific section that is most relevant here is Chapter
>> 13, document pages 29-31 (in the PDF, it is pages 31-33):
>>
>>
>> https://archive.org/details/ProgrammingTipsPeeksAndPokesForTheTandyPortableComputers/page/n31/mode/2up
>>
>>
>> In particular, this paragraph was helpful for me (from around the middle
>> of page 30):
>>
>> You can "reseed" the random number generator yourself at the beginning of
>> the program by asking for the user to choose a number between 1 and 100, or
>> by using the seconds digits from the clock, or by reading one of the two
>> running counters in high memory, then poking the value into the memory seed
>> location at 64634 (100 and 102) or 63277 (200).
>>
>>
>> My approach only extended this by reseeding things based on the clock’s
>> seconds digits when the user interacted with the app. (In hindsight, I
>> could’ve explored those two running counters in high memory instead of, or
>> in addition to, the seconds digits). My loose logic was that they’d likely
>> be unable to pull off two dice rolls within the same second, as it takes
>> them a bit of time to choose what to roll, watch the results, do whatever
>> they’re going to do with the results (e.g., playing RPGs), etc.
>>
>> Here’s the current version of my DICE.DO source code:
>>
>>
>> http://www.club100.org/memfiles/index.php?action=downloadfile&filename=DICE.DO&directory=Steve%20Baker&;
>>
>>
>> Let’s start with the important bits from the above source code that show
>> how I chose to leverage the tips from Tony’s book. Basically it’s a
>> three-part play:
>>
>> 1. First, a one-time step to setup a variable ZZ with the current seconds
>> digits when the code first runs
>>
>> 2. As the user interacts with the program, it resets a second variable ZY
>> to the current seconds digits — my assumption was that ZY will be different
>> from ZZ at least most of the time, as there’s of course a chance it could
>> coincidentally be the same seconds digits as ZZ on occasion.
>>
>> 3.  Finally, a third variable ZX fetches the current seconds digits again
>> when we’re actually rolling a dice — it resets every time a dice is rolled,
>> which could be several times (for instance, a 3d20 request triggers this
>> routine three separate times to roll the d20 dice once each time, etc.)
>>
>> So, my use of the randomizer is three-deep, in that we have ZZ that is
>> set once, ZY that is set when they use the menu, and ZX that is set with
>> each dice roll. I put the code the does the heavy lifting toward the front
>> of the program so it’s faster to access during runtime, etc. The following
>> elaborates on those three steps with some actual code snippets.
>>
>> * First step — one-time initialization of variable ZZ (via the
>> “GOSUB8900" call)
>>
>> There’s a big chunk of variable initialization stuff at the end of the
>> code (I read that having the “only use once” initialization stuff at the
>> back of the program is more efficient). That chunk begins on line 8900.
>> First, ZZ gets the seconds digits from the clock when the app initializes:
>>
>> 8900ZZ=VAL(RIGHT$(TIME$,2)): … the other initialization stuff
>>
>> * Second step — setting variable ZY with each menu interaction (via the
>> “GOSUB45” call)
>>
>> When we search for “GOSUB45” in the code, we see that it is executed when
>> the user interacts with the menu, e.g. makes a selection from the set of
>> choices.
>>
>> 45ZY=VAL(RIGHT$(TIME$,2)):RETURN
>>
>> * Third step — the loop that sets variable ZX and uses the three variable
>> in a loop to actually pluck out a number (via the “GOSUB42” call)
>>
>> When we search for “GOSUB42” in the code, we see that it is executed for
>> each roll of the dice. In the subroutine, LZ is the loop index and Q is
>> used to pop the randomizer function to burn off some quantity of random
>> numbers (we don’t keep Q). The global variable DI is set before the roll
>> with the number of sides for the dice (e.g., DI=10 for a d10 dice, 12 for a
>> d12, etc.). The global variable RL is short for “roll” as in dice roll
>> result, and this subroutine modifies that variable with the resulting roll.
>>
>>
>> 42ZX=VAL(RIGHT$(TIME$,2)):FORLZ=1TO(ZX+ZZ+(RND(1)*ZY)):Q=RND(1):NEXTLZ:IFDI>1THENRL=INT(RND(1)*DI)+1:RETURN
>> 43IFRND(1)>=0.5THENRL=1:RETURNELSERL=0:RETURN
>>
>> … and line 43 is used for dice rolls that are either 1 or 0 as in true or
>> false or hit/miss (those absolute times when it is either yes or no). We
>> get to line 43 when the final if statement in line 42, where it looks to
>> see if DI is greater than one, fails. Put another way, when DI is set to 1
>> then we’re looking for a d1 roll. In this situation line 43 looks at the
>> random number and if it’s greater than or equal to 0.5 then it’s
>> yes/true/success/etc., and if it’s less than 0.5 then it’s no/false/failure.
>>
>> Line 42 is not particularly efficient and I figured that’s alright
>> because my use of the randomizer is heavily dependent on the seconds digits
>> as well on as the user themselves. My intent was not to intentionally stall
>> for time, so to speak, but “good" numbers over lighting-fast responses.
>>
>> One interesting thought is that by depending on the user’s interaction to
>> finalize the random number, it replicates the act of them shaking the dice.
>> At least to me, the vision of them influencing the selection of their
>> random number is like they are basically shaping their destiny, LOL.
>>
>> Looking back on all this with a few years’ perspective, perhaps hitting
>> those two running counters (maybe with grabbing the seconds digits a time
>> or two) might be simple and efficient. One of these days (years?) I may
>> revisit the code to streamline it a bit now that I know a lot more.
>>
>> Given all this, I’m sure there’s probably a way to instrument something
>> to test the RNG uniformity via chi squared or equivalent tests. Just not
>> sure where or how to put that into the code, given this kludgy three-part
>> algorithm is quite user-dependent (their actions trigger most of it).
>>
>> My apologies for the delay in this response (I’d forgotten about the
>> football stuff this past weekend and I ended up having plans both days).
>> Hopefully this helps a little bit at least? It was fun to revisit the code
>> after so long (well, four years at least) and the memories of writing it.
>>
>> Cheers and thanks,
>> Steve
>>
>> PS, I am still (very slowly) photographing and inventorying my T102s,
>> T200s, T600s, peripherals, and the like. to offer for sale to this group.
>> My hope is to sell fewer, larger lots of gear rather than one-offs, but
>> will be open to special situations and whatnot. As they say, stay tuned!
>>
>>
>>
>> --
>> Greetings from Steve Baker (he/him/his)
>> “Gravity brings me down…”
>>
>>
>>
>>
>> On Feb 8, 2024, at 8:08 PM, B 9 <[email protected]> wrote:
>>
>> On Tue, Dec 26, 2023 at 4:36 PM Steve Baker <[email protected]>
>> wrote:
>>
>> That was one of the hardest parts about writing that app — I did a lot of
>> research about tips and tricks, and have a few nested randomizer routines
>> in there to compensate. While I have not done a statistical study with a
>> significant sample set, it kicks out dice rolls that are very random, and
>> the folks that I play with feel comfortable with its results. The source
>> code is available and I’d be happy to highlight the key bits (some upfront
>> prep early in the code and most of the randomizer functions are towards the
>> back, etc).
>>
>> Steve,
>>
>> I was curious how nesting the randomizer had affected the quality of the
>> randomization, so I poked around at the source code for DICEBOX. While I
>> had a lot of fun trying to weave my way through the tightly crunched code,
>> in the end I wasn't able to extract just the randomizer. Could you help me
>> grok it? Once the routine is extracted, I think your RNG might be useful
>> for anyone else who wants to reuse your work.
>>
>> I've already written code that can do a check of RNG uniformity via the
>> χ² (chi squared) test on a Model T. (See attachment). It rolls a D sided
>> die N times and returns a probability p that the RNG is selecting every
>> number equally. I just need your routine at line 10000 which takes variable
>> D (number of sides) as input and outputs variable R (random integer from 1
>> to D, inclusive).
>>
>> —b9
>>
>> P.S. On the Tandy 200, DICEBOX needs a line 15 GOTO 100 or it gives an
>> error.
>>
>>
>> --- CUT HERE ---
>>
>> 0 GOTO 1000 REM RNG Tester by hackerb9
>> 1 DATA 0.000, 0.000, 0.001, 0.004, 0.016, 2.706, 3.841, 5.024, 6.635,
>> 7.879
>> 2 DATA 0.010, 0.020, 0.051, 0.103, 0.211, 4.605, 5.991, 7.378, 9.210,
>> 10.597
>> 3 DATA 0.072, 0.115, 0.216, 0.352, 0.584, 6.251, 7.815, 9.348, 11.345,
>> 12.838
>> 4 DATA 0.207, 0.297, 0.484, 0.711, 1.064, 7.779, 9.488, 11.143, 13.277,
>> 14.860
>> 5 DATA 0.412, 0.554, 0.831, 1.145, 1.610, 9.236, 11.070, 12.833, 15.086,
>> 16.750
>> 6 DATA 0.676, 0.872, 1.237, 1.635, 2.204, 10.645, 12.592, 14.449, 16.812,
>> 18.548
>> 7 DATA 0.989, 1.239, 1.690, 2.167, 2.833, 12.017, 14.067, 16.013, 18.475,
>> 20.278
>> 8 DATA 1.344, 1.646, 2.180, 2.733, 3.490, 13.362, 15.507, 17.535, 20.090,
>> 21.955
>> 9 DATA 1.735, 2.088, 2.700, 3.325, 4.168, 14.684, 16.919, 19.023, 21.666,
>> 23.589
>> 10 DATA 2.156, 2.558, 3.247, 3.940, 4.865, 15.987, 18.307, 20.483,
>> 23.209, 25.188
>> 11 DATA 2.603, 3.053, 3.816, 4.575, 5.578, 17.275, 19.675, 21.920,
>> 24.725, 26.757
>> 12 DATA 3.074, 3.571, 4.404, 5.226, 6.304, 18.549, 21.026, 23.337,
>> 26.217, 28.300
>> 13 DATA 3.565, 4.107, 5.009, 5.892, 7.042, 19.812, 22.362, 24.736,
>> 27.688, 29.819
>> 14 DATA 4.075, 4.660, 5.629, 6.571, 7.790, 21.064, 23.685, 26.119,
>> 29.141, 31.319
>> 15 DATA 4.601, 5.229, 6.262, 7.261, 8.547, 22.307, 24.996, 27.488,
>> 30.578, 32.801
>> 16 DATA 5.142, 5.812, 6.908, 7.962, 9.312, 23.542, 26.296, 28.845,
>> 32.000, 34.267
>> 17 DATA 5.697, 6.408, 7.564, 8.672, 10.085, 24.769, 27.587, 30.191,
>> 33.409, 35.718
>> 18 DATA 6.265, 7.015, 8.231, 9.390, 10.865, 25.989, 28.869, 31.526,
>> 34.805, 37.156
>> 19 DATA 6.844, 7.633, 8.907, 10.117, 11.651, 27.204, 30.144, 32.852,
>> 36.191, 38.582
>> 20 DATA 7.434, 8.260, 9.591, 10.851, 12.443, 28.412, 31.410, 34.170,
>> 37.566, 39.997
>> 21 DATA 8.034, 8.897, 10.283, 11.591, 13.240, 29.615, 32.671, 35.479,
>> 38.932, 41.401
>> 22 DATA 8.643, 9.542, 10.982, 12.338, 14.041, 30.813, 33.924, 36.781,
>> 40.289, 42.796
>> 23 DATA 9.260, 10.196, 11.689, 13.091, 14.848, 32.007, 35.172, 38.076,
>> 41.638, 44.181
>> 24 DATA 9.886, 10.856, 12.401, 13.848, 15.659, 33.196, 36.415, 39.364,
>> 42.980, 45.559
>> 25 DATA 10.520, 11.524, 13.120, 14.611, 16.473, 34.382, 37.652, 40.646,
>> 44.314, 46.928
>> 26 DATA 11.160, 12.198, 13.844, 15.379, 17.292, 35.563, 38.885, 41.923,
>> 45.642, 48.290
>> 27 DATA 11.808, 12.879, 14.573, 16.151, 18.114, 36.741, 40.113, 43.195,
>> 46.963, 49.645
>> 28 DATA 12.461, 13.565, 15.308, 16.928, 18.939, 37.916, 41.337, 44.461,
>> 48.278, 50.993
>> 29 DATA 13.121, 14.256, 16.047, 17.708, 19.768, 39.087, 42.557, 45.722,
>> 49.588, 52.336
>> 30 DATA 13.787, 14.953, 16.791, 18.493, 20.599, 40.256, 43.773, 46.979,
>> 50.892, 53.672
>> 40 DATA 20.707, 22.164, 24.433, 26.509, 29.051, 51.805, 55.758, 59.342,
>> 63.691, 66.766
>> 50 DATA 27.991, 29.707, 32.357, 34.764, 37.689, 63.167, 67.505, 71.420,
>> 76.154, 79.490
>> 60 DATA 35.534, 37.485, 40.482, 43.188, 46.459, 74.397, 79.082, 83.298,
>> 88.379, 91.952
>> 70 DATA 43.275, 45.442, 48.758, 51.739, 55.329, 85.527, 90.531, 95.023,
>> 100.425, 104.215
>> 80 DATA 51.172, 53.540, 57.153, 60.391, 64.278, 96.578, 101.879, 106.629,
>> 112.329, 116.321
>> 90 DATA 59.196, 61.754, 65.647, 69.126, 73.291, 107.565, 113.145,
>> 118.136, 124.116, 128.299
>> 100 DATA 67.328, 70.065, 74.222, 77.929, 82.358, 118.498, 124.342,
>> 129.561, 135.807, 140.169
>> 110 REM Calculate p from df and X2
>> 120 IF DF<1 THEN DF=1: PRINT "Using df =" DF
>> 130 IF DF>100 THEN DF=100: PRINT "Using df =" DF
>> 140 IF DF>30 AND (DF MOD 10) > 0 THEN DF=DF\10: PRINT "Using df =" DF
>> 150 ' Select a DATA line to read from
>> 151 IF DF=1 THEN RESTORE 1
>> 152 IF DF=2 THEN RESTORE 2
>> 153 IF DF=3 THEN RESTORE 3
>> 154 IF DF=4 THEN RESTORE 4
>> 155 IF DF=5 THEN RESTORE 5
>> 156 IF DF=6 THEN RESTORE 6
>> 157 IF DF=7 THEN RESTORE 7
>> 158 IF DF=8 THEN RESTORE 8
>> 159 IF DF=9 THEN RESTORE 9
>> 160 IF DF=10 THEN RESTORE 10
>> 161 IF DF=11 THEN RESTORE 11
>> 162 IF DF=12 THEN RESTORE 12
>> 163 IF DF=13 THEN RESTORE 13
>> 164 IF DF=14 THEN RESTORE 14
>> 165 IF DF=15 THEN RESTORE 15
>> 166 IF DF=16 THEN RESTORE 16
>> 167 IF DF=17 THEN RESTORE 17
>> 168 IF DF=18 THEN RESTORE 18
>> 169 IF DF=19 THEN RESTORE 19
>> 170 IF DF=20 THEN RESTORE 20
>> 171 IF DF=21 THEN RESTORE 21
>> 172 IF DF=22 THEN RESTORE 22
>> 173 IF DF=23 THEN RESTORE 23
>> 174 IF DF=24 THEN RESTORE 24
>> 175 IF DF=25 THEN RESTORE 25
>> 176 IF DF=26 THEN RESTORE 26
>> 177 IF DF=27 THEN RESTORE 27
>> 178 IF DF=28 THEN RESTORE 28
>> 179 IF DF=29 THEN RESTORE 29
>> 180 IF DF=30 THEN RESTORE 30
>> 181 IF DF=40 THEN RESTORE 40
>> 182 IF DF=50 THEN RESTORE 50
>> 183 IF DF=60 THEN RESTORE 60
>> 184 IF DF=70 THEN RESTORE 70
>> 185 IF DF=80 THEN RESTORE 80
>> 186 IF DF=90 THEN RESTORE 90
>> 187 IF DF=100 THEN RESTORE 100
>> 210 REM p value for columns in data
>> 220 A(0)=1
>> 230 A(1)=.995: A(2)=.990: A(3)=.975
>> 240 A(4)=.950: A(5)=.900: A(6)=.100
>> 250 A(7)=.050: A(8)=.025: A(9)=.010
>> 260 A(10)=.005
>> 300 REM X2 is chi square, M is alpha
>> 310 FOR T=1 TO 10
>> 320 READ M
>> 330 IF X2 > M THEN NEXT
>> 350 P=A(T-1)
>> 390 RETURN
>> 990 REM By hackerb9 February 2024.
>> 999 '
>> 1000 REM Chi Squared statistic to
>> 1001 REM check for uniformity of RNG.
>> 1002 ' Note: This does NOT check for
>> 1003 ' independence. For that, use
>> 1004 ' autocorrelation, gap, poker
>> 1005 ' or other "diehard" tests.
>> 1010 ES$=CHR$(27) 'Screen Esc sequences
>> 1020 PRINT ES$"U"; ' Turn off labels
>> 1030 CLS
>> 1038 ' d is number of histogram bins
>> 1039 ' or, equivalently, sides of a die.
>> 1040 D=6
>> 1050 DIM X(D)
>> 1059 ' n is number of times to roll.
>> 1060 N=100
>> 1069 ' is the count expected in a bin
>> 1070 E=N/D
>> 1099 '
>> 1100 REM Roll dice and show histogram bins.
>> 1120 PRINT"Rolling a";D;"sided die";N;"times";
>> 1130 FOR I=1 TO N
>> 1140 GOSUB 10000 ' R = random(D)
>> 1150 X(R)=X(R)+1
>> 1160 TT=(R-1)*4: GOSUB 1550
>> 1170 PRINT X(R);
>> 1180 NEXT I
>> 1190 TT=D*4:GOSUB 1550: PRINT
>> 1199 '
>> 1200 REM Calculate chi square statistic
>> 1201 ' Chi-Square = sum{(x(i)-E)^2 / E}
>> 1210 FOR I=1 TO D: IF X(I)<5 THEN PRINT "Results may be inaccurate due to
>> insufficient trials (n ="N", d ="D")": ELSE NEXT
>> 1230 X2=0: DF=-1
>> 1240 FOR I=1 TO D
>> 1250 X2 = X2 + ( (X(I) - E)^2 / E)
>> 1260 IF X(I) > 0 THEN DF=DF+1
>> 1270 NEXT I
>> 1280 PRINT "Chi square is";X2
>> 1290 PRINT "Degrees of Freedom is";df
>> 1299 '
>> 1300 REM Set p (percentage points) by
>> 1301 REM looking up x2 (chi square)
>> 1302 REM at df (degree of freedom) in
>> 1303 REM the chisq distribution table.
>> 1310 GOSUB 110
>> 1399 '
>> 1400 REM Interpret result
>> 1410 PRINT "p =" P*100 "%"
>> 1420 PRINT"This Random Number Generator"
>> 1430 IF P>0.95 THEN PRINT "is too regular. (BAD)": END
>> 1440 IF P<0.05 THEN PRINT "is not uniformly distributed. (BAD)": END
>> 1450 PRINT "seems uniformly distributed."
>> 1460 END
>> 1499 '
>> 1500 'Move cursor to Row AA and Col BB
>> 1510 PRINT ES$"Y"CHR$(32+AA)CHR$(32+BB);
>> 1520 RETURN
>> 1550 REM Move cursor to position TT
>> 1560 AA=TT\40+1:BB=TT MOD 40:GOSUB 1500
>> 1570 RETURN
>> 9999 '
>> 10000 REM RANDOM NUMBER GENERATOR
>> 10001 ' Input D, integer
>> 10002 ' Output R, integer
>> 10003 ' 1..D, inclusive
>> 10010 R=INT(D*RND(1))+1
>> 10090 RETURN
>>
>>
>>
>> <x2.ba>
>>
>>
>>

Reply via email to