Rewrite the !dice command from scratch
This commit is contained in:
parent
d7e313c9fa
commit
6d44960867
13 changed files with 376 additions and 126 deletions
107
Geekbot.net/Commands/Utils/Dice.cs
Normal file
107
Geekbot.net/Commands/Utils/Dice.cs
Normal file
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib.DiceParser;
|
||||
using Geekbot.net.Lib.ErrorHandling;
|
||||
|
||||
namespace Geekbot.net.Commands.Utils
|
||||
{
|
||||
public class Dice : ModuleBase
|
||||
{
|
||||
private readonly IErrorHandler _errorHandler;
|
||||
private readonly IDiceParser _diceParser;
|
||||
|
||||
public Dice(IErrorHandler errorHandler, IDiceParser diceParser)
|
||||
{
|
||||
_errorHandler = errorHandler;
|
||||
_diceParser = diceParser;
|
||||
}
|
||||
|
||||
// ToDo: Separate representation and logic
|
||||
// ToDo: Translate
|
||||
[Command("dice", RunMode = RunMode.Async)]
|
||||
[Summary("Roll a dice. (use '!dice help' for a manual)")]
|
||||
public async Task RollCommand([Remainder] [Summary("input")] string diceInput = "1d20")
|
||||
{
|
||||
try
|
||||
{
|
||||
if (diceInput == "help")
|
||||
{
|
||||
await ShowDiceHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
var parsed = _diceParser.Parse(diceInput);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"{Context.User.Mention} :game_die:");
|
||||
foreach (var die in parsed.Dice)
|
||||
{
|
||||
sb.AppendLine($"**{die.DiceName}**");
|
||||
var diceResultList = new List<string>();
|
||||
var total = 0;
|
||||
|
||||
foreach (var roll in die.Roll())
|
||||
{
|
||||
diceResultList.Add(roll.ToString());
|
||||
total += roll.Result;
|
||||
}
|
||||
|
||||
sb.AppendLine(string.Join(" | ", diceResultList));
|
||||
|
||||
if (parsed.SkillModifier != 0)
|
||||
{
|
||||
sb.AppendLine($"Skill: {parsed.SkillModifier}");
|
||||
}
|
||||
|
||||
if (parsed.Options.ShowTotal)
|
||||
{
|
||||
var totalLine = $"Total: {total}";
|
||||
if (parsed.SkillModifier > 0)
|
||||
{
|
||||
totalLine += ($" (+{parsed.SkillModifier} = {total + parsed.SkillModifier})");
|
||||
}
|
||||
|
||||
if (parsed.SkillModifier < 0)
|
||||
{
|
||||
totalLine += ($" ({parsed.SkillModifier} = {total - parsed.SkillModifier})");
|
||||
}
|
||||
|
||||
sb.AppendLine(totalLine);
|
||||
}
|
||||
}
|
||||
|
||||
await Context.Channel.SendMessageAsync(sb.ToString());
|
||||
}
|
||||
catch (DiceException e)
|
||||
{
|
||||
await Context.Channel.SendMessageAsync($"**:warning: {e.DiceName} is invalid:** {e.Message}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _errorHandler.HandleCommandException(e, Context);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowDiceHelp()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("**__Examples__**");
|
||||
sb.AppendLine("```");
|
||||
sb.AppendLine("'!dice' - throw a 1d20");
|
||||
sb.AppendLine("'!dice 1d12' - throw a 1d12");
|
||||
sb.AppendLine("'!dice +1d20' - throw with advantage");
|
||||
sb.AppendLine("'!dice -1d20' - throw with disadvantage");
|
||||
sb.AppendLine("'!dice 1d20 +2' - throw with a +2 skill bonus");
|
||||
sb.AppendLine("'!dice 1d20 -2' - throw with a -2 skill bonus");
|
||||
sb.AppendLine("'!dice 8d6' - throw ~~a fireball~~ a 8d6");
|
||||
sb.AppendLine("'!dice 8d6 total' - calculate the total");
|
||||
sb.AppendLine("'!dice 2d20 6d6 2d4 2d12' - drop your dice pouch");
|
||||
sb.AppendLine("```");
|
||||
|
||||
await Context.Channel.SendMessageAsync(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.net.Lib.RandomNumberGenerator;
|
||||
|
||||
namespace Geekbot.net.Commands.Utils.Dice
|
||||
{
|
||||
public class Dice : ModuleBase
|
||||
{
|
||||
private readonly IRandomNumberGenerator _randomNumberGenerator;
|
||||
|
||||
public Dice(IRandomNumberGenerator randomNumberGenerator)
|
||||
{
|
||||
_randomNumberGenerator = randomNumberGenerator;
|
||||
}
|
||||
|
||||
[Command("dice", RunMode = RunMode.Async)]
|
||||
[Summary("Roll a dice.")]
|
||||
public async Task RollCommand([Remainder] [Summary("dice-type")] string diceType = "1d20")
|
||||
{
|
||||
var splitedDices = diceType.Split("+");
|
||||
var dices = new List<DiceTypeDto>();
|
||||
var mod = 0;
|
||||
foreach (var i in splitedDices)
|
||||
{
|
||||
var dice = ToDice(i);
|
||||
if (dice.Sides != 0 && dice.Times != 0)
|
||||
{
|
||||
dices.Add(dice);
|
||||
}
|
||||
else if (dice.Mod != 0)
|
||||
{
|
||||
if (mod != 0)
|
||||
{
|
||||
await ReplyAsync("You can only have one mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = dice.Mod;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dices.Any())
|
||||
{
|
||||
await ReplyAsync(
|
||||
"That is not a valid dice, examples are: 1d20, 1d6, 2d6, 1d6+2, 1d6+2d8+1d20+6, etc...");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (dices.Any(d => d.Times > 20))
|
||||
{
|
||||
await ReplyAsync("You can't throw more than 20 dices");
|
||||
return;
|
||||
}
|
||||
|
||||
if (dices.Any(d => d.Sides > 144))
|
||||
{
|
||||
await ReplyAsync("A dice can't have more than 144 sides");
|
||||
return;
|
||||
}
|
||||
|
||||
var rep = new StringBuilder();
|
||||
rep.AppendLine($":game_die: {Context.User.Mention}");
|
||||
rep.Append("**Result:** ");
|
||||
var resultStrings = new List<string>();
|
||||
var total = 0;
|
||||
foreach (var dice in dices)
|
||||
{
|
||||
var results = new List<int>();
|
||||
for (var i = 0; i < dice.Times; i++)
|
||||
{
|
||||
var roll = _randomNumberGenerator.Next(1, dice.Sides);
|
||||
total += roll;
|
||||
results.Add(roll);
|
||||
}
|
||||
|
||||
resultStrings.Add($"{dice.DiceType} ({string.Join(",", results)})");
|
||||
}
|
||||
|
||||
rep.Append(string.Join(" + ", resultStrings));
|
||||
if (mod != 0)
|
||||
{
|
||||
rep.Append($" + {mod}");
|
||||
total += mod;
|
||||
}
|
||||
|
||||
rep.AppendLine();
|
||||
rep.AppendLine($"**Total:** {total}");
|
||||
await ReplyAsync(rep.ToString());
|
||||
}
|
||||
|
||||
private DiceTypeDto ToDice(string dice)
|
||||
{
|
||||
var diceParts = dice.Split('d');
|
||||
if (diceParts.Length == 2
|
||||
&& int.TryParse(diceParts[0], out var times)
|
||||
&& int.TryParse(diceParts[1], out var max))
|
||||
return new DiceTypeDto
|
||||
{
|
||||
DiceType = dice,
|
||||
Times = times,
|
||||
Sides = max
|
||||
};
|
||||
if (dice.Length == 1
|
||||
&& int.TryParse(diceParts[0], out var mod))
|
||||
return new DiceTypeDto
|
||||
{
|
||||
Mod = mod
|
||||
};
|
||||
return new DiceTypeDto();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
namespace Geekbot.net.Commands.Utils.Dice
|
||||
{
|
||||
internal class DiceTypeDto
|
||||
{
|
||||
public string DiceType { get; set; }
|
||||
public int Times { get; set; }
|
||||
public int Sides { get; set; }
|
||||
public int Mod { get; set; }
|
||||
}
|
||||
}
|
13
Geekbot.net/Lib/DiceParser/DiceException.cs
Normal file
13
Geekbot.net/Lib/DiceParser/DiceException.cs
Normal 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; }
|
||||
}
|
||||
}
|
11
Geekbot.net/Lib/DiceParser/DiceInput.cs
Normal file
11
Geekbot.net/Lib/DiceParser/DiceInput.cs
Normal 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; }
|
||||
}
|
||||
}
|
7
Geekbot.net/Lib/DiceParser/DiceInputOptions.cs
Normal file
7
Geekbot.net/Lib/DiceParser/DiceInputOptions.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.net.Lib.DiceParser
|
||||
{
|
||||
public struct DiceInputOptions
|
||||
{
|
||||
public bool ShowTotal { get; set; }
|
||||
}
|
||||
}
|
102
Geekbot.net/Lib/DiceParser/DiceParser.cs
Normal file
102
Geekbot.net/Lib/DiceParser/DiceParser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
Geekbot.net/Lib/DiceParser/DieAdvantageType.cs
Normal file
9
Geekbot.net/Lib/DiceParser/DieAdvantageType.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Geekbot.net.Lib.DiceParser
|
||||
{
|
||||
public enum DieAdvantageType
|
||||
{
|
||||
Advantage,
|
||||
Disadvantage,
|
||||
None
|
||||
}
|
||||
}
|
30
Geekbot.net/Lib/DiceParser/DieResult.cs
Normal file
30
Geekbot.net/Lib/DiceParser/DieResult.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
7
Geekbot.net/Lib/DiceParser/IDiceParser.cs
Normal file
7
Geekbot.net/Lib/DiceParser/IDiceParser.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.net.Lib.DiceParser
|
||||
{
|
||||
public interface IDiceParser
|
||||
{
|
||||
DiceInput Parse(string input);
|
||||
}
|
||||
}
|
72
Geekbot.net/Lib/DiceParser/SingleDie.cs
Normal file
72
Geekbot.net/Lib/DiceParser/SingleDie.cs
Normal 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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
Geekbot.net/Lib/Extensions/IntExtensions.cs
Normal file
15
Geekbot.net/Lib/Extensions/IntExtensions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ using Geekbot.net.Handlers;
|
|||
using Geekbot.net.Lib;
|
||||
using Geekbot.net.Lib.Clients;
|
||||
using Geekbot.net.Lib.Converters;
|
||||
using Geekbot.net.Lib.DiceParser;
|
||||
using Geekbot.net.Lib.ErrorHandling;
|
||||
using Geekbot.net.Lib.GlobalSettings;
|
||||
using Geekbot.net.Lib.GuildSettingsManager;
|
||||
|
@ -169,6 +170,7 @@ namespace Geekbot.net
|
|||
var kvMemoryStore = new KvInInMemoryStore();
|
||||
var translationHandler = new TranslationHandler(_logger, _guildSettingsManager);
|
||||
var errorHandler = new ErrorHandler(_logger, translationHandler, _runParameters);
|
||||
var diceParser = new DiceParser(randomNumberGenerator);
|
||||
|
||||
services.AddSingleton(_userRepository);
|
||||
services.AddSingleton<IGeekbotLogger>(_logger);
|
||||
|
@ -183,6 +185,7 @@ namespace Geekbot.net
|
|||
services.AddSingleton<IKvInMemoryStore>(kvMemoryStore);
|
||||
services.AddSingleton<IGlobalSettings>(_globalSettings);
|
||||
services.AddSingleton<IErrorHandler>(errorHandler);
|
||||
services.AddSingleton<IDiceParser>(diceParser);
|
||||
services.AddSingleton<ITranslationHandler>(translationHandler);
|
||||
services.AddSingleton<IReactionListener>(_reactionListener);
|
||||
services.AddSingleton<IGuildSettingsManager>(_guildSettingsManager);
|
||||
|
|
Loading…
Reference in a new issue