Liberty Basic is develeopped by Carl Gundel Original Newsletter compiled by Alyce Watson and Brosco Translation to HTML: Raymond Roumeas
This issue is devoted to two topics:
I will be using the RNG as my example for producing the reusable code, so first we will describe what a RNG isUntil you have the need for one, its hard to imagine why anyone would even want a RNG. Well, it is commonly used in games programs, simulating rolling a dice or shuffling a deck of cards. Its also used in business. When an accountant audits a company's books, they rarely look at every entry - particularly with large companies. Can you imagine how many auditors would be required to check Microsoft's accounts if every entry was to be scrutinized? So they use a RNG to get a representative sample from the books and just audit those.
You can use a RNG in two ways. The first is to let it provide a purely random set of numbers. The second is when you want to be able to repeat a specific set of random numbers. Why would you want to repeat a set of random numbers you ask? Have you every noticed some games give you the option of 'playing the same game again' or 'play game number xxxxx'?
This is common in games of Solitaire - and the 'xxxxx' is a starting point for the RNG. For this value - the cards will always be shuffled into the same sequence. This starting point is refered to as a SEED.
This algorythm for a RNG was posted by Omar Sigurdsson on the LB4ALL Message Board. Thanks Omar.
"Here is a simple random number generator that gives numbers distributed uniformly over the interval A to B and repeats itself for the same seed.
X(n+1) = ( C1 * X(n) + C2 ) mod C3 where X(0) = seed number (0 <= seed <= 199017) C1 = 24298 C2 = 99991 C3 = 199017
The algorythm requires that you provide a SEED, to produce the first number, and that number produced is also the Seed for the next, and so on. The code to perform this in Liberty Basic is:
Seed = 100 C1=24298 C2=99991 C3=199017 SeedTmp = C1 * Seed + C2 Seed = SeedTmp - int( SeedTmp / C3 ) * C3 ' *** Note 1 RN = Seed / C3 DiceThrow = int(RN*6) + 1 ' *** Note 2 print DiceThrow ' *** Note 1 ' The result of the calculation is stored to be the new SEED for the next calculation ' ' *** Note 2 ' The RNG returns a number between 0 and 1, so to convert to a Throw of the Dice, we ' multiply by 6 and add 1. If we wanted to pick a card from a deck of playing cards ' the code would be: Card = int(RN*52) + 1
Because I have set Seed = 100, this code will produce the same set of numbers every time. If we want to get a different set of random numbers, we need to change the value of Seed. If we want to get different random numbers every time that we run the program, we must find a way of setting Seed to a random value. We can do this in a number of ways.
First, we could get the TimerTicks from the CPU and just use the milliseconds portion - that would give us a reasonably random number. Another method is to use Liberty Basic's RND function. RND is a RNG, but unfortunately, its not a very good one. In fact, in the documentation it is stated that it is really an Arbitary Number Generator. What's the difference?
If I use a RNG to simulate rolling 2 dice 10000 times, I should get a result where each number (one thru six) comes up roughly the same amount of times. Plus, it should also throw a double one sixth of the times. With the LB Rnd fuction, the results vary too much from what would happen in the real world, and the players of the game would say that the game is RIGGED! In fact, some of the older Arcade games were avoided for exactly this reason. They did not use a good RNG that simulated a real world result.
With the tests I've done with the algorythm posted by Omar, I got good results for all combinations that I tried.
Back to the code. Here's the code to SEED our RNG with a random number at the start. Plus, I've made it into a subroutine so that you can call it from anywhere in your program.
Seed = rnd(1) * 199017 gosub [RandomN] DiceThrow = int(RN*6) + 1 ' *** Note 2 print DiceThrow input var$ [RandomN] C1=24298 C2=99991 C3=199017 SeedTmp = C1 * Seed + C2 Seed = SeedTmp - int( SeedTmp / C3 ) * C3 RN = Seed / C3 return
You could take this code and paste it into your programs, but you need to be careful that the variable names I use, aren't also used somewhere in your program - otherwise you may get unexpected results.
To avoid this problem, I try to write subroutines in such a way that they can be 'cut and pasted' directly into my new program without fear that the subroutine may corrupt variables used by my program.
To achieve this I use a very simple naming convention. I've called this subroutine [RandomN]. Now, every variable that I use in the subroutine will have a name of 'RandomN.xxxxx'. Just check through my new code for the function - it does exactly the same as the previous example - I've just changed the variable names.
RandomN.Seed = rnd(1) * 199017 gosub [RandomN] DiceThrow = int(RandomN.RN*6) + 1 print DiceThrow input Var$ [RandomN] RandomN.C1=24298 RandomN.C2=99991 RandomN.C3=199017 RandomN.SeedTmp = RandomN.C1 * RandomN.Seed + RandomN.C2 RandomN.Seed = RandomN.SeedTmp - _ int( RandomN.SeedTmp / RandomN.C3 ) * RandomN.C3 RandomN.RN = RandomN.Seed / RandomN.C3 return
The period (fullstop) in the variable name has no special meaning, its just another valid character you can use when you are making up variable names. If this function had I needed a FOR....NEXT loop, I would have coded it like this:
For RandomN.i = 1 to ... .... Next RandomN.i
So now that we have a working subroutine that won't corrupt program variables, can we save it? NO! It may be 6 months or more before you require this routine. You will forget how it works, and what the Input and Output are of the function. You may also forget to SEED the RNG before issuing the GOSUB. This means that you will waste valuable time studying this code to see what variable names are used. So what we do now is add some comments at the start to describe each Function within the subroutine, and each of the Input and Output variables. We also embed the SEED code within the function.
gosub [RandomN] DiceThrow = int(RandomN.RN*6) + 1 print DiceThrow input Var$ ' Random Number Generator ' Functions ' [RandomN] Generates Random Numbers starting with the SEED value ' supplied in RandomN.Seed. If RandomN.Seed = 0 then ' a random seed will be selected. ' INPUT: RandomN.Seed value of 0 to 199017 to SEED the RNG. ' OUTPUT: RandomN.RN Returns a random value between 0 and 1. ' [RandomN] if RandomN.Seed = 0 then gosub [RandomN.SeedRNG] RandomN.C1=24298 RandomN.C2=99991 RandomN.C3=199017 RandomN.SeedTmp = RandomN.C1 * RandomN.Seed + RandomN.C2 RandomN.Seed = RandomN.SeedTmp - _ int( RandomN.SeedTmp / RandomN.C3 ) * RandomN.C3 RandomN.RN = RandomN.Seed / RandomN.C3 return [RandomN.SeedRNG] RandomN.Seed = rnd(1) * 199017 return
Now I add one more thing. I put some code at the start that demonstrates the function. So the function will now run as a standalone program and I an 'remind' myself about the way that it works. I can also modify the parameters here and test that it will work the way that I need in my new program. You can, of course, remove this part of the code when you are 'pasting' it to your own program. You could remove the comments as well, but I would recommend that you leave them there.
' Test Random Number Generator routine. for i = 1 to 10 gosub [RandomN] DiceThrow = int(RandomN.RN*6) + 1 print DiceThrow next i input Var$ ' Random Number Generator ' Functions ' [RandomN] Generates Random Numbers starting with the SEED value ' supplied in RandomN.Seed ' INPUT: RandomN.Seed value of 0 to 199017 to SEED the RNG. If a ' value of 0 is given, the LB Rnd function is ' called to give a Random Seed. ' OUTPUT: RandomN.RN Returns a value between 0 and 1. ' [RandomN] if RandomN.Seed = 0 then gosub [RandomN.SeedRNG] RandomN.C1=24298 RandomN.C2=99991 RandomN.C3=199017 RandomN.SeedTmp = RandomN.C1 * RandomN.Seed + RandomN.C2 RandomN.Seed = RandomN.SeedTmp - _ int( RandomN.SeedTmp / RandomN.C3 ) * RandomN.C3 RandomN.RN = RandomN.Seed / RandomN.C3 return [RandomN.SeedRNG] RandomN.Seed = rnd(1) * 199017 return
So, the above is the code that you can save in your subroutine library. What we have done, is effectively extended the Liberty Basic language with another command. I will be providing more reusable subroutines in future issues of the newsletter.
When Release 2.0 of Liberty Basic is available, it is expected to include a facility for User Defined Functions. I imagine that functions written in the way described here will be easily converted.
Newsletter written by: Brosco. Comments, requests or corrections to: brosco@orac.net.au Translated from Australian to English by an American: Alyce Watson. Thanks Alyce. Assistance with the RNG code by: Omar Sigurdsson. Thanks Omar.