gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Beginners > roll dice code please?

#131498 - ggkfc - Sat Jun 16, 2007 9:05 am

can anyone post up the whole code for making a proggy using palib where it just says "roll dice" on the top and "you rolled" + a rand(1-6) number and refreshes everytime you proess (A)
thnx

#131500 - felix123 - Sat Jun 16, 2007 10:16 am

What you just described is the perfect first program for a beginner DS programmer. You can probably do it with less than 5 lines.
_________________
Nintendo DS homebrew on Wikipedia

#131540 - ggkfc - Sun Jun 17, 2007 2:40 am

yea im so noob so don't even know how to do it. mind posting up the code?

#131541 - kusma - Sun Jun 17, 2007 2:51 am

Code:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   printf("roll dice.");
   int value = int(float(rand()) * (6.0 / RAND_MAX));
   printf("you rolled: %d\n", value);
   return 0;
}

ish.

#131544 - gmiller - Sun Jun 17, 2007 3:43 am

Of course the last code has nothing to do with doing this on a GBA or DS ..

#131557 - keldon - Sun Jun 17, 2007 8:30 am

What are you trying to achieve? You seem to be starting out at the wrong place - programming is not hard in any way. Anyone can learn to program, it's just like instructing someone to go to the shop.

Example: I want you to go to the shop and buy me a pack of cigarettes. I want my brand to be Benson and Hedges, if they don't have that brand then buy me Rothman's; and if that fails then buy me some orange juice.

That is just like a program, you won't get any orange juice unless the shop is out of B&H and Rothman's. All you're doing in programming is writing these instructions.

#131558 - Optihut - Sun Jun 17, 2007 10:52 am

gmiller wrote:
Of course the last code has nothing to do with doing this on a GBA or DS ..


Also, 6 divided by RAND_MAX is 0, so value always comes up as zero as well.

I also would like to know what the aim of the question is. Surely it must be easier to learn by doing than to learn by looking at other people's code (although that can help, of course. Especially when you're stuck.).

#131561 - kusma - Sun Jun 17, 2007 12:18 pm

Optihut wrote:
Also, 6 divided by RAND_MAX is 0, so value always comes up as zero as well.

Yes, but 6.0 divided by RAND_MAX, like the code I posted uses, isn't 0. It depends on the rand-implementation, and is a small factor that maps random numbers from 0..RAND_MAX to 0..6. There's issues in the code (6 virtually never happens because the int-cast floors - instead the code should use (7.0 - 1e-10) / RAND_MAX or something, and ofcourse, the random seed stays constant, so every time you run the program you get the same pseudo-random number), but your explanation sure does not touch any of them ;)

#131562 - kusma - Sun Jun 17, 2007 12:22 pm

gmiller wrote:
Of course the last code has nothing to do with doing this on a GBA or DS ..

Sure it does, but it can't just be compiled and run on any of them without modifications. You'd have to set up a console etc first. I was trying to show how this usually is done, since he said he had no idea. Sure, it doesn't use PA_lib like he requested, but I can't help him with that, since I'm not a PA_lib-user. Then again, I'm quite sure he can find PA_lib code for setting up whatever is needed for some text-plotting quite easily somewhere else.

#131564 - Optihut - Sun Jun 17, 2007 12:53 pm

kusma wrote:
Optihut wrote:
Also, 6 divided by RAND_MAX is 0, so value always comes up as zero as well.

Yes, but 6.0 divided by RAND_MAX, like the code I posted uses, isn't 0. It depends on the rand-implementation, and is a small factor that maps random numbers from 0..RAND_MAX to 0..6. There's issues in the code (6 virtually never happens because the int-cast floors - instead the code should use (7.0 - 1e-10) / RAND_MAX or something, and ofcourse, the random seed stays constant, so every time you run the program you get the same pseudo-random number), but your explanation sure does not touch any of them ;)


Well, actually it went like this: I compiled your code with dev-cpp and the output was 0. Since I was expecting a number in the range from 1 to 6, I looked at the code and thought "oh, it's because of the division."

It didn't occur to me that this was actually a random result and that it just happened to be always 0 due to the same seed being used. Thanks for the clarification.

#131567 - chishm - Sun Jun 17, 2007 1:20 pm

I might point out that 0 doesn't appear on a standard die. Use something like:
Code:
int value = 1 + int(float(rand()) * (6.0 / (RAND_MAX + 1)));

_________________
http://chishm.drunkencoders.com
http://dldi.drunkencoders.com

#131568 - kusma - Sun Jun 17, 2007 1:46 pm

chishm wrote:
I might point out that 0 doesn't appear on a standard die. Use something like:
Code:
int value = 1 + int(float(rand()) * (6.0 / (RAND_MAX + 1)));


Good point. And the "6.0 / (RAND_MAX + 1)"-trick is much neater than the lame epsilon I threw in in my second post. ;)

#131572 - tepples - Sun Jun 17, 2007 2:45 pm

kusma wrote:
And the "6.0 / (RAND_MAX + 1)"-trick is much neater than the lame epsilon I threw in in my second post. ;)

Unless RAND_MAX equals INT_MAX, in which case RAND_MAX + 1 equals -INT_MAX - 1 due to twos complement integer wraparound. Also, use of 'float' or 'double' values isn't the best policy on GBA or DS for reasons that will be explained later.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#131577 - Optihut - Sun Jun 17, 2007 2:54 pm

This is just a general question out of curiosity for kusma and chishm and anyone else who feels like chipping in, really:

If I understand it correctly, then rand() gives a random number from 0 to RAND_MAX. A printf of RAND_MAX shows that it's 32767 for my program, but for the sake of the argument, let's just take a number from 0 to 9.

In that case the distribution with kusma's code would be:

int value = int(float(rand()) * (6.0 / RAND_MAX));
0 - 0
1 - 0
2 - 1
3 - 2
4 - 2
5 - 3
6 - 4
7 - 4
8 - 5
9 ? 6

So the odds for 1, 3, 5 and 6 are 10% each and for 0, 2 and 4 they are 20%.

With Chishm's code, it would look like this:

int value = 1 + int(float(rand()) * (6.0 / (RAND_MAX + 1)));

0-1
1-1
2-2
3-2
4-3
5-4
6-4
7-5
8-5
9-6

Again the odds for 4 and 6 would be different from the odds for the rest of the numbers.

The actual distribution would vary with the increase of RAND_MAX, but the problem itself would persist.

So isn't it better to forget about RAND_MAX and use modulus and truncation ("rerolling" any result that's outside of the dice range of 1-6, while having equal odds for 1 to 6 to occur) in order to get an equal spread of the numbers?

Code:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
   time_t timer;
   time(&timer);
   srand((unsigned int)timer);
   printf("roll dice.");
   int value;
do{
   value = rand()%10;
}while (!(value>0 and value <7));
   printf("you rolled: %d\n", value);   
   return 0;
}


EDIT: I've just noticed a limitation of creating a single random number per runtime of the program. Apparently the timer based randomization of the seed seems to change only every second, so if you repeatedly run the program in one second, you get the same number.
The obvious solution to this is to have the program run for more than a second, perhaps by creating several pseudo random numbers in one go.

EDIT Number 2: Ok, so I've just realized that it's only an equal distribution in this case, because RAND_MAX of 32767 ends on a 7. If it were for example 32765, then the 6 would be slightly less likely to come up. Therefore the actual truncation needs to be done beforehand on the random number that is created and needs to be based on the value of RAND_MAX.

#131584 - kusma - Sun Jun 17, 2007 3:21 pm

Optihut wrote:

value = rand()%10;


This is generally discouraged, because the most significant bits are usually better (ie more random) than the least significant bits in most random algorithms. That's why the float-method are more used.

Now, the issue you point out is indeed true. Re-rolling is a solution, but with proper rand()-implementations the statistical errors should be neglectable. If you want better randomness, I'd rather use a better random-routine than the one provided by stdlib - it's usually not very good in the first place. The mersianne twister is well known for producing good results, so are other methods. But as always, if you can do without, then don't ;)

#131585 - Optihut - Sun Jun 17, 2007 3:34 pm

kusma wrote:
Optihut wrote:

value = rand()%10;


This is generally discouraged, because the most significant bits are usually better (ie more random) than the least significant bits in most random algorithms. That's why the float-method are more used.


Ah, I see. I was not aware of that. I thought the aversion to modulus stems from the problem of having an unequal distribution due to the modulus operation. For example, if I just do a 1+(rand()%6) to model a 6 sided die, then the later numbers will be slightly less likely to come up (hence the mod 10 and rerolling).

Good to know that it's a general issue with the algorithm, though.

#131586 - tepples - Sun Jun 17, 2007 3:40 pm

kusma wrote:
Optihut wrote:

value = rand()%10;

This is generally discouraged, because the most significant bits are usually better (ie more random) than the least significant bits in most random algorithms.

But it's easy to "temper" the distribution by mixing the upper bits into the lower bits. For example:
Code:
#define RANDMIXING_MAX 32767
int randMixing(void) {
  int r = rand();
  r = r ^ (r >> 16);
  return r & 0x7FFF;
}


Quote:
If you want better randomness, I'd rather use a better random-routine than the one provided by stdlib - it's usually not very good in the first place. The mersianne twister is well known for producing good results, so are other methods.

Mersenne Twister (MT19937) introduces a computation delay after every 624 elements of the sequence. This is OK if you can amortize performance but not if you have a worst-case CPU time ceiling, as many games have.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#131590 - sgeos - Sun Jun 17, 2007 4:39 pm

Optihut wrote:
value = rand()%10;

This is LCG (RNG) blasphemy. Don't do it. Ever.

Do something like this instead:
Code:
#define RNG_M      69069
#define RNG_C      1
#define RNG_MASK   0x3FFFFFFF
#define RNG_SHIFT   15

typedef unsigned long rng_t;

rng_t mRngState;

rng_t rng(void)
{
   mRngState *= RNG_M;
   mRngState += RNG_C;
   mRngState &= RNG_MASK;
   return mRngState;
}

// return from 0 to (V - 1)
int roll(int pRange)
{
   int result = rng() >> RNG_SHIFT;
   result *= pRange;
   result = result >> RNG_SHIFT;
   return result;
}

rng_t seed(rng_t pSeed)
{
   int oldSeed = mRngState;
   mRngState = pSeed & RNG_MASK;
   return oldSeed;
}

Note the use of RNG_SHIFT. That is the important bit. There are fancier ways to get things done.

tepples wrote:
Mersenne Twister (MT19937) introduces a computation delay after every 624 elements of the sequence. This is OK if you can amortize performance but not if you have a worst-case CPU time ceiling, as many games have.

Don't worry about it. Many commercial games use the Mersenne Twister and they probably do a whole lot more than whatever your homebrew app is going to do.

-Brendan

#131593 - Optihut - Sun Jun 17, 2007 4:54 pm

Quote:
Do something like this instead:


You're pulling my leg, right? That looks awfully complicated compared to what people posted on page 1.

I'm rather following kusma's advice and am reading up on Mersenne primes and the Mersenne twister (my head hurts) right now.

#131597 - sgeos - Sun Jun 17, 2007 6:53 pm

Optihut wrote:
You're pulling my leg, right?

Not at all. That is what a linear congruental generator looks like under the hood. That is all you should need. It has been tested and works.

Optihut wrote:
That looks awfully complicated compared to what people posted on page 1.

This is what a range function using rand() looks like. The code posted on the first page is a version of this with hard coded values. This function is untested and may require a cast to float somewhere.
Code:
int roll(int pMin, int pMax)
{
   int range = pMax - pMin + 1;
   return pMin + (int) (range * (rand() / (RAND_MAX + 1.0)));
}
The rand man page is good reading. man rand

Optihut wrote:
I'm rather following kusma's advice and am reading up on Mersenne primes and the Mersenne twister (my head hurts) right now.

This is a good idea. The Mersenne Twister is a far better RNG than rand() and the simple LCG I posted above. (This will not matter for a game.)

-Brendan