From 915a4cc8bfcb47d513219fc7f7163f3bceb85dc3 Mon Sep 17 00:00:00 2001 From: barelyprofessional <150058423+barelyprofessional@users.noreply.github.com> Date: Sat, 16 May 2026 11:34:37 -0500 Subject: [PATCH] Added MathNet.Numerics for Cecil. Refactored and fixed some issues --- .../Commands/{ => Kasino}/CecilCommand.cs | 31 +++++++------- KfChatDotNetBot/KfChatDotNetBot.csproj | 1 + .../Models/DbModels/MoneyDbModels.cs | 3 +- KfChatDotNetBot/Program.cs | 36 ++++++++++++++++ .../{Commands => Services}/Cecil.cs | 41 ++++++++++--------- 5 files changed, 75 insertions(+), 37 deletions(-) rename KfChatDotNetBot/Commands/{ => Kasino}/CecilCommand.cs (82%) rename KfChatDotNetBot/{Commands => Services}/Cecil.cs (62%) diff --git a/KfChatDotNetBot/Commands/CecilCommand.cs b/KfChatDotNetBot/Commands/Kasino/CecilCommand.cs similarity index 82% rename from KfChatDotNetBot/Commands/CecilCommand.cs rename to KfChatDotNetBot/Commands/Kasino/CecilCommand.cs index 74e703f..bd64fba 100644 --- a/KfChatDotNetBot/Commands/CecilCommand.cs +++ b/KfChatDotNetBot/Commands/Kasino/CecilCommand.cs @@ -4,24 +4,24 @@ using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Services; using KfChatDotNetBot.Settings; -using KfChatDotNetWsClient.Models.Events; -using NLog; namespace KfChatDotNetBot.Commands.Kasino; +[KasinoCommand] +[WagerCommand] public class CecilCommand : ICommand { public List Patterns => [ new Regex(@"^cecil (?\d+(?:\.\d+)?) (?\d+(?:\.\d+)?) (?\d+(?:\.\d+)?)", RegexOptions.IgnoreCase), new Regex(@"^cecil (?\d+(?:\.\d+)?) (?\d+(?:\.\d+)?)", RegexOptions.IgnoreCase), new Regex(@"^cecil (?\d+(?:\.\d+)?)", RegexOptions.IgnoreCase), - new Regex("^keno") + new Regex("^cecil") ]; public string? HelpText => "!cecil "; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(60); - public RateLimitOptionsModel? RateLimitOptions => new RateLimitOptionsModel + public RateLimitOptionsModel? RateLimitOptions => new() { MaxInvocations = 5, Window = TimeSpan.FromSeconds(10) @@ -41,7 +41,7 @@ public class CecilCommand : ICommand if (!arguments.TryGetValue("bet", out var amount)) //if user just enters !keno { - await botInstance.SendChatMessageAsync( + await botInstance.ReplyToUser(message, $"{user.FormatUsername()}, not enough arguments. !cecil <[i]optional max win > 1[/i] - Cecil Tool: https://i.ddos.lgbt/raw/CecilHelper.html>", true, autoDeleteAfter: cleanupDelay); RateLimitService.RemoveMostRecentEntry(user, this); @@ -54,14 +54,13 @@ public class CecilCommand : ICommand throw new InvalidOperationException($"Caught a null when retrieving gambler for {user.KfUsername}"); if (gambler.Balance < wager) { - await botInstance.SendChatMessageAsync( + await botInstance.ReplyToUser(message, $"{user.FormatUsername()}, your balance of {await gambler.Balance.FormatKasinoCurrencyAsync()} isn't enough for this wager.", true, autoDeleteAfter: cleanupDelay); RateLimitService.RemoveMostRecentEntry(user, this); return; } - bool beta; double difficulty; double result; @@ -76,32 +75,32 @@ public class CecilCommand : ICommand if (!arguments.TryGetValue("maxwin", out var maxWin)) { - GammaSkew skew = new GammaSkew(difficulty, 0); + var skew = new GammaSkew(difficulty, 0); result = Cecil.Consult(skew, 0); } else { - double mWin = Convert.ToDouble(maxWin.Value); + var mWin = Convert.ToDouble(maxWin.Value); if (mWin < 1) { - await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, max win must be greater than 1.", true, autoDeleteAfter: cleanupDelay); + await botInstance.ReplyToUser(message, $"{user.FormatUsername()}, max win must be greater than 1.", true, autoDeleteAfter: cleanupDelay); return; } - BetaSkew skew = new BetaSkew(difficulty, mWin, 0); - result = Cecil.Consult(skew, 0); + var skew = new BetaSkew(difficulty, mWin, 0); + result = Cecil.Consult(skew); } var payout = wager * Convert.ToDecimal(result); var net = payout - wager; - var newBalance = await Money.NewWagerAsync(gambler.Id, wager, net, WagerGame.Cecil); + var newBalance = await Money.NewWagerAsync(gambler.Id, wager, net, WagerGame.Cecil, ct: ctx); var colors = await SettingsProvider.GetMultipleValuesAsync([ BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor ]); - var red = $"{colors[BuiltIn.Keys.KiwiFarmsRedColor].Value}"; - var green = $"{colors[BuiltIn.Keys.KiwiFarmsGreenColor].Value}"; + var red = colors[BuiltIn.Keys.KiwiFarmsRedColor].Value; + var green = colors[BuiltIn.Keys.KiwiFarmsGreenColor].Value; var color = (payout > wager) ? green : red; - await botInstance.SendChatMessageAsync( + await botInstance.ReplyToUser(message, $"{user.FormatUsername()}, Cecil has determined you are due [color={color}]{await payout.FormatKasinoCurrencyAsync()}[/color] from your wager of {await wager.FormatKasinoCurrencyAsync()}. Balance: {await newBalance.FormatKasinoCurrencyAsync()}", true, autoDeleteAfter: cleanupDelay); diff --git a/KfChatDotNetBot/KfChatDotNetBot.csproj b/KfChatDotNetBot/KfChatDotNetBot.csproj index f58f7cb..bc40a2a 100644 --- a/KfChatDotNetBot/KfChatDotNetBot.csproj +++ b/KfChatDotNetBot/KfChatDotNetBot.csproj @@ -14,6 +14,7 @@ + all diff --git a/KfChatDotNetBot/Models/DbModels/MoneyDbModels.cs b/KfChatDotNetBot/Models/DbModels/MoneyDbModels.cs index 0337c03..4cee1f0 100644 --- a/KfChatDotNetBot/Models/DbModels/MoneyDbModels.cs +++ b/KfChatDotNetBot/Models/DbModels/MoneyDbModels.cs @@ -310,7 +310,8 @@ public enum WagerGame Plinko, [Description("Roulette but live")] Roulette, - Krash + Krash, + Cecil } public enum GamblerState diff --git a/KfChatDotNetBot/Program.cs b/KfChatDotNetBot/Program.cs index 75b436f..c943326 100644 --- a/KfChatDotNetBot/Program.cs +++ b/KfChatDotNetBot/Program.cs @@ -65,6 +65,42 @@ namespace KfChatDotNetBot } await db.SaveChangesAsync(); } + + if (Path.Exists("tags.csv")) + { + logger.Info("Importing from tags.csv"); + var tags = await File.ReadAllTextAsync("tags.csv"); + var i = 0; + foreach (var row in tags.Split(Environment.NewLine)) + { + i++; + var values = row.Split(",", StringSplitOptions.RemoveEmptyEntries); + if (values.Length < 2) + { + logger.Error($"Row {i} does not have enough columns"); + continue; + } + var image = await db.Images.FirstOrDefaultAsync(image => image.Id == Convert.ToInt32(values[0])); + if (image == null) + { + logger.Error($"Row {i} has an unknown image ID"); + continue; + } + + var importTags = values[1].ToLower().Split(" ", + StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(); + if (importTags.Count == 0) + { + logger.Error($"Row {i} has no tags after splitting the string"); + continue; + } + + var newList = image.TagList.Concat(importTags).Distinct().ToList(); + if (newList == image.TagList) continue; + image.TagList = newList; + } + await db.SaveChangesAsync(); + } logger.Info("Handing over to bot now"); Console.OutputEncoding = Encoding.UTF8; _ = new ChatBot(); diff --git a/KfChatDotNetBot/Commands/Cecil.cs b/KfChatDotNetBot/Services/Cecil.cs similarity index 62% rename from KfChatDotNetBot/Commands/Cecil.cs rename to KfChatDotNetBot/Services/Cecil.cs index cd3a79c..9c024cf 100644 --- a/KfChatDotNetBot/Commands/Cecil.cs +++ b/KfChatDotNetBot/Services/Cecil.cs @@ -1,50 +1,51 @@ -using KfChatDotNetBot.Migrations; using MathNet.Numerics; -using KfChatDotNetBot.Services; using MathNet.Numerics.Distributions; using RandN; using RandN.Compat; -namespace KfChatDotNetBot.Commands.Kasino; + +namespace KfChatDotNetBot.Services; public static class Cecil { - private static RandomShim _rand = RandomShim.Create(StandardRng.Create()); + private static readonly RandomShim Rand = RandomShim.Create(StandardRng.Create()); public static double Consult(Skew skew, double minThreshold = 0) { - double r = _rand.NextDouble(); + var r = Rand.NextDouble(); if (r < skew.LossRate) { return 0; } - double winRate = 1 - skew.LossRate; - double scaledR = (r - skew.LossRate) / winRate; + var winRate = 1 - skew.LossRate; + var scaledR = (r - skew.LossRate) / winRate; double baseResult; - if (skew is BetaSkew betaSkew) + switch (skew) { - baseResult = Beta.InvCDF(betaSkew.Weight, betaSkew.Beta, scaledR) * betaSkew.CalibratedMaxWin; + case BetaSkew betaSkew: + baseResult = Beta.InvCDF(betaSkew.Weight, betaSkew.Beta, scaledR) * betaSkew.CalibratedMaxWin; + break; + case GammaSkew gammaSkew: + baseResult = Gamma.InvCDF(gammaSkew.Weight, gammaSkew.Weight, scaledR); + break; + default: + return 0; } - else if (skew is GammaSkew gammaSkew) - { - baseResult = Gamma.InvCDF(gammaSkew.Weight, gammaSkew.Weight, scaledR); - } - else return 0; baseResult /= winRate; if (minThreshold == 0) return baseResult; - double limit = (skew is BetaSkew b) ? b.MaxWin : double.MaxValue; + var limit = (skew is BetaSkew b) ? b.MaxWin : double.MaxValue; return Math.Min(Round(baseResult, minThreshold), limit); double Round(double baseR, double minT) { - double lower = Math.Floor(baseR / minT) * minT; - double upper = lower + minT; - double roundChance = (baseR - lower) / minT; - return (_rand.NextDouble() < roundChance) ? lower : upper; + var lower = Math.Floor(baseR / minT) * minT; + var upper = lower + minT; + var roundChance = (baseR - lower) / minT; + return (Rand.NextDouble() < roundChance) ? lower : upper; } } @@ -82,7 +83,7 @@ public class BetaSkew : Skew public override void Calibrate(double winRate) { CalibratedMaxWin = MaxWin * winRate; - double normalizer = TargetEv / CalibratedMaxWin; + var normalizer = TargetEv / CalibratedMaxWin; Alpha = normalizer * Weight; Beta = (1 - normalizer) * Weight; }