From bdeb2acdf85d4b7a3bf938d371cd7151b32b5080 Mon Sep 17 00:00:00 2001 From: CrackmaticSoftware <248342529+CrackmaticSoftware@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:51:17 +0100 Subject: [PATCH] added dice game. added random double generator. --- .../Commands/Kasino/DiceCommand.cs | 114 ++++++++++++++++++ KfChatDotNetBot/Services/Money.cs | 22 ++++ 2 files changed, 136 insertions(+) create mode 100644 KfChatDotNetBot/Commands/Kasino/DiceCommand.cs diff --git a/KfChatDotNetBot/Commands/Kasino/DiceCommand.cs b/KfChatDotNetBot/Commands/Kasino/DiceCommand.cs new file mode 100644 index 0000000..e6c62e6 --- /dev/null +++ b/KfChatDotNetBot/Commands/Kasino/DiceCommand.cs @@ -0,0 +1,114 @@ +using System.Text.RegularExpressions; +using KfChatDotNetBot.Extensions; +using KfChatDotNetBot.Models; +using KfChatDotNetBot.Models.DbModels; +using KfChatDotNetBot.Services; +using KfChatDotNetBot.Settings; +using KfChatDotNetWsClient.Models.Events; + +namespace KfChatDotNetBot.Commands.Kasino; + +[KasinoCommand] +[WagerCommand] +public class DiceCommand : ICommand +{ + public List Patterns => [ + new Regex(@"dice (?\d+)$", RegexOptions.IgnoreCase), + new Regex(@"^dice (?\d+\.\d+)$", RegexOptions.IgnoreCase) + ]; + + public string? HelpText => "!dice, roll the dice (not really, you roll between 0 - 100)"; + public UserRight RequiredRight => UserRight.Loser; + public TimeSpan Timeout => TimeSpan.FromSeconds(5); + public RateLimitOptionsModel? RateLimitOptions => null; + + private static double _houseEdge = 0.05; // house edge hack? are we doing perfect 50/50 games? + + public async Task RunCommand(ChatBot botInstance, MessageModel messagen, UserDbModel user, GroupCollection arguments, + CancellationToken ctx) + { + var cleanupDelay = TimeSpan.FromMilliseconds((await SettingsProvider.GetValueAsync(BuiltIn.Keys.KasinoKenoCleanupDelay)).ToType()); + if (!arguments.TryGetValue("amount", out var amount)) + { + await botInstance.SendChatMessageAsync( + $"{user.FormatUsername()}, not enough arguments. !dice ", + true, autoDeleteAfter: cleanupDelay); + return; + } + var wager = Convert.ToDecimal(amount.Value); + var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); + if (gambler == null) + throw new InvalidOperationException($"Caught a null when retrieving gambler for {user.KfUsername}"); + if (gambler.Balance < wager) + { + await botInstance.SendChatMessageAsync( + $"{user.FormatUsername()}, your balance of {await gambler.Balance.FormatKasinoCurrencyAsync()} isn't enough for this wager.", + true, autoDeleteAfter: cleanupDelay); + return; + } + var rolled = Money.GetRandomDouble(gambler); + decimal newBalance; + var colors = + await SettingsProvider.GetMultipleValuesAsync([ + BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor + ]); + if (rolled > 0.5 + _houseEdge) + { + // you win dice + var effect = wager; + await Money.NewWagerAsync(gambler.Id, wager, effect, WagerGame.Dice, ct: ctx); + newBalance = gambler.Balance + effect; + await botInstance.SendChatMessageAsync( + $"{user.FormatUsername()}, you rolled a {rolled * 100:N2} and [B][COLOR={colors[BuiltIn.Keys.KiwiFarmsGreenColor].Value}]WON![/COLOR][/B] " + + $"You won {await effect.FormatKasinoCurrencyAsync()} and your balance is now {await newBalance.FormatKasinoCurrencyAsync()}", + true, autoDeleteAfter: cleanupDelay); + } + else + { + // you lose dice + await Money.NewWagerAsync(gambler.Id, wager, -wager, WagerGame.Dice, ct: ctx); + newBalance = gambler.Balance - wager; + await botInstance.SendChatMessageAsync( + $"{user.FormatUsername()}, you rolled a {rolled:0:00} and [B][COLOR={colors[BuiltIn.Keys.KiwiFarmsRedColor].Value}]LOST![/COLOR][/B] " + + $"Your balance is now {await newBalance.FormatKasinoCurrencyAsync()}", + true, autoDeleteAfter: cleanupDelay); + } + } + + private static string ConstructDiceGameOutput(double rolled) + { + // returns two rows as one string + // row one has dice emoji shifted usisng spaces by appropriate ammount accourding to rolled + // second row constructs the dice "meter" according to game specs + var diceEmoji = "🎲"; + var invisibleAsciiSpace = "⠀"; //U+2800 + // 21 asciispaces fills the display, 20 dashes and one | fills the meter + // rolled * 21 * invisible ascii space creates the illusion of dice being alligned with rolled number + var toShift = (int)Math.Round(21 * rolled); + string diceDisplayShifted = String.Concat(Enumerable.Repeat(invisibleAsciiSpace, toShift)); + diceDisplayShifted += diceEmoji; + + const int DICE_METER_LENGTH = 20; // uhh this should influence how much the dice emoji is shifted as declared before just leave at 20 for now + const string DICE_METER_LEFT = "─"; + const string DICE_METER_LEFT_COLOR = "#f1323e"; // red for lose + const string DICE_METER_MIDDLE = "┃"; + const string DICE_METER_MIDDLE_COLOR = "#886cff"; // I forgot what this color is + const string DICE_METER_RIGHT = "─"; + const string DICE_METER_RIGHT_COLOR = "#3dd179"; // green for win + + string diceMeter = ""; + + // TODO: construct rigged dice display here + + // no rig scenario + diceMeter += $"[B][COLOR={DICE_METER_LEFT_COLOR}]"; + diceMeter += String.Concat(Enumerable.Repeat(DICE_METER_LEFT, DICE_METER_LENGTH / 2)); // --------- + diceMeter += "[/COLOR]"; + diceMeter += $"[COLOR={DICE_METER_MIDDLE_COLOR}]{DICE_METER_MIDDLE}[/COLOR]"; // | + diceMeter += $"[COLOR={DICE_METER_RIGHT_COLOR}]"; + diceMeter += String.Concat(Enumerable.Repeat(DICE_METER_RIGHT, DICE_METER_LENGTH / 2)); // -------- + diceMeter += "[/COLOR][/B]"; + + return $"{diceDisplayShifted}\n{diceMeter}"; + } +} \ No newline at end of file diff --git a/KfChatDotNetBot/Services/Money.cs b/KfChatDotNetBot/Services/Money.cs index a3ea3c8..259c15b 100644 --- a/KfChatDotNetBot/Services/Money.cs +++ b/KfChatDotNetBot/Services/Money.cs @@ -439,6 +439,28 @@ public static class Money return result; } + /// + /// Get random number double [0, 1] + /// + /// Gambler entity to reference their random seed + /// Number of random number generator iterations to run before returning a result + /// A random number based on the given parameters + /// + public static double GetRandomDouble(GamblerDbModel gambler, int iterations = 10) + { + var rng = StandardRng.Create(); + var random = RandomShim.Create(rng); + var result = 0.0; + var i = 0; + if (iterations <= 0) throw new ArgumentException("Iterations cannot be 0 or lower"); + while (i < iterations) + { + i++; + result = random.NextDouble(); + } + return result; + } + /// /// Get the user's current VIP level ///