5
\$\begingroup\$

dRoll: Beginner Dice Roller

Roll dice base on a input string.

1/. Input.

<number_of_dice> d <value> [/ <modifier>]
  • <number_of_dice>, int indicate the number of dice.
  • d, delimiter.
  • <value>, int dice value.
  • /, Optional delimiter for dice modifier.
  • <modifier>, int only with / delimiter

Each valid input can be separated by an ;.
Exemple of input:

"2d8"     Valid
"1d8;1d8" Valid
"1d8/-2"  Valid

Any not valid input is ignored.

2/. Roll rules:

If the rolled value is equals to the die value "Ace". An other roll is add to the die result.
Exemple:

  • "1d4" Roll n°1=4 n°2=3 TotalValue= 7
  • "1d4/-2" Roll n°1=4 n°2=3 TotalValue= 5

A Die 6 is also roll for every roll as Bonus die. resultB

3/. Code

class dRoll
{
    private string _input;
    private List<dSet> _dSets = new List<dSet>();

    public dRoll(string input) {
        _input = input;
        ValidateInput();
        Run();
    }

    private void Run() { 
        foreach(var d in _dSets){
            d.Roll();
            d.PrintC();
        }
    }

    private void ValidateInput() {

        string[] argD = _input.Split(';');

        foreach (string arg in argD)
        {
            if (string.IsNullOrWhiteSpace(arg))
            {
                continue;
            }

            string[] dSplit = arg.ToLower().Split('d');
            if (dSplit.Length != 2)
            {
                continue;
            }

            int numT;
            if (!(int.TryParse(dSplit[0], out numT) && numT > 0))
            {
                continue;
            }


            int valT; int mod = 0;
            if (dSplit[1].Contains('/'))
            {
                string[] ModT = dSplit[1].Split('/');
                if (ModT.Length != 2)
                {
                    continue;
                }
                if (!(int.TryParse(ModT[0], out valT) && valT > 1))
                {
                    continue;
                }
                if (!(int.TryParse(ModT[1], out mod) && mod > 0))
                {
                    continue;
                }
            }
            else
            {
                if (!(int.TryParse(dSplit[1], out valT) && valT > 1))
                {
                    continue;
                }
            }

            for (int i = 1; i <= numT; i++)
            {
                if (mod == 0)
                {
                    _dSets.Add(new dSet(1, valT));
                }
                else
                {
                    _dSets.Add(new dSet(1, valT, mod));                        
                }
            }
        }
    }
}

class dSet 
{
    private static readonly Random random = new Random();

    private int _Dnum;
    private int _Dval;
    private int _Mval;

    dResult dResult = new dResult();

    public bool isMod
    {
        get
        {
            return _Mval!=0;
        }
    }

    public dSet() {            
      _Dnum=0;
      _Dval=0;
      _Mval=0;
    }

    public dSet(int Dnum, int Dval) {
        _Dnum = Dnum;
        _Dval = Dval;
        _Mval = 0;
    }

    public dSet(int Dnum, int Dval, int Mval) {
        _Dnum = Dnum;
        _Dval = Dval;
        _Mval = Mval;
    }

    public void Roll(){
        for (int i = 1; i <= _Dnum; i++)
        {
            bool NAce = true;
            bool BAce = true;

            for (int nNbr = 1; NAce; nNbr++)
            {
                int nRnd = random.Next(1, _Dval+1);
                dResult.resultN.Add(new Tuple<int, int>(nNbr, nRnd));
                if (nRnd != _Dval) 
                    NAce = false;
            }

            for (int bNbr = 1; BAce; bNbr++)
            {
                int bRnd = random.Next(1, 6);
                dResult.resultB.Add(new Tuple<int, int>(bNbr, bRnd));
                if (bRnd != 6) 
                    BAce = false;
            }
        }
    }

    public void PrintC(){
        Console.WriteLine("D{0}:{1}", _Dval, _Mval);

        Console.WriteLine("\tNormal : {0}",dResult.Ntot + _Mval);
        foreach (var n in dResult.resultN){
            Console.WriteLine("\t\t{0}: {1}", n.Item1, n.Item2);
        }

        Console.WriteLine("\tBonus : {0}", dResult.Btot + _Mval);
        foreach (var b in dResult.resultB) {
            Console.WriteLine("\t\t{0}: {1}", b.Item1, b.Item2);
        }        
    }
}

class dResult
{
    public List<Tuple<int, int>> resultN = new List<Tuple<int, int>>();
    public List<Tuple<int, int>> resultB = new List<Tuple<int, int>>();
    public int Ntot {
        get {
            return resultN.Sum(x => x.Item2);
        }
    }
    public int Btot {
        get
        {
            return resultB.Sum(x => x.Item2);
        }
    }

}
\$\endgroup\$
2
\$\begingroup\$

There are a couple of things I would have done differently in your code.

  1. Validation

    • I suggest you should try and get familiar with regex. I know that it has a steep learning curve and it's difficult to understand at first glance, but it's going to save you a lot of time instead of coding things the alternative way.
    • In your ValidateInput function you do a lot of splitting and int parsing. Would it surprise you that with regex the whole input parsing could be reduced just to 4-5 lines of code?

      Check this out:

      string Input = "8d2/-4";
      Match RegexMatch = Regex.Match(Input, @"^(\d+)d(\d+)(\/-?\d+)?$");
      Console.WriteLine(RegexMatch.Groups[1].Value); // Output: 8
      Console.WriteLine(RegexMatch.Groups[2].Value); // Output: 2
      Console.WriteLine(string.IsNullOrEmpty(RegexMatch.Groups[3].Value) ? "Modifier not available" : RegexMatch.Groups[3].Value.Substring(1)); // Output: -4
      
      string Input = "8d2";
      Match RegexMatch = Regex.Match(Input, @"^(\d+)d(\d+)(\/-?\d+)?$");
      Console.WriteLine(RegexMatch.Groups[1].Value); // Output: 8
      Console.WriteLine(RegexMatch.Groups[2].Value); // Output: 2
      Console.WriteLine(string.IsNullOrEmpty(RegexMatch.Groups[3].Value) ? "Modifier not available" : RegexMatch.Groups[3].Value.Substring(1)); // Output: Modifier not available
      
      string Input = "8d2/";
      Match RegexMatch = Regex.Match(Input, @"^(\d+)d(\d+)(\/-?\d+)?$");
      Console.WriteLine(RegexMatch.Groups[1].Value); // Outputs nothing
      Console.WriteLine(RegexMatch.Groups[2].Value); // Outputs nothing
      Console.WriteLine(string.IsNullOrEmpty(RegexMatch.Groups[3].Value) ? "Modifier not available" : RegexMatch.Groups[3].Value.Substring(1)); // Output: Modifier not available
      
      string Input = "8d2/abcd";
      Match RegexMatch = Regex.Match(Input, @"^(\d+)d(\d+)(\/-?\d+)?$");
      Console.WriteLine(RegexMatch.Groups[1].Value); // Outputs nothing
      Console.WriteLine(RegexMatch.Groups[2].Value); // Outputs nothing
      Console.WriteLine(string.IsNullOrEmpty(RegexMatch.Groups[3].Value) ? "Modifier not available" : RegexMatch.Groups[3].Value.Substring(1)); // Output: Modifier not available
      
  2. Rolling

    • In your Roll function, you stop the for loop by setting NAce to false. While this solution works, there is definitely a cleaner one. The break statement terminates the closest enclosing loop, so you will exit the for loop immediately and won't need two booleans dedicated to stop the loop.
    • When the bonus Die 6 is rolled, you request a random number with random.Next(1, 6);. A regular Die 6 has six sides and six values, but the Next function of your random class will just return numbers from 1 to 5. 6 is going to be left out, because that is how the Random class in C# works. Your number is going to be greater or equal to the minimum value and less than the maximum value.
  3. Coding style

    • This doesn't influence the way things will work, but will result in a cleaner code.
    • You should either use variable names that speak for themselves or document the purpose of your variables. It is hard to decode one's function with variables that make no sense to us.
    • When an if, for, while or using statement is followed by a single action, no scoping ( { and } ) is needed.
    • Lambda expressions have been introduced in C# 3.0. One particular is the forEach function for List<> that implemented the old foreach statement in a formal manner.
    • With C# 6.0 expression bodied functions and properties have been introduced. You can implement read-only (getter only) properties using expressions.

      Instead of

      public int Ntot {
          get {
              return resultN.Sum(x => x.Item2);
          }
      }
      

      you could write just

      public int Ntot => resultN.Sum(x => x.Item2);
      
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.