Thread: Designing Data Structures for Dice Rolling

  1. #1
    Registered User
    Join Date
    Feb 2009
    Posts
    20

    Designing Data Structures for Dice Rolling

    For some games I eventually want to program/play in, I want to create a framework that will roll dice in a number of different ways. For my test, I want a polyhedral dice structure, chosen as six for simplicity. The first roll is simply rolling the die and recording the number. The second roll is somewhat like that used in White Wolf's Storyteller system. Specifically, the computer will roll the die and return 1 if a 5 or 6 was rolled, -1 if a 1 was rolled, and 0 otherwise. The basic die structure I have is:

    Code:
    struct Basic_Die        //state must be initialized before use
    {
            unsigned num_sides;
            //unsigned (* roll_die) (struct Basic_Die *this, void * roll_params);   //different rolling schemes
            signed (* roll_die) (struct Basic_Die *this);
            signed last_rolled;
    };
    The commented line for "different rolling" is one solution to my problem. A simple roll is accomplished with (assuming it compiles):

    Code:
    signed int basic_roll(struct Basic_Die *this)   //changes state of *this
    {
            this->last_rolled = 1 + (rand() % this->num_sides);     //this is noted as being a bad idea, but I am using this method for simplicity
            return this->last_rolled;       //return new value
    }
    This code works well with just the number of sides on the die. However, I have realized this scheme will not suffice for the "Storyteller" die roll.

    Code:
    signed int ww_roll_p5(struct Basic_Die *this)   //changes state of *this, function like White Wolf's Storyteller rolls
    {
            int raw_roll = 1 + (rand() % this->num_sides);
            if (raw_roll >= 5)
            {
                    this->last_rolled = 1;  /*Rolled high enough*/
            }
            else if (raw_roll == 1)
            {
                    this->last_rolled = -1; /*Failed on this die*/
            }
            else
            {
                    this->last_rolled = 0;  /*No loss, no gain*/
            }
    
            return this->last_rolled;
    }
    The disadvantage clearly evident is the hard coded values for the different results e.g. greater than five. One solution I have thought of is passing an additional pointer to the roll_die() function. A second solution is to provide parameter storage in the Basic_Die structure. A third is encapsulating the Basic_Die in a different structure. For the second proposal, I could do a list of additional parameters, with minimal extra overhead. Which of these is the best option, or are there even better options not listed? I am programming in C.

  2. #2
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    An idea, for good or ill:
    Code:
    class Die {
        public:
            int Roll();
            Die(std::vector<int> possible_sides);
        private:
            int number_of_sides;
            std::vector<int> sides;
    };
    
    Die::Die(std::vector<int> possible_sides) :
        number_of_sides(possible_sides.size()), sides(possible_sides.begin(), possible_sides.end())
    {
    }
    
    int Die::Roll() {
        return sides[rand()%number_of_sides];
    }
    In other words, each die knows what the markings on its sides are and returns whichever one is rolled. Using a vector is a little awkward (ok, a lot awkward, unless there's a nice easy way to build a vector with values {-1,0,0,0,1,1} that I've missed to pass into the constructor), but I wasn't sure if you were always using d6. If so, then just make it an array of six, pass in an array of 6 and be done with it.

    EDIT: Holy cow, I'm sorry, I thought I was in the C++ board. Alright. Ignore the vector part, and the class part, and just everything. I still think that each die should know its markings and return. For penance, a C version:
    Code:
    struct Die {
        int number_of_sides;
        int *sides;
    };
    
    int createDie(int n, int *sidelist, struct Die *newdie) {
        newdie->number_of_sides = n;
        newdie->sides = malloc(n*sizeof(int));
        if (newdie->sides == NULL) {
            return 1; /*bad things*/
        }
        for (int i = 0; i < n; i++) {
            newdie->sides[i] = sidelist[i];
        }
        return 0;
    }
    
    int RollDie(struct Die die) {
        return die.sides[rand()%die.number_of_sides];
    }
    Last edited by tabstop; 02-26-2009 at 11:50 PM.

  3. #3
    Registered User
    Join Date
    Feb 2009
    Posts
    20
    Unfortunately, I am programming in C, but using a lookup table is a method I forgot. However, I might be using many different sizes of dice. Further, in Storyteller, usually you score for rolls greater than or equal to eight. However, some conditions in the game make the "threshold" nine or ten. Thus, I may need to change the minimum value. However, I do question why I am taking a somewhat object oriented approach; it's probably to make the program easily extensible. Another alternative would be to have a different function interpret the rolls, though that does somewhat defeat the purpose of having the function pointer so that I can make different rolls.
    If I go with the linked list, would strings forming a name:value pair be a usual way of doing this?

  4. #4
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    I can only suggest ideas; without knowing the different ways you're using dice, I can't say how applicable they are.

    With that caveat: many different sizes of dice is no problem with the above idea, as each die knows how big it is, which means the Roll function can choose appropriately. (I make no claim on it, as that was a feature of each of your designs as well.) As for the threshold changing, I see more-or-less two straightforward choices: a variable called 'threshold' with a check if (die.roll() > threshold), or different dice for each different threshold (corresponding in your original idea, I think, with different function pointers). If you go with your function pointers, you can actually keep two or three different functions around, and as the situation changes reassign the die's function pointer to whichever is appropriate. That seems, though, like a lot of work for the same payoff as the if-statement above.

    I suppose I took an object-oriented approach at first for the obvious I-don't-know-where-I-am reason; having said that, if your dice are at all "weird" than it seems like a good idea. If all your rolls fall into the two categories (1) plain old numbers from 1-n and (2) bigger than n or not bigger than n, then I don't think you gain much over if.

    I did not, and would not, suggest a linked list here.

  5. #5
    Registered User
    Join Date
    Feb 2009
    Posts
    20
    Quote Originally Posted by tabstop View Post
    I did not, and would not, suggest a linked list here.
    My reasoning behind using a linked list was to make the Basic_Die able to have multiple internal variables without affecting the outside view of the struct. This way, I could use more or less generic functions to roll the dice in various ways; one way I can foresee rolling in an unusual manner is rolling a die until I don't get a six, somewhat like the wild die in d6 Star Wars. Admittedly, in doing so I would be stretching the word "die" all out of shape. Thinking about it, I may have taken an object oriented approach so that die types could be changed.

  6. #6
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Well, but you don't have to have a physical die to emulate. If you want to roll a d6 until you don't get a 6, just roll a d5. (I would still just use a malloc'ed array, rather than a linked list.)

  7. #7
    Registered User
    Join Date
    Feb 2009
    Posts
    20
    Quote Originally Posted by tabstop View Post
    Well, but you don't have to have a physical die to emulate. If you want to roll a d6 until you don't get a 6, just roll a d5. (I would still just use a malloc'ed array, rather than a linked list.)
    I should have clarified rolling a d6 until I don't get a 6. I mean that if I roll a 6 on that die, I keep the 6, roll again and add it to the running total, and if my new roll was a 6, lather, rinse, and repeat. The array is definitely one option, though if I want to have the dice "evolve" from user-input rules during runtime, a linked list would be one way to go. Another option that I forgot to mention was giving the die rolling routine a variable number of arguments. That could also increase how generic the die is, e.g. letting me change thresholds.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. i need advice about data structures
    By sawer in forum C Programming
    Replies: 2
    Last Post: 04-22-2006, 03:40 AM
  2. Need some help regarding data structures
    By Afrinux in forum C Programming
    Replies: 15
    Last Post: 01-28-2006, 05:19 AM
  3. array of structures, data from file
    By nomi in forum C Programming
    Replies: 5
    Last Post: 01-11-2004, 01:42 PM
  4. C diamonds and perls :°)
    By Carlos in forum A Brief History of Cprogramming.com
    Replies: 7
    Last Post: 05-16-2003, 10:19 PM
  5. need help with data structures
    By straightedgekid in forum C++ Programming
    Replies: 0
    Last Post: 10-05-2001, 02:17 PM

Tags for this Thread