mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-06-17 09:45:17 -04:00
Added MathNet.Numerics for Cecil. Refactored and fixed some issues
This commit is contained in:
+15
-16
@@ -4,24 +4,24 @@ using KfChatDotNetBot.Models;
|
|||||||
using KfChatDotNetBot.Models.DbModels;
|
using KfChatDotNetBot.Models.DbModels;
|
||||||
using KfChatDotNetBot.Services;
|
using KfChatDotNetBot.Services;
|
||||||
using KfChatDotNetBot.Settings;
|
using KfChatDotNetBot.Settings;
|
||||||
using KfChatDotNetWsClient.Models.Events;
|
|
||||||
using NLog;
|
|
||||||
|
|
||||||
namespace KfChatDotNetBot.Commands.Kasino;
|
namespace KfChatDotNetBot.Commands.Kasino;
|
||||||
|
|
||||||
|
[KasinoCommand]
|
||||||
|
[WagerCommand]
|
||||||
public class CecilCommand : ICommand
|
public class CecilCommand : ICommand
|
||||||
{
|
{
|
||||||
public List<Regex> Patterns => [
|
public List<Regex> Patterns => [
|
||||||
new Regex(@"^cecil (?<bet>\d+(?:\.\d+)?) (?<difficulty>\d+(?:\.\d+)?) (?<maxwin>\d+(?:\.\d+)?)", RegexOptions.IgnoreCase),
|
new Regex(@"^cecil (?<bet>\d+(?:\.\d+)?) (?<difficulty>\d+(?:\.\d+)?) (?<maxwin>\d+(?:\.\d+)?)", RegexOptions.IgnoreCase),
|
||||||
new Regex(@"^cecil (?<bet>\d+(?:\.\d+)?) (?<difficulty>\d+(?:\.\d+)?)", RegexOptions.IgnoreCase),
|
new Regex(@"^cecil (?<bet>\d+(?:\.\d+)?) (?<difficulty>\d+(?:\.\d+)?)", RegexOptions.IgnoreCase),
|
||||||
new Regex(@"^cecil (?<bet>\d+(?:\.\d+)?)", RegexOptions.IgnoreCase),
|
new Regex(@"^cecil (?<bet>\d+(?:\.\d+)?)", RegexOptions.IgnoreCase),
|
||||||
new Regex("^keno")
|
new Regex("^cecil")
|
||||||
];
|
];
|
||||||
|
|
||||||
public string? HelpText => "!cecil <bet> <optional difficulty> <optional max win>";
|
public string? HelpText => "!cecil <bet> <optional difficulty> <optional max win>";
|
||||||
public UserRight RequiredRight => UserRight.Loser;
|
public UserRight RequiredRight => UserRight.Loser;
|
||||||
public TimeSpan Timeout => TimeSpan.FromSeconds(60);
|
public TimeSpan Timeout => TimeSpan.FromSeconds(60);
|
||||||
public RateLimitOptionsModel? RateLimitOptions => new RateLimitOptionsModel
|
public RateLimitOptionsModel? RateLimitOptions => new()
|
||||||
{
|
{
|
||||||
MaxInvocations = 5,
|
MaxInvocations = 5,
|
||||||
Window = TimeSpan.FromSeconds(10)
|
Window = TimeSpan.FromSeconds(10)
|
||||||
@@ -41,7 +41,7 @@ public class CecilCommand : ICommand
|
|||||||
|
|
||||||
if (!arguments.TryGetValue("bet", out var amount)) //if user just enters !keno
|
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 <bet> <optional difficulty> <[i]optional max win > 1[/i] - Cecil Tool: https://i.ddos.lgbt/raw/CecilHelper.html>",
|
$"{user.FormatUsername()}, not enough arguments. !cecil <bet> <optional difficulty> <[i]optional max win > 1[/i] - Cecil Tool: https://i.ddos.lgbt/raw/CecilHelper.html>",
|
||||||
true, autoDeleteAfter: cleanupDelay);
|
true, autoDeleteAfter: cleanupDelay);
|
||||||
RateLimitService.RemoveMostRecentEntry(user, this);
|
RateLimitService.RemoveMostRecentEntry(user, this);
|
||||||
@@ -54,14 +54,13 @@ public class CecilCommand : ICommand
|
|||||||
throw new InvalidOperationException($"Caught a null when retrieving gambler for {user.KfUsername}");
|
throw new InvalidOperationException($"Caught a null when retrieving gambler for {user.KfUsername}");
|
||||||
if (gambler.Balance < wager)
|
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.",
|
$"{user.FormatUsername()}, your balance of {await gambler.Balance.FormatKasinoCurrencyAsync()} isn't enough for this wager.",
|
||||||
true, autoDeleteAfter: cleanupDelay);
|
true, autoDeleteAfter: cleanupDelay);
|
||||||
RateLimitService.RemoveMostRecentEntry(user, this);
|
RateLimitService.RemoveMostRecentEntry(user, this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool beta;
|
|
||||||
double difficulty;
|
double difficulty;
|
||||||
|
|
||||||
double result;
|
double result;
|
||||||
@@ -76,32 +75,32 @@ public class CecilCommand : ICommand
|
|||||||
|
|
||||||
if (!arguments.TryGetValue("maxwin", out var maxWin))
|
if (!arguments.TryGetValue("maxwin", out var maxWin))
|
||||||
{
|
{
|
||||||
GammaSkew skew = new GammaSkew(difficulty, 0);
|
var skew = new GammaSkew(difficulty, 0);
|
||||||
result = Cecil.Consult(skew, 0);
|
result = Cecil.Consult(skew, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double mWin = Convert.ToDouble(maxWin.Value);
|
var mWin = Convert.ToDouble(maxWin.Value);
|
||||||
if (mWin < 1)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
BetaSkew skew = new BetaSkew(difficulty, mWin, 0);
|
var skew = new BetaSkew(difficulty, mWin, 0);
|
||||||
result = Cecil.Consult(skew, 0);
|
result = Cecil.Consult(skew);
|
||||||
}
|
}
|
||||||
|
|
||||||
var payout = wager * Convert.ToDecimal(result);
|
var payout = wager * Convert.ToDecimal(result);
|
||||||
var net = payout - wager;
|
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 =
|
var colors =
|
||||||
await SettingsProvider.GetMultipleValuesAsync([
|
await SettingsProvider.GetMultipleValuesAsync([
|
||||||
BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor
|
BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor
|
||||||
]);
|
]);
|
||||||
var red = $"{colors[BuiltIn.Keys.KiwiFarmsRedColor].Value}";
|
var red = colors[BuiltIn.Keys.KiwiFarmsRedColor].Value;
|
||||||
var green = $"{colors[BuiltIn.Keys.KiwiFarmsGreenColor].Value}";
|
var green = colors[BuiltIn.Keys.KiwiFarmsGreenColor].Value;
|
||||||
var color = (payout > wager) ? green : red;
|
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()}",
|
$"{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);
|
true, autoDeleteAfter: cleanupDelay);
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
<PackageReference Include="Homoglyphic" Version="2.0.1" />
|
<PackageReference Include="Homoglyphic" Version="2.0.1" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="3.0.1" />
|
<PackageReference Include="Humanizer.Core" Version="3.0.1" />
|
||||||
|
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -310,7 +310,8 @@ public enum WagerGame
|
|||||||
Plinko,
|
Plinko,
|
||||||
[Description("Roulette but live")]
|
[Description("Roulette but live")]
|
||||||
Roulette,
|
Roulette,
|
||||||
Krash
|
Krash,
|
||||||
|
Cecil
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum GamblerState
|
public enum GamblerState
|
||||||
|
|||||||
@@ -65,6 +65,42 @@ namespace KfChatDotNetBot
|
|||||||
}
|
}
|
||||||
await db.SaveChangesAsync();
|
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");
|
logger.Info("Handing over to bot now");
|
||||||
Console.OutputEncoding = Encoding.UTF8;
|
Console.OutputEncoding = Encoding.UTF8;
|
||||||
_ = new ChatBot();
|
_ = new ChatBot();
|
||||||
|
|||||||
@@ -1,50 +1,51 @@
|
|||||||
using KfChatDotNetBot.Migrations;
|
|
||||||
using MathNet.Numerics;
|
using MathNet.Numerics;
|
||||||
using KfChatDotNetBot.Services;
|
|
||||||
using MathNet.Numerics.Distributions;
|
using MathNet.Numerics.Distributions;
|
||||||
using RandN;
|
using RandN;
|
||||||
using RandN.Compat;
|
using RandN.Compat;
|
||||||
namespace KfChatDotNetBot.Commands.Kasino;
|
|
||||||
|
namespace KfChatDotNetBot.Services;
|
||||||
|
|
||||||
public static class Cecil
|
public static class Cecil
|
||||||
{
|
{
|
||||||
private static RandomShim<StandardRng> _rand = RandomShim.Create<StandardRng>(StandardRng.Create());
|
private static readonly RandomShim<StandardRng> Rand = RandomShim.Create(StandardRng.Create());
|
||||||
public static double Consult(Skew skew, double minThreshold = 0)
|
public static double Consult(Skew skew, double minThreshold = 0)
|
||||||
{
|
{
|
||||||
double r = _rand.NextDouble();
|
var r = Rand.NextDouble();
|
||||||
if (r < skew.LossRate)
|
if (r < skew.LossRate)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
double winRate = 1 - skew.LossRate;
|
var winRate = 1 - skew.LossRate;
|
||||||
double scaledR = (r - skew.LossRate) / winRate;
|
var scaledR = (r - skew.LossRate) / winRate;
|
||||||
|
|
||||||
double baseResult;
|
double baseResult;
|
||||||
if (skew is BetaSkew betaSkew)
|
switch (skew)
|
||||||
{
|
{
|
||||||
|
case BetaSkew betaSkew:
|
||||||
baseResult = Beta.InvCDF(betaSkew.Weight, betaSkew.Beta, scaledR) * betaSkew.CalibratedMaxWin;
|
baseResult = Beta.InvCDF(betaSkew.Weight, betaSkew.Beta, scaledR) * betaSkew.CalibratedMaxWin;
|
||||||
}
|
break;
|
||||||
else if (skew is GammaSkew gammaSkew)
|
case GammaSkew gammaSkew:
|
||||||
{
|
|
||||||
baseResult = Gamma.InvCDF(gammaSkew.Weight, gammaSkew.Weight, scaledR);
|
baseResult = Gamma.InvCDF(gammaSkew.Weight, gammaSkew.Weight, scaledR);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
else return 0;
|
|
||||||
|
|
||||||
baseResult /= winRate;
|
baseResult /= winRate;
|
||||||
|
|
||||||
if (minThreshold == 0) return baseResult;
|
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);
|
return Math.Min(Round(baseResult, minThreshold), limit);
|
||||||
|
|
||||||
double Round(double baseR, double minT)
|
double Round(double baseR, double minT)
|
||||||
{
|
{
|
||||||
double lower = Math.Floor(baseR / minT) * minT;
|
var lower = Math.Floor(baseR / minT) * minT;
|
||||||
double upper = lower + minT;
|
var upper = lower + minT;
|
||||||
double roundChance = (baseR - lower) / minT;
|
var roundChance = (baseR - lower) / minT;
|
||||||
return (_rand.NextDouble() < roundChance) ? lower : upper;
|
return (Rand.NextDouble() < roundChance) ? lower : upper;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,7 @@ public class BetaSkew : Skew
|
|||||||
public override void Calibrate(double winRate)
|
public override void Calibrate(double winRate)
|
||||||
{
|
{
|
||||||
CalibratedMaxWin = MaxWin * winRate;
|
CalibratedMaxWin = MaxWin * winRate;
|
||||||
double normalizer = TargetEv / CalibratedMaxWin;
|
var normalizer = TargetEv / CalibratedMaxWin;
|
||||||
Alpha = normalizer * Weight;
|
Alpha = normalizer * Weight;
|
||||||
Beta = (1 - normalizer) * Weight;
|
Beta = (1 - normalizer) * Weight;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user