mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-05-13 09:32:44 -04:00
@@ -0,0 +1,120 @@
|
|||||||
|
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<Regex> Patterns => [
|
||||||
|
new Regex(@"dice (?<amount>\d+)$", RegexOptions.IgnoreCase),
|
||||||
|
new Regex(@"^dice (?<amount>\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 => new()
|
||||||
|
{
|
||||||
|
MaxInvocations = 3,
|
||||||
|
Window = TimeSpan.FromSeconds(30)
|
||||||
|
};
|
||||||
|
|
||||||
|
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<int>());
|
||||||
|
if (!arguments.TryGetValue("amount", out var amount))
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, not enough arguments. !dice <wager>",
|
||||||
|
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
|
||||||
|
]);
|
||||||
|
// print dice game slider
|
||||||
|
await botInstance.SendChatMessageAsync($"{ConstructDiceGameOutput(rolled)}",true, autoDeleteAfter: cleanupDelay);
|
||||||
|
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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -439,6 +439,28 @@ public static class Money
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get random number double [0, 1]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gambler">Gambler entity to reference their random seed</param>
|
||||||
|
/// <param name="iterations">Number of random number generator iterations to run before returning a result</param>
|
||||||
|
/// <returns>A random number based on the given parameters</returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the user's current VIP level
|
/// Get the user's current VIP level
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user