Find the answer to your Linux question:
Page 1 of 2 1 2 LastLast
Results 1 to 10 of 20
I'm working on an engine for Risk in C. I was wondering if someone could help me with the dice. If you're unfamiliar with Risk, there are two sets of ...
  1. #1
    Just Joined!
    Join Date
    Feb 2008
    Posts
    72

    simulating two sets of dice in C

    I'm working on an engine for Risk in C. I was wondering if someone could help me with the dice. If you're unfamiliar with Risk, there are two sets of dice. One of three, and the other of two. Two players each roll a set and the numbers determine who wins. The problem is, my dice aren't random enough. The set of three ALWAYS beats the other. Once in a while its a tie ("one each") but I've yet to see results that showed the set of two winning. The attacker's dice (the set of three) are almost always all higher than the defender's. Right now I'm just using the common pseudo-random number generator with time(). Can someone help me with a more random generator that will even out the results? You can download dice.c here and dice.h here.

  2. #2
    Just Joined!
    Join Date
    Mar 2008
    Posts
    20
    First of all, if you want an array of three ints, you need to declare it with [3] and not [2] (the number of ints in the array, not the index of the last int). As for your random numbers, here's the reason it's not working:

    You already know that if you set the seed to some constant (like 123 or something) then every time you run the program you'll get the same set of random numbers. This isn't just true for the whole program, but for each function. That is, every time you call the get_dice () function, if rand () has the same seed, it will return the same number. So right now, the attacker's rolls will be different from the defender's rolls because you reseeded rand () in between, but within those two sets, the rolls will not be random.

    What I usually do when I have a program that needs to call rand () more than once in the same second is this:

    Define a global int called randseed, and set it to 0.
    Seed rand () with time (NULL) + randseed.
    At the end of every single function that calls rand (), (but before the return statement, obviously) add randseed++.

    Now you'll never call the same function with the same seed, because every time an affected function is called, the seed changes.

  3. #3
    Linux Engineer wje_lf's Avatar
    Join Date
    Sep 2007
    Location
    Mariposa
    Posts
    1,192
    As a general rule, take a tip from the Perl book and use as a seed not just
    Code:
    time(NULL)
    but
    Code:
    time(NULL)+getpid()
    --
    Bill

    Old age and treachery will overcome youth and skill.

  4. #4
    Just Joined!
    Join Date
    Feb 2008
    Posts
    72
    I did what both of you guys said, and I'm still getting the same problem. The numbers are still different each time the program is run, but the attacker's are always higher. The links in the OP have been updated with the new source code.

  5. #5
    Linux Engineer wje_lf's Avatar
    Join Date
    Sep 2007
    Location
    Mariposa
    Posts
    1,192
    First, your comment preceding function getdice() says "Returns a pseudorandom number 0 through 6". The comment is accurate; that's what the code actually does. But why would you want to do that? You have a seven-sided die or something? You later seem to eliminate the resultant 0, but why even bother to get it? What's wrong with using a single-sided die in the first place, 1 through 6?

    Second, your program does not tend to deal out higher die values to the attacker than to the defender. I modified your code in dice.h as shown in red below:
    Code:
    #ifndef DICE_H
    #define DICE_H
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <time.h>
    
    // Attacker's dice
    static int a_dice[3];
    
    // Defender's dice
    static int d_dice[2];
    
    // Used to get a different seed every time 
    static int randseed = 0;
    
    // Set the seed for the dice
    void randomize() {
       srand(time(NULL) + randseed + getpid());
    }
    
    // Set the seed for the defender's dice
    /* void d_randomize() {
       srand(time(NULL) + randseed + getpid());
    } */
    
    // Returns a pseudorandom number 0 through 6
    int get_dice(char *who) {
       randomize();
       int n = rand() % 7;
       randseed++;
       printf("%s %d\n",who,n);
       return n;
    }
    
    // Rolls the attacker's dice.
    void attacker_rolldice() {
       a_dice[0] = get_dice("a");
       a_dice[1] = get_dice("a");
       a_dice[2] = get_dice("a");
        
       /* Since dice do not have a side 0, keep rolling until you get results with
          no zeroes */
       while (a_dice[0] == 0) {
          a_dice[0] = get_dice("a");
       }
       
       while (a_dice[1] == 0) {
          a_dice[1] = get_dice("a");
       }
       
       while (a_dice[2] == 0) {
          a_dice[2] = get_dice("a");
       }
    }
    
    // Roll the defender's dice.
    void defender_rolldice() {
       d_dice[0] = get_dice("d");
       d_dice[1] = get_dice("d");
       
       // Check for zeroes
       while (d_dice[0] == 0) {
          d_dice[0] = get_dice("d");
       }
       
       while (d_dice[1] == 0) {
          d_dice[1] = get_dice("d");
       }
    }
    
    /* Sort the dice from highest to lowest so we can tell who won. Bubble sort the
       attacker's dice and do a simple check and swap on the defender's. */
    void sort_dice() {
       int a_diceL = 3; // The length of a_dice[]
       int i;
       int j;
       int tmp;
       
       // Sort the attacker's dice
       for (i = 0; i <= 3; i++) {
          for (j = 0; j <= 3; j++) {
             if (a_dice[j+1] > a_dice[j]) {
                tmp = a_dice[j];
                a_dice[j] = a_dice[j+1];
                a_dice[j+1] = tmp;
             }
          }
       }
       
       // Sort the defender's dice
       if (d_dice[1] > d_dice[0]) {
          tmp = d_dice[0];
          d_dice[0] = d_dice[1];
          d_dice[1] = tmp;
       }
    }
    
    /* Determines the results of the dice. Returns 2 if the attacker loses 2
       units, 3 if the defender loses 2 units, and 4 if each player loses one
       unit. Returns 1 on error. */   
    int get_results() {
       // Used to count how many units are lost for the attacker and defender.
       int a_strikes = 0;
       int d_strikes = 0;
       
       // Sort the dice.
       sort_dice();
       
       /* Compare the attacker's highest two dice to the defender's dice to get
          the results. */
       if (a_dice[0] == d_dice[0]) {
          a_strikes++; // The defender always wins ties!
       }
      
       if (a_dice[1] == d_dice[1]) {
          a_strikes++;
       }
       
       if (a_dice[0] > d_dice[0]) {
          d_strikes++;
       }
       
       if (a_dice[1] > d_dice[1]) {
          d_strikes++;
       }
       
       if (a_dice[0] < d_dice[0]) {
          a_strikes++;
       }
       
       if (a_dice[1] < d_dice[1]) {
          a_strikes++;
       }
       
       // Count the strikes and return the results
       if (a_strikes == 2) {
          return 2;
       }
       
       if (d_strikes == 2) {
          return 3;
       }
       
       if (a_strikes == 1 && d_strikes == 1) {
          return 4;
       }
       
       // If we get this far, something wen't wrong
       return 1;   
    }
    
    #endif
    Then I recompiled the program and ran it several times. There seems to be no long-range bias toward giving the attacker higher values of the die roll.

    Third, it is customary to set the random seed only once per run of a program. You don't buy anything (and you might lose something if you do it wrong) by setting the seed more than once per run. Again, making the seed a function of the time and the process ID seems to work well for most applications.

    Fourth, it is customary to refrain from putting real code in a .h file. ("h" stands for "header".) Usually header files contain stuff that is not real code: macro definitions (of code or otherwise), other constant definitions, and most often function prototypes. As such, the header files are included by both the .c files which define the code in the functions which are declared in the header files, and any .c files which use those functions.

    Hope this helps.
    --
    Bill

    Old age and treachery will overcome youth and skill.

  6. #6
    Just Joined!
    Join Date
    Feb 2008
    Posts
    72
    What's wrong with using a single-sided die in the first place, 1 through 6?
    I dont know of a way to specify 1 through 6 at first, so I just got 0 through 6 and kept repeating the rolls until there were results with no zeroes, just like the comments say. There's no such thing as a single-sided die. The dice I'm using are six-sided. Six sides, one number per side = six numbers.

    Then I recompiled the program and ran it several times. There seems to be no long-range bias toward giving the attacker higher values of the die roll.
    I changed it to match what you posted, and the attacker is still getting higher numbers every time..

    Fourth, it is customary to refrain from putting real code in a .h file. ("h" stands for "header".) Usually header files contain stuff that is not real code: macro definitions (of code or otherwise), other constant definitions, and most often function prototypes. As such, the header files are included by both the .c files which define the code in the functions which are declared in the header files, and any .c files which use those functions.
    I'll keep that in mind and modify it accordingly. Thanks.
    Third, it is customary to set the random seed only once per run of a program. You don't buy anything (and you might lose something if you do it wrong) by setting the seed more than once per run.
    Well I have to at least set it twice, one for each set of dice. Otherwise, the defender's dice will be the same as the attacker's first two. I changed it to set the seed multiple times because that's what rufio suggested.

  7. #7
    Just Joined!
    Join Date
    Mar 2008
    Posts
    20
    Quote Originally Posted by wje_lf View Post
    Third, it is customary to set the random seed only once per run of a program. You don't buy anything (and you might lose something if you do it wrong) by setting the seed more than once per run. Again, making the seed a function of the time and the process ID seems to work well for most applications.
    Unfortunately, it doesn't work if you have a function that calls rand () which is called more than once a second, as I suspect his would be. I downloaded those files (your edited header, anyway), compiled and ran them without the randseed lines, and got:

    Code:
    a 3
    a 3
    a 3
    d 3
    d 3
    Attacker's dice:
    Die 1: 3
    Die 2: 3
    Die 3: 3
    Defender's dice:
    Die 1: 3
    Die 2: 3
    Attacker loses 2 units!
    then

    Code:
    a 1
    a 1
    a 1
    d 1
    d 1
    Attacker's dice:
    Die 1: 1
    Die 2: 1
    Die 3: 1
    Defender's dice:
    Die 1: 1
    Die 2: 1
    Attacker loses 2 units!
    Not random at all. (I had to take out the getpid () though, because for some reason gcc didn't recognize it. I don't think getpid () changes during runtime though.)

  8. #8
    Linux Engineer wje_lf's Avatar
    Join Date
    Sep 2007
    Location
    Mariposa
    Posts
    1,192
    I can think of a reason or two why the randomness disappeared entirely in your code.

    rufio, would you please post your entire code? Is this possible?
    --
    Bill

    Old age and treachery will overcome youth and skill.

  9. #9
    Just Joined!
    Join Date
    Mar 2008
    Posts
    20
    I just downloaded his dice.c file, copy-pasted your version of the dice.h file, and removed all mentions of randseed.

  10. #10
    Linux Engineer wje_lf's Avatar
    Join Date
    Sep 2007
    Location
    Mariposa
    Posts
    1,192
    Well, then, I can't explain it.

    I did as I assume you did: I took my edited version of dice.h (the one you started with, most likely) and removed completely every line which contained the string "randseed". That gave me this for dice.h:

    Code:
    #ifndef DICE_H
    #define DICE_H
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <time.h>
    
    // Attacker's dice
    static int a_dice[3];
    
    // Defender's dice
    static int d_dice[2];
    
    // Used to get a different seed every time 
    // static int randseed = 0;
    
    // Set the seed for the dice
    void randomize() {
       // srand(time(NULL) + randseed + getpid());
    }
    
    // Set the seed for the defender's dice
    /* void d_randomize() {
       // srand(time(NULL) + randseed + getpid());
    } */
    
    // Returns a pseudorandom number 0 through 6
    int get_dice(char *who) {
       randomize();
       int n = rand() % 7;
       // randseed++;
       printf("%s %d\n",who,n);
       return n;
    }
    
    // Rolls the attacker's dice.
    void attacker_rolldice() {
       a_dice[0] = get_dice("a");
       a_dice[1] = get_dice("a");
       a_dice[2] = get_dice("a");
        
       /* Since dice do not have a side 0, keep rolling until you get results with
          no zeroes */
       while (a_dice[0] == 0) {
          a_dice[0] = get_dice("a");
       }
       
       while (a_dice[1] == 0) {
          a_dice[1] = get_dice("a");
       }
       
       while (a_dice[2] == 0) {
          a_dice[2] = get_dice("a");
       }
    }
    
    // Roll the defender's dice.
    void defender_rolldice() {
       d_dice[0] = get_dice("d");
       d_dice[1] = get_dice("d");
       
       // Check for zeroes
       while (d_dice[0] == 0) {
          d_dice[0] = get_dice("d");
       }
       
       while (d_dice[1] == 0) {
          d_dice[1] = get_dice("d");
       }
    }
    
    /* Sort the dice from highest to lowest so we can tell who won. Bubble sort the
       attacker's dice and do a simple check and swap on the defender's. */
    void sort_dice() {
       int a_diceL = 3; // The length of a_dice[]
       int i;
       int j;
       int tmp;
       
       // Sort the attacker's dice
       for (i = 0; i <= 3; i++) {
          for (j = 0; j <= 3; j++) {
             if (a_dice[j+1] > a_dice[j]) {
                tmp = a_dice[j];
                a_dice[j] = a_dice[j+1];
                a_dice[j+1] = tmp;
             }
          }
       }
       
       // Sort the defender's dice
       if (d_dice[1] > d_dice[0]) {
          tmp = d_dice[0];
          d_dice[0] = d_dice[1];
          d_dice[1] = tmp;
       }
    }
    
    /* Determines the results of the dice. Returns 2 if the attacker loses 2
       units, 3 if the defender loses 2 units, and 4 if each player loses one
       unit. Returns 1 on error. */   
    int get_results() {
       // Used to count how many units are lost for the attacker and defender.
       int a_strikes = 0;
       int d_strikes = 0;
       
       // Sort the dice.
       sort_dice();
       
       /* Compare the attacker's highest two dice to the defender's dice to get
          the results. */
       if (a_dice[0] == d_dice[0]) {
          a_strikes++; // The defender always wins ties!
       }
      
       if (a_dice[1] == d_dice[1]) {
          a_strikes++;
       }
       
       if (a_dice[0] > d_dice[0]) {
          d_strikes++;
       }
       
       if (a_dice[1] > d_dice[1]) {
          d_strikes++;
       }
       
       if (a_dice[0] < d_dice[0]) {
          a_strikes++;
       }
       
       if (a_dice[1] < d_dice[1]) {
          a_strikes++;
       }
       
       // Count the strikes and return the results
       if (a_strikes == 2) {
          return 2;
       }
       
       if (d_strikes == 2) {
          return 3;
       }
       
       if (a_strikes == 1 && d_strikes == 1) {
          return 4;
       }
       
       // If we get this far, something wen't wrong
       return 1;   
    }
    and, to review, dice.c looked like this:
    Code:
    #include <stdlib.h>
    #include "dice.h"
    
    int main() {
       attacker_rolldice();
       defender_rolldice();
       sort_dice();
       
       printf("Attacker's dice:\n");
       printf("Die 1: %i\n", a_dice[0]);
       printf("Die 2: %i\n", a_dice[1]);
       printf("Die 3: %i\n", a_dice[2]);
       
       printf("Defender's dice:\n");
       printf("Die 1: %i\n", d_dice[0]);
       printf("Die 2: %i\n", d_dice[1]);
          
       int results = get_results();
       if (results == 2) {
          printf("Attacker loses 2 units!\n");
          return 0;
       }
       
       if (results == 3) {
          printf("Defender loses 2 units!\n");
          return 0;
       }
       
       if (results == 4) {
          printf("One each!\n");
          return 0;
       }
       
       if (results == 1) {
          printf("There was an error getting the results :(\n");
          return 0;
       }
       
       // We shouldn't get this far..
       return 1;
    }
    Then I compiled it thus:
    Code:
    gcc dice.c -o dice
    I ran it twice and got first:
    Code:
    a 1
    a 4
    a 2
    d 5
    d 1
    Attacker's dice:
    Die 1: 5
    Die 2: 4
    Die 3: 2
    Defender's dice:
    Die 1: 1
    Die 2: 1
    Defender loses 2 units!
    and then:
    Code:
    a 1
    a 4
    a 2
    d 5
    d 1
    Attacker's dice:
    Die 1: 5
    Die 2: 4
    Die 3: 2
    Defender's dice:
    Die 1: 1
    Die 2: 1
    Defender loses 2 units!
    Please note three things.

    The first is that in my runs the results are both the same. But you don't want them to be. The standard way to avoid that is to call srand() exactly once, and a good way to do that is to use as the parameter to srand() the time plus the value of getpid(). Exactly once, at the beginning of the application, before you do the first rand() call.

    The second is that in your runs the results are different from one run to the next. I can't explain that, assuming that you compiled the code exactly as I have it above. It seems impossible to me.

    The third is that within each of your runs, each die toss ends with the same result. Again, I can't explain this; it shouldn't be happening that way, again assuming that you compiled the code exactly as I have it above.
    --
    Bill

    Old age and treachery will overcome youth and skill.

Page 1 of 2 1 2 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
...