Results 1 to 3 of 3

Thread: Contest 14 - DnD Dice Roller - [Wossy]

  1. #1

    Thread Starter
    Junior Member wossy's Avatar
    Join Date
    Aug 2020
    Posts
    26

    Contest 14 - DnD Dice Roller - [Wossy]

    Written in C# Core 3.1.

    (I tried to get this work with a 3rd party code-hosting site as the contest rules asked for, but I didn't see a way to allow command-line arguments to be specified. If anyone knows how to do that I'd be eager to know.)

    This is a console based program, which is best executed from a powershell window using a command similar to:
    Code:
    .\WossDice 3D12 D6 2D%
    The output from which is:
    Code:
    PS C:\[############]WossDice\bin\Debug\netcoreapp3.1> .\WossDice 3D12 D6 2D%
    
    ------------ Rolling 3 D12:
    1
    12
    5
    Batch total 18
    
    
    ------------ Rolling 1 D6:
    5
    
    ------------ Rolling 2 D%:
    85 %
    39 %
    Batch AVERAGE 62 %

    And the code:
    Code:
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;
    
    namespace WossDice
    {
        class DiceBatch
        {
            private readonly int _sides;     //how many faces this type of die has
            private readonly int _quantity;  //how many dice of this type in this batch
            private readonly Random _roller;
    
            public DiceBatch(int sides, int quantity)
            {
                _sides = sides;
                _quantity = quantity;
                _roller = new Random();
            }
    
            public void Roll()
            {
                int total = 0;
                int currentRoll;
    
                if (_sides == 100) //PERCENTAGE DICE PAIR
                {
                    Console.WriteLine($"\r\n------------ Rolling {_quantity} D%:");
    
                    for (uint i = 1; i <= _quantity; i++)
                    {
                        currentRoll = _roller.Next(0, 100); //0 to 99 not including 100
                        total += currentRoll;
                        Console.WriteLine($"{currentRoll} %");
                    }
                }
                else if (_sides == 33) //LOADED D20 DIE (easter egg)
                {
                    double[] weights = new double[20]
                    {
                        /* 1*/ 90.0,
                        /* 2*/ 110.0,
                        /* 3*/ 70.0,
                        /* 4*/ 80.0,
                        /* 5*/ 70.0,
                        /* 6*/ 60.0,
                        /* 7*/ 85.0,
                        /* 8*/ 100.0,
                        /* 9*/ 70.0,
                        /*10*/ 85.0,
                        /*11*/ 70.0,
                        /*12*/ 80.0,
                        /*13*/ 60.0,
                        /*14*/ 115.0,
                        /*15*/ 70.0,
                        /*16*/ 80.0,
                        /*17*/ 65.0,
                        /*18*/ 80.0,
                        /*19*/ 60.0,
                        /*20*/ 140.0
                    };
    
                    Console.WriteLine($"\r\n------------ Rolling {_quantity} LOADED D20:");
    
                    for (uint i = 1; i <= _quantity; i++)
                    {
                        double weighttotal = 0.0;
                        double running = 0.0;
                        double target;
    
                        for (currentRoll = 0; currentRoll < weights.Length; currentRoll++)
                            weighttotal += weights[currentRoll];
    
                        target = _roller.NextDouble() * weighttotal;
    
                        currentRoll = 0;
                        do
                            running += weights[currentRoll++];
                        while (running < target);
    
                        //now currentRoll contains the die side that we rolled with our very sneaky loaded D20!
                        total += currentRoll;
                        Console.Write(currentRoll);
                        if (currentRoll == 20)
                            Console.Write(" -- Supernatural twenty!!!");
                        Console.WriteLine();
                    }
                }
                else
                {
                    Console.WriteLine($"\r\n------------ Rolling {_quantity} D{(int)_sides}:");
    
                    for (uint i = 1; i <= _quantity; i++)
                    {
                        currentRoll = _roller.Next(1, (int)_sides + 1); //1 to _sides inclusive
                        total += currentRoll;
    
                        Console.Write(currentRoll);
    
                        if (_sides == 20 && currentRoll == 20)
                            Console.Write(" -- Natural twenty!!!");
    
                        Console.WriteLine();
                    }
                }
    
                if (_quantity > 1)
                {
                    if (_sides == 100) //show average for percentage rolls, instead of total (handy for n'th degree Gaussian distributions)
                        Console.WriteLine($"Batch AVERAGE {total / _quantity} %\r\n");
                    else //show total for 'normal' die roll batches
                        Console.WriteLine($"Batch total {total}\r\n");
                }
            }
        }
    
        class Program
        {
            private static readonly int MAX_DICE_PER_BATCH = 100;
    
            static int Main(string[] args)
            {
                List<DiceBatch> batches = ProcessArgs(args, out bool helpRequested, out bool argErrors);
    
                if (helpRequested || batches.Count == 0)
                {
                    DisplayHelp();
                    return 0;
                }
                else if (argErrors)
                {
                    return 1;
                }
    
                //no errors and no help requested, all is well, roll 'em!
                foreach (DiceBatch die in batches)
                    die.Roll();
    
                return 0;
            }
    
            private static List<DiceBatch> ProcessArgs(string[] args, out bool helpRequested, out bool argErrors)
            {
                List<DiceBatch> batch = new List<DiceBatch>();
                helpRequested = false;
                argErrors = false;
    
                foreach (string arg in args)
                {
                    string clean = arg.Trim().ToUpper();
                    int q = 0;
                    int s = 0;
    
                    if (clean == "HELP" || clean == "?")
                    {
                        helpRequested = true;
                        continue;
                    }
    
                    //Sensible strings are in the style: D4 1D4 10D20 3D% etc.
                    //
                    //the string must include a 'D' in it if it's a valid DiceBatch definition
                    //we can use the D as a split delimeter to get the quantity (left of the D)
                    //and the # of sides (right of the D)...
                    if (clean.Contains('D'))
                    {
                        string[] fields = clean.Split('D', StringSplitOptions.None);
    
                        if (fields.Length == 2) //2 is good
                        {
                            if (fields[0] == "") //die quantity is to the left of the D
                            {
                                q = 1; //default when not explicitly stated
                            }
                            else
                            {
                                //quantity was provided, try to parse it into an integer
                                if (!int.TryParse(fields[0], out q))
                                {
                                    //can't parse it into an int, error
                                    argErrors = true;
                                }
    
                                if (q <= 0 || q > MAX_DICE_PER_BATCH)
                                {
                                    //was parsed to an int, but not a value that we like, error
                                    argErrors = true;
                                }
                            }
    
                            if (fields[1] == "%") //special case of a percent symbol being used instead of a number
                            {
                                s = 100;
                            }
                            else if (!int.TryParse(fields[1], out s)) //die sides is to the right of the D
                            {
                                argErrors = true;
                            }
                            else
                            {
                                //test actual value of s here to make sure it's valid
    
                                switch (s)
                                {
                                    case 4:
                                    case 6:
                                    case 8:
                                    case 10:
                                    case 12:
                                    case 20:
                                    case 33:  //loaded D20
                                    case 100: //percentage pair
                                        break;
    
                                    default:
                                        argErrors = true;
                                        break;
                                }
                            }
                        }
                        else //after splitting the string on 'D' there were not exactly 2 fields
                        {
                            argErrors = true;
                        }
                    }
                    else
                    {
                        //'D' was missing from the arg
                        argErrors = true;
                    }
    
                    if (argErrors)
                        MoanAboutDiceSyntax(arg);
                    else //arg string format was good
                        batch.Add(new DiceBatch(s, q));
                }
    
                return batch;
            }
    
            private static void MoanAboutDiceSyntax(string s)
            {
                Console.WriteLine($"The dice description '{s}' is not valid.  Please use 'HELP' for examples of proper formatting.");
            }
    
            private static void DisplayHelp()
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                string name = Path.GetFileNameWithoutExtension(codeBase);
    
                Console.WriteLine($"Usage examples:\r\n\t{name} D4 D6 D8 D10 D12 D20 D% 3D6\r\n");
            }
        }
    }
    Had fun writing this, it's been a while since I did any C#, so this did my brain some good. I even put in an easter egg for those evil Dungeon Masters who like to mess with their players .
    On the bright side, I've still got pessimism and despair to fall back on.

  2. #2
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    9,765

    Re: Contest 14 - DnD Dice Roller - [Wossy]

    The program worked without issues.

    There were several points of optimization that could have been taken, for example:
    Code:
    if (helpRequested || batches.Count == 0)
    {
        DisplayHelp();
        return 0;
    }
    else if (argErrors)
    {
        return 1;
    }
    In this case, the else if is redundant because the if that immediately proceeds it returns a value; removing the else and keeping it an if would've been better.

    Another example is:
    Code:
    for (uint i = 1; i <= _quantity; i++)
    {
        currentRoll = _roller.Next(0, 100); //0 to 99 not including 100
        total += currentRoll;
        Console.WriteLine($"{currentRoll} %");
    }
    It is more performant to declare i as an int. In fact if you declare it using var, it implicitly types it to int.

    There were several stylistic changes that I would like to have seen, such as using var when the type is obvious or removing unnecessary overloads (like line 167), but these don't effect the overall performance of the code.

    I really enjoyed this implementation!

  3. #3

    Thread Starter
    Junior Member wossy's Avatar
    Join Date
    Aug 2020
    Posts
    26

    Re: Contest 14 - DnD Dice Roller - [Wossy]

    Quote Originally Posted by dday9 View Post
    In this case, the else if is redundant...
    Wow, you really did look at these in detail, I'm impressed. Yeah you're right, I could have written this in several ways that would have been clearer. I have a talent for choosing ones that make me look noobish

    Quote Originally Posted by dday9 View Post
    such as using var when the type is obvious
    The day I willingly use "var" in my code will be the day Beelzebub has to scrape the frost off his windshield!

    Thanks for running this contest for us, dday9. A shame there weren't more entries really. Remember back in the day when there would be like 40 ppl all piling in at once. Good times.

    I'm off to have a look at the other entries now

    Cheers.
    On the bright side, I've still got pessimism and despair to fall back on.

Posting Permissions

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



Click Here to Expand Forum to Full Width