• The VOIDRUNNER'S CODEX is LIVE! Explore new worlds, fight oppressive empires, fend off fearsome aliens, and wield deadly psionics with this comprehensive boxed set expansion for 5E and A5E!

d20 die roll string parser and roller in C#

This is not a compiled app, just one class written in C#.

This is a static class 'Dice' using a static method 'Roll(string)' that accepts any normal type die roll string (including parenthetical groupings) and outputs the individual rolls, grouping break-downs, and totals as a string. I took this code from HERE , and hacked the java code into C#.

It works really well. Very versatile. It can take any number of dice, and any number of sides per dice, and any amount of modifiers. Spaces are not a problem for the string. Leave them in or out.

I love the use of parenthetical groupings. I think this is where it really shines.

EXAMPLE Input strings:
"(3d6+5)+10d12+(5d6+2)"
"10(10d100)"
"2d42+6d7-44"

EXAMPLE Output Strings:
"(5d6+15)+1d6 = 4+1+2+3+4+15+2 = 29+2 = 31"
"(5d6+15)+1d6 = 4+2+4+2+1+15+1 = 28+1 = 29"
"2d6+15 = 2+5+15 = 7+15 = 22"

This from the original coder: ...as you can see the parenthesis collect the different dice into separate groups in the next-to-last column, so for example:
(1+2+3)+4 = 1+2+3+4 = 6+4 = 10

To implement, call the Dice.Roll(string) method. There are actually two overloaded Roll methods. The second takes three int's and is used by the first.

The reason I made this was b/c I could find an infinite number of die rollers on the web, but no die roller that could accept complex roll strings AND was written in C# AND had the code available to look at. Hence the reason I post the code, and not a compiled app. There are enough of those. But I did find a java version here on these forums, and translated that and added some more code to it. Use it to make your own app. If you can't code, buy a book and learn how. I was an English major and I taught myself to code. .NET is a decent platform with good, free IDE's. Ideal for the hobbyist.

Check it out, take a look at the comments. Since I basically took all the code and cobbled pieces together, there might be ways to make it more efficient or elegant. But it can roll over a thousand rolls in a second, so whats the need to worry about it? I suggest you enter the string "1000(1000d100)" and see how long it takes. I guess that'd be like a million rolls, right?

Code is posted as is, use it at your own peril. I encourage reuse and modification of it, just include the original author's name etc etc. All the normal stuff. I'm not responsible for any of your own stupid decisions.

This is the first, and probably last thing I will ever contribute. But, hey...at least its something!

Bitches!


P.S. If you find any bugs, let me know here.
 

Attachments

  • Dice.zip
    2.7 KB · Views: 538

log in or register to remove this ad



A bug fix

Had to make a fix to the code where if you passed a die-string using only one die it would malfunction. So now, if you pass in "1d6" or "1d1000" or whatever it will.

I have also pasted the code here in the post below.

PS. I hate that I have to zip the file in order to upload it.

Code:
    public static class Dice
    {
        private static Random randomGenerator = new Random();

        /// <summary>
        /// Pass a die roll string in any standard d20-type format, including 
        /// parenthetical rolls, and it will output a string breaking down each 
        /// roll as well as summing the total. This works very nicely. The code was
        /// hacked from java code from Malar's RPG Dice Version 0.9 By Simon Cederqvist 
        /// (simon.cederqvist(INSERT AT HERE) gmail.com). 13.March.2007
        /// http://users.tkk.fi/~scederqv/Dice/
        /// His same license still applies to this code. But if you figure out how to
        /// make a million dollars off this code, then you're smarter than me and 
        /// you deserve to keep it.  On the other hand, share and share alike. 
        /// </summary>
        /// <param name="diceString">This is a standard d20 die roll string, parenthesis 
        /// are allowed. Example Input: (2d8+9)+(3d6+1)-10</param>
        /// <returns>A string breaking down and summing the roll. Example Output: 
        /// (2d8+9)+(3d6+1)-10 = 7+4+9+5+1+2+1-10 = 20+9-10 = 19</returns>
        public static string Roll(string diceString)
        {
            StringBuilder finalResultBuilder = new StringBuilder();
            string tempString = "";
            int intermediateTotal = 0;
            ArrayList sums = new ArrayList();
            ArrayList items = new ArrayList();
            ArrayList dice = new ArrayList();
            int totals = 0;
            bool collate = false;
            bool positive = true;
            string validChars = "1234567890d";
            char[] diceCharArray = diceString.ToLower().ToCharArray();

            for (int i = 0; i < diceString.Length; i++)
            {
                switch (diceCharArray[i])
                {
                    case '+':
                        {
                            if (tempString.Length < 1)
                            {
                                positive = true;
                                break;
                            }
                            dice = calcSubStringRoll(tempString);
                            for (int j = 0; j < dice.Count; j++)
                            {
                                if (!positive)
                                {
                                    items.Add(-1 * Convert.ToInt32(dice[j].ToString()));
                                    intermediateTotal += (-1 * Convert.ToInt32(dice[j].ToString()));
                                }
                                else
                                {
                                    items.Add(Convert.ToInt32(dice[j].ToString()));
                                    intermediateTotal += (Convert.ToInt32(dice[j].ToString()));
                                }
                            }
                            if (!collate)
                            {
                                sums.Add(intermediateTotal);
                                intermediateTotal = 0;
                            }
                            positive = true;
                            tempString = "";
                            break;
                        }
                    case '-':
                        {
                            if (tempString.Length < 1)
                            {
                                positive = false;
                                break;
                            }
                            dice = calcSubStringRoll(tempString);
                            for (int j = 0; j < dice.Count; j++)
                            {
                                if (!positive)
                                {
                                    items.Add(-1 * Convert.ToInt32(dice[j].ToString()));
                                    intermediateTotal += (-1 * Convert.ToInt32(dice[j].ToString()));
                                }
                                else
                                {
                                    items.Add(Convert.ToInt32(dice[j].ToString()));
                                    intermediateTotal += (Convert.ToInt32(dice[j].ToString()));
                                }
                            }
                            if (!collate)
                            {
                                sums.Add(intermediateTotal);
                                intermediateTotal = 0;
                            }
                            positive = false;
                            tempString = "";
                            break;
                        }
                    case '(': collate = true; break;
                    case ')': collate = false; break;
                    default:
                        {
                            if (validChars.Contains("" + diceCharArray[i]))
                                tempString += diceCharArray[i];
                            break;
                        }
                }
            }

            // And once more for the remaining text
            if (tempString.Length > 0)
            {
                dice = calcSubStringRoll(tempString);
                for (int j = 0; j < dice.Count; j++)
                {
                    if (!positive)
                    {
                        items.Add(-1 * Convert.ToInt32(dice[j].ToString()));
                        intermediateTotal += (-1 * Convert.ToInt32(dice[j].ToString()));
                    }
                    else
                    {
                        items.Add(Convert.ToInt32(dice[j].ToString()));
                        intermediateTotal += (Convert.ToInt32(dice[j].ToString()));
                    }
                }
                sums.Add(intermediateTotal);
                intermediateTotal = 0;
            }

            //// Print it all.
            finalResultBuilder.Append(diceString + " = ");
            for (int i = 0; i < items.Count; i++)
            {
                if (Convert.ToInt32(items[i].ToString()) > 0 && i > 0)
                    finalResultBuilder.Append("+" + items[i].ToString());
                else
                    finalResultBuilder.Append(items[i].ToString());
            }
            if (sums.Count > 1 && items.Count > sums.Count)
            { // Don't print just one, or items again.
                finalResultBuilder.Append(" = ");
                for (int i = 0; i < sums.Count; i++)
                {
                    if (Convert.ToInt32(sums[i].ToString()) > 0 && i > 0)
                        finalResultBuilder.Append("+" + sums[i].ToString());
                    else
                        finalResultBuilder.Append(sums[i].ToString());
                }
            }
            for (int i = 0; i < sums.Count; i++)
                totals += Convert.ToInt32(sums[i].ToString());
            finalResultBuilder.Append(" = " + totals + "\n");

            return finalResultBuilder.ToString();
        }


        /// <summary>
        /// Rolls the specified number of die each with the specified number of
        /// sides and returns the numeric result as a string. I had to introduce a 
        /// call to Thread.Sleep() so that the random num gen would seed differently on 
        /// each iteration. 
        /// </summary>
        /// <param name="numberOfDice">The number of die to roll.</param>
        /// <param name="numberOfSides">The number of faces on each dice rolled.</param>
        /// <param name="rollMod"></param>
        /// <returns>A string containing the result of the roll.</returns>
        public static string Roll(int numberOfDice, int numberOfSides, int rollMod)
        {
            // don't allow a Number of Dice less than or equal to zero
            if (numberOfDice <= 0)
                throw new ApplicationException("Number of die must be greater than zero.");

            // don't allow a Number of Sides less than or equal to zero
            if (numberOfSides <= 0)
                throw new ApplicationException("Number of sides must be greater than zero.");

            //// Create the string builder class used to build the string
            //// we return with the result of the die rolls.
            //// See: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemtextstringbuilderclasstopic.asp
            //StringBuilder result = new StringBuilder();

            // Declare the integer in which we will keep the total of the rolls
            int total = 0;
            
            // repeat once for each number of dice
            for (int i = 0; i < numberOfDice; i++)
            {
                // Create the random class used to generate random numbers.
                // See: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemRandomClassTopic.asp

                // Get a pseudo-random result for this roll
                int roll = randomGenerator.Next(1, numberOfSides);

                // Add the result of this roll to the total
                total += roll;

                //// Add the result of this roll to the string builder
                //result.AppendFormat("Dice {0:00}:\t{1}\n", i + 1, roll);
            }
            
            return (total + rollMod).ToString();

            //// Add a line to the result to seperate the rolls from the total
            //result.Append("\t\t--\n");

            //// Add the total to the result
            //result.AppendFormat("TOTAL:\t\t{0}\n", total);

            //// Now that we've finished building the result, get the string
            //// that we've been building and return it.
            //return result.ToString();            

        }

        /// <summary>
        /// This function merely breaks down the *basic* die roll string
        /// into the requsite integers. It is used by the above Roll(string) 
        /// method. 
        /// </summary>
        /// <param name="s">A simple die roll string, such as 3d6. Nothing more.</param>
        /// <returns>Returns an ArrayList of int's containing the various die
        /// rolls as passed in as a parameter.</returns>
        public static ArrayList calcSubStringRoll(string s)
        {
            int x, d;
            ArrayList dice = new ArrayList();
            if (s.Contains("d"))
            {
                x = Convert.ToInt32(s.Split('d')[0]);
                d = Convert.ToInt32(s.Split('d')[1]);

                // I loop here so that each roll is added to the ArrayList, and 
                // therefore works properly with the code I hacked from java above. 
                for (int i = 0; i < x; i++)
                    dice.Add(Dice.Roll(1, d, 0));
            }
            else
                dice.Add(Convert.ToInt32(s));

            return dice;
        }
    }
 

Attachments

  • Dice.zip
    2.6 KB · Views: 197

azhrei_fje

First Post
Thank you for posting your code! I don't know C#, but I have a mild curiosity of what the language looks like, so I appreciate having a class that I can understand its function. :)

Having said that, I would like to point out that in a true object-oriented design, this implementation would likely need to change somewhat.

For example, the class should not be deciding how the data is returned to the caller. Instead, the caller should register a "callback" function and your class would then call that function either multiple times with each die result passed as a parameter each time, or a single time with a list of values passed as an array/linked list/etc.

I can explain further, if you'd like, or you can check out the book Design Patterns by the Gang of Four (the only one I can ever remember is Gamma; look it up on Amazon thought and you'll find it quickly). One of the chapters is on the Observer/Observable design pattern and that pattern is a really good idea for a dice rolling class. In fact, when I teach OOA&D and get to chapter on design patterns, I often use the dice roller as an example of this pattern. Taken to its logical extreme, the output is completely decoupled from the class itself, allowing an entire hierarchy of classes which implement the callback method.

Anyway, as I mentioned at the beginning, thanks for the code snippet. I'm looking forward to perusing it in detail. :)
 

my apologies for getting on a rant here, buuuuuuuuut.......

dude...it rolls dice.....and its a static method, thats all.

using delegates (or call back methods) is overkill for such a simple piece of code. The vast majority of the code is simply parsing the string. If you want to have the method sometimes return an int and sometimes a string..then just overload it or create multiple methods..or use the 'out' keyword. (my fault, you said you weren't familiar with C#). But I think you said you were writing a WPF app, so you should be familiar with .NET, right? or was that someone else?

but the point of the method was to parse the die roll string..you mentioned that your delegate would pass in each die roll or an array of rolls...well if they're already parsed from the input string then there is no need for the method. just instantiate your own Random object and roll directly.

I'm fully versed in the Observer pattern, I must argue that your use of it as you explained is not what I would consider the most efficient. Calling various delegate (or callback) methods is the same as calling overloads, and is unnecessary. It introduces complexity where there need be none.

For example, the class should not be deciding how the data is returned to the caller. Instead, the caller should register a "callback" function and your class would then call that function either multiple times with each die result passed as a parameter each time, or a single time with a list of values passed as an array/linked list/etc.
.....ummmm no. maybe if we were dealing with a highly diversified object model then that would be appropriate. But we're not. We're dealing with strings, int's, and one little method.

Look, I'm not trying to pick an argument or be a pain or a jerk. But implementing the pattern in the way you explain is really not necessary or helpful.

Bottom Line: There are only two possible types the caller would want the data returned as....a string or an int. The best answer, imo, is simply return as int and let the caller cast to string as needed. Simple. Elegant.

No need for delegates. Besides, the caller is not observing anything. The caller is not waiting for something to happen on the part of the dice roller. The caller simply wants a result from his given input. Applying the Observer pattern is nonsensical in this case.

Be careful how you implement grand ideas. Sometimes, you just need to do what you have to do, and no more. Plus I didn't write the code or algorithm, I translated it from Java that another person posted here. I left the algorithm intact. Remember the major point of the method is to parse text. Take a look at my explanation in the first post.

If you teach analysis and design, answer this question that I came across: http://forums.microsoft.com/MSDN/ShowPost.aspx?siteid=1&PostID=2994966

Again, I apologize for my rudeness if that is how I'm coming across. It is not my intention.
 
Last edited:

and if you're talking about the format of the string returned (and all the extraneous info about the die roll), thats just done for demonstration purposes. I assume that anyone using the code would take that out as needed. like I said, the best thing would be just to return type int.
 

azhrei_fje

First Post
dickenscider said:
Again, I apologize for my rudeness if that is how I'm coming across. It is not my intention.
Yeah, perhaps I came across that way too. My apologies. :(

I used to write code. Lots of it. (But you know the saying, "Those who can't do, teach." :))

Anyway, my experience over the last 30 years (ouch! THAT LONG?!) has been that if I don't write something generic in the beginning, I end up redesigning/reimplementing it again later. Usually this happens when I'm writing code for myself, because I think, "Well, I'm only ever going to use this code in this application. Why bother to do a grand design when the code won't be reused anywhere else?" And of course, you know what usually happens... ;)

In any case, the Observer pattern is supposed to separate an object that generates events from the object that interprets those events. This is going to get long-winded, but I'll try to get through it all anyway... :)

Imagine a hierarchy of classes such as DieRoller that takes a dice string to parse and generates a list of random numbers. How should those numbers be displayed? They could be displayed on a terminal, drawn on the screen as graphics elements, flashed on LEDs hooked up to the parallel port, spoken aloud, and so on. Because the data might be used in many different ways, the specific implementation should not be in the DieRoller class.

Instead, a new class called DieObserver is created. (This would actually be an interface, but I'm simplifying.) The DieObserver would implement a callback, such as dieRollUpdated(DieRoller dr). When the DieRoller class is told to roll its dice, each random number generated causes DieRoller to invoke the dieRollUpdated() method of a DieObserver that was passed in at the time the DieRoller was created. The DieObserver object can now query back to the original DieRoller using the passed in parameter and ask it what the latest roll was. (The query back to DieRoller is done to avoid hard-coding a data type into the parameter list of the dieRollUpdated method, and hence, makes the DieObserver more generic and reusable. And, it many cases allows a single DieObserver to be used by multiple DieRollers; not in this example, though.)

This allows me to use DieObserver as the top class in a hierarchy of classes: GraphicsDieObserver, LEDDieObserver, TerminalDieObserver, and so on. The main application passes the dice string to be parsed to the DieRoller, and as it generates random numbers it passes them to a particular DieObserver and the DieObserver is responsible for building the result. In your code example, that would be StringDieObserver -- it would simply concatenate all of the values together into a string.

Because the DieObserver was passed to the DieRoller at instantiation time, the main application can have a reference to the DieObserver cached and
later call a method of the DieObserver to retrieve the string.

Empirical data suggests that object-oriented code has roughly 15%-20% more lines of code than a procedural program (Martin Fowler, UML Distilled, 2nd Edition). This means that it takes longer to get from the starting point to the end result. But if analysis patterns and design patterns are applied at each step along the way, the resulting code is highly modular and quite easy to reuse in other applications later. This means that the next version of the code can be released sooner, that the readability of the code is improved, and that maintenance is easier.

<rant>
I read somewhere that, "[...] software doesn't need maintenance. It doesn't require periodic oil changes and its tires don't wear out. Instead, the functionality required of the software changes. Software doesn't need maintenance -- it needs redesign and reimplementation." I like that quote because it pinpoints one of the major problems with software production today: the feeling that the code is such a mess that it needs to be redone from the ground up. Why would that happen? It happens because the A&D steps were skipped or severely limited so that the program did not include the proper design elements. Since the design was not robust enough in the beginning, the patches made later to add functionality instead just obscure the existing functionality! Instead of patching a single class to add functionality, that subsystem of the application should be sent back to the A&D folks so that they can evaluate what the proper design should be to accommodate the change in functionality that is desired and then the code reimplemented.
</rant>

Concerning the link to the Microsoft web site, I understand the goal behind the question, but I'm not sure I understand whether the question is asking for a design that will accomplish what the OP is asking about or if the OP wants an implementation. The answer provided by the next post mentions the Provider pattern and this seems like a good way to go. If the question was concerning the C# implementation, ie. "How do I implement this in C#?", then I can't help. :(

Again, I apologize again for the long-winded reply. And yes, this probably seems like a lot of work to go through to get from point A to point B. I have found that if I write all of my code as though it were going into a library/framework instead of into a specific application, I end up with generic, reusable classes more often. It is hard to discipline myself, however, when I think that I just want something quick and dirty that works. :)
 
Last edited:

Plane Sailing

Astral Admin - Mwahahaha!
dickenscider said:
thanks for noticing!


So I've just come back from a WPF course via work, and I've been inspired to write a D&D related program :)

I'll include this class and let you know if I discover any (other!) bugs in the process.

Cheers
 

@azhrei, that sounds more like a model-view-controller pattern (or n-tier)...but really that is a type of observer pattern I suppose. In your description, this class is just (a portion of) the controller not the observer or viewer. I'd leave it up to the GUI code to type cast or transform the data as necessary. I really expect anyone using this code to tailor it. Also, since the UI should know when a die roll was called, there shouldn't necessarily need to be an event handler to update the UI. I suppose there could be, but I'd find it odd if the UI didn't know when a call was made by (assumedly) the end-user.

@Plane Sailing, I think you can simplify the code if you like. It outputs a long string when only the actual die roll result is probably most useful. The extra info in the output is just there for display purposes in case you want to show a breakdown of the rolls to the UI or whatever consumer/caller.

About the link to the MSDN forums, I saw that post and really REALLY wanted to know the answer to the question b/c I would love to create an app like that. Or at least would love to see the source for it.

And w/ 4E coming soon, I think there will be a big desire for new apps supporting the new edition.

It's been a good conversation. I've enjoyed it. Thanks! :)
 

Voidrunner's Codex

Remove ads

Top