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);
}
}
}
1 Answer 1
There are a couple of things I would have done differently in your code.
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
Rolling
- In your
Roll
function, you stop thefor
loop by settingNAce
tofalse
. While this solution works, there is definitely a cleaner one. Thebreak
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 theNext
function of yourrandom
class will just return numbers from 1 to 5. 6 is going to be left out, because that is how theRandom
class in C# works. Your number is going to be greater or equal to the minimum value and less than the maximum value.
- In your
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
orusing
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 forList<>
that implemented the oldforeach
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);