Rewrite the !dice command from scratch

This commit is contained in:
runebaas 2020-06-21 03:33:05 +02:00
parent d7e313c9fa
commit 6d44960867
No known key found for this signature in database
GPG key ID: 2677AF508D0300D6
13 changed files with 376 additions and 126 deletions

View file

@ -0,0 +1,13 @@
using System;
namespace Geekbot.net.Lib.DiceParser
{
public class DiceException : Exception
{
public DiceException(string message) : base(message)
{
}
public string DiceName { get; set; }
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Geekbot.net.Lib.DiceParser
{
public class DiceInput
{
public List<SingleDie> Dice { get; set; } = new List<SingleDie>();
public DiceInputOptions Options { get; set; } = new DiceInputOptions();
public int SkillModifier { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Geekbot.net.Lib.DiceParser
{
public struct DiceInputOptions
{
public bool ShowTotal { get; set; }
}
}

View file

@ -0,0 +1,102 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using Geekbot.net.Lib.RandomNumberGenerator;
namespace Geekbot.net.Lib.DiceParser
{
public class DiceParser : IDiceParser
{
private readonly IRandomNumberGenerator _randomNumberGenerator;
private readonly Regex _inputRegex;
private readonly Regex _singleDieRegex;
public DiceParser(IRandomNumberGenerator randomNumberGenerator)
{
_randomNumberGenerator = randomNumberGenerator;
_inputRegex = new Regex(
@"((?<DieAdvantage>\+\d+d\d+)|(?<DieDisadvantage>\-\d+d\d+)|(?<DieNormal>\d+d\d+)|(?<Keywords>(total))|(?<SkillModifer>(\+|\-)\d+))\s",
RegexOptions.Compiled | RegexOptions.IgnoreCase,
new TimeSpan(0, 0, 2));
_singleDieRegex = new Regex(
@"\d+d\d+",
RegexOptions.Compiled | RegexOptions.IgnoreCase,
new TimeSpan(0, 0, 0, 0, 500));
}
public DiceInput Parse(string input)
{
// adding a whitespace at the end, otherwise the parser might pickup on false items
var inputWithExtraWhitespace = $"{input} ";
var matches = _inputRegex.Matches(inputWithExtraWhitespace);
var result = new DiceInput();
var resultOptions = new DiceInputOptions();
foreach (Match match in matches)
{
foreach (Group matchGroup in match.Groups)
{
if (matchGroup.Success)
{
switch (matchGroup.Name)
{
case "DieNormal":
result.Dice.Add(Die(matchGroup.Value, DieAdvantageType.None));
break;
case "DieAdvantage":
result.Dice.Add(Die(matchGroup.Value, DieAdvantageType.Advantage));
break;
case "DieDisadvantage":
result.Dice.Add(Die(matchGroup.Value, DieAdvantageType.Disadvantage));
break;
case "Keywords":
Keywords(matchGroup.Value, ref resultOptions);
break;
case "SkillModifer":
result.SkillModifier = SkillModifer(matchGroup.Value);
break;
}
}
}
}
if (!result.Dice.Any())
{
result.Dice.Add(new SingleDie(_randomNumberGenerator));
}
result.Options = resultOptions;
return result;
}
private SingleDie Die(string match, DieAdvantageType advantageType)
{
var x = _singleDieRegex.Match(match).Value.Split('d');
var die = new SingleDie(_randomNumberGenerator)
{
Amount = int.Parse(x[0]),
Sides = int.Parse(x[1]),
AdvantageType = advantageType
};
die.ValidateDie();
return die;
}
private int SkillModifer(string match)
{
return int.Parse(match);
}
private void Keywords(string match, ref DiceInputOptions options)
{
switch (match)
{
case "total":
options.ShowTotal = true;
break;
}
}
}
}

View file

@ -0,0 +1,9 @@
namespace Geekbot.net.Lib.DiceParser
{
public enum DieAdvantageType
{
Advantage,
Disadvantage,
None
}
}

View file

@ -0,0 +1,30 @@
using System;
namespace Geekbot.net.Lib.DiceParser
{
public class DieResult
{
// public int Result { get; set; }
public int Roll1 { get; set; }
public int Roll2 { get; set; }
public DieAdvantageType AdvantageType { get; set; }
public override string ToString()
{
return AdvantageType switch
{
DieAdvantageType.Advantage => Roll1 > Roll2 ? $"(**{Roll1}**, {Roll2})" : $"({Roll1}, **{Roll2}**)",
DieAdvantageType.Disadvantage => Roll1 < Roll2 ? $"(**{Roll1}**, {Roll2})" : $"({Roll1}, **{Roll2}**)",
_ => Result.ToString()
};
}
public int Result => AdvantageType switch
{
DieAdvantageType.None => Roll1,
DieAdvantageType.Advantage => Math.Max(Roll1, Roll2),
DieAdvantageType.Disadvantage => Math.Min(Roll1, Roll2),
_ => 0
};
}
}

View file

@ -0,0 +1,7 @@
namespace Geekbot.net.Lib.DiceParser
{
public interface IDiceParser
{
DiceInput Parse(string input);
}
}

View file

@ -0,0 +1,72 @@
using System.Collections.Generic;
using Geekbot.net.Lib.Extensions;
using Geekbot.net.Lib.RandomNumberGenerator;
namespace Geekbot.net.Lib.DiceParser
{
public class SingleDie
{
private readonly IRandomNumberGenerator _random;
public SingleDie(IRandomNumberGenerator random)
{
_random = random;
}
public int Sides { get; set; } = 20;
public int Amount { get; set; } = 1;
public DieAdvantageType AdvantageType { get; set; } = DieAdvantageType.None;
public string DiceName => AdvantageType switch
{
DieAdvantageType.Advantage => $"{Amount}d{Sides} (with advantage)",
DieAdvantageType.Disadvantage => $"{Amount}d{Sides} (with disadvantage)",
_ => $"{Amount}d{Sides}"
};
public List<DieResult> Roll()
{
var results = new List<DieResult>();
Amount.Times(() =>
{
var result = new DieResult
{
Roll1 = _random.Next(1, Sides),
AdvantageType = AdvantageType
};
if (AdvantageType == DieAdvantageType.Advantage || AdvantageType == DieAdvantageType.Disadvantage)
{
result.Roll2 = _random.Next(1, Sides);
}
results.Add(result);
});
return results;
}
public void ValidateDie()
{
if (Amount < 1)
{
throw new DiceException("To few dice, must be a minimum of 1");
}
if (Amount > 24)
{
throw new DiceException("To many dice, maximum allowed is 24") { DiceName = DiceName };
}
if (Sides < 2)
{
throw new DiceException("Die must have at least 2 sides") { DiceName = DiceName };
}
if (Sides > 144)
{
throw new DiceException("Die can not have more than 144 sides") { DiceName = DiceName };
}
}
}
}

View file

@ -0,0 +1,15 @@
using System;
namespace Geekbot.net.Lib.Extensions
{
public static class IntExtensions
{
public static void Times(this int count, Action action)
{
for (var i = 0; i < count; i++)
{
action();
}
}
}
}