Refactored krash

This commit is contained in:
barelyprofessional
2026-04-06 21:13:19 -05:00
parent 26e0b1f49f
commit 5e85566577
3 changed files with 70 additions and 79 deletions

View File

@@ -67,7 +67,7 @@ public class KrashBetCommand : ICommand
true, autoDeleteAfter: TimeSpan.FromSeconds(5)); true, autoDeleteAfter: TimeSpan.FromSeconds(5));
return; return;
} }
if (botInstance.BotServices.KasinoKrash.theGame == null) if (botInstance.BotServices.KasinoKrash.TheGame == null)
{ {
//start a new game //start a new game
await botInstance.BotServices.KasinoKrash.StartGame(gambler, wager, multi); await botInstance.BotServices.KasinoKrash.StartGame(gambler, wager, multi);

View File

@@ -416,7 +416,8 @@ public enum WagerGame
[Description("Plinko")] [Description("Plinko")]
Plinko, Plinko,
[Description("Roulette but live")] [Description("Roulette but live")]
Roulette Roulette,
Krash
} }
public enum GamblerState public enum GamblerState

View File

@@ -1,12 +1,9 @@
using System.Text.Json; using System.Text.Json;
using KfChatDotNetBot.Extensions; using KfChatDotNetBot.Extensions;
using KfChatDotNetBot.Models;
using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Models.DbModels;
using KfChatDotNetBot.Settings; using KfChatDotNetBot.Settings;
using NLog; using NLog;
using StackExchange.Redis; using StackExchange.Redis;
using System.Text.Json.Serialization;
using KfChatDotNetBot.Commands.Kasino;
using RandN; using RandN;
using RandN.Compat; using RandN.Compat;
@@ -19,8 +16,7 @@ public class KasinoKrash : IDisposable
private IDatabase? _redisDb; private IDatabase? _redisDb;
private ChatBot _kfChatBot; private ChatBot _kfChatBot;
private CancellationToken _ct; private CancellationToken _ct;
public decimal HOUSE_EDGE = 0.98m; public KasinoKrashModel? TheGame;
public KasinoKrashModel? theGame;
public KasinoKrash(ChatBot kfChatBot, CancellationToken ct = default) //the service itself public KasinoKrash(ChatBot kfChatBot, CancellationToken ct = default) //the service itself
@@ -37,8 +33,8 @@ public class KasinoKrash : IDisposable
var redis = ConnectionMultiplexer.Connect(connectionString.Value); var redis = ConnectionMultiplexer.Connect(connectionString.Value);
_redisDb = redis.GetDatabase(); _redisDb = redis.GetDatabase();
//attempt to pull a game from the db in case the bot crashed while a game was ongoing. if so it will restart the run //attempt to pull a game from the db in case the bot crashed while a game was ongoing. if so it will restart the run
theGame = GetKrashState().Result; TheGame = GetKrashState().Result;
if (theGame != null) _ = RunGame(); if (TheGame != null) _ = RunGame();
} }
public bool IsInitialized() public bool IsInitialized()
{ {
@@ -56,7 +52,7 @@ public class KasinoKrash : IDisposable
{ {
if (_redisDb == null) throw new InvalidOperationException("Kasino krash service isn't initialized"); if (_redisDb == null) throw new InvalidOperationException("Kasino krash service isn't initialized");
await _redisDb.KeyDeleteAsync("Krash.State"); await _redisDb.KeyDeleteAsync("Krash.State");
theGame = null; TheGame = null;
} }
public async Task SaveKrashState(KasinoKrashModel krash) public async Task SaveKrashState(KasinoKrashModel krash)
{ {
@@ -67,78 +63,72 @@ public class KasinoKrash : IDisposable
public async Task AttemptKrash(GamblerDbModel gambler) public async Task AttemptKrash(GamblerDbModel gambler)
{ {
if (theGame == null) if (TheGame == null)
{ {
throw new InvalidOperationException("Failed to retrieve state or no krash is in progress"); throw new InvalidOperationException("Failed to retrieve state or no krash is in progress");
} }
if (!theGame.bets.Any(x => x.gambler.User.KfId == gambler.User.KfId)) return; if (TheGame.Bets.All(x => x.Gambler.User.KfId != gambler.User.KfId)) return;
if (!theGame.krashAccepted) return; if (!TheGame.KrashAccepted) return;
//find which bet is yours //find which bet is yours
int index = 0; var index = TheGame.Bets.TakeWhile(bet => bet.Gambler.User.KfId != gambler.User.KfId).Count();
foreach (var bet in theGame.bets)
{
if (bet.gambler.User.KfId == gambler.User.KfId) break;
index++;
}
var krashBet = theGame.bets[index]; var krashBet = TheGame.Bets[index];
theGame.bets.RemoveAt(index); TheGame.Bets.RemoveAt(index);
decimal payout = theGame.currentMulti * krashBet.wager - krashBet.wager; var payout = TheGame.CurrentMulti * krashBet.Wager - krashBet.Wager;
var newBalance = await Money.NewWagerAsync(krashBet.gambler.Id, krashBet.wager, payout, WagerGame.Krash); var newBalance = await Money.NewWagerAsync(krashBet.Gambler.Id, krashBet.Wager, payout, WagerGame.Krash, ct: _ct);
await _kfChatBot.SendChatMessageAsync( await _kfChatBot.SendChatMessageAsync(
$"{krashBet.gambler.User.FormatUsername()}, you [color=limegreen][b]won[/b][/color] {await payout.FormatKasinoCurrencyAsync()}!", $"{krashBet.Gambler.User.FormatUsername()}, you [color=limegreen][b]won[/b][/color] {await payout.FormatKasinoCurrencyAsync()}!",
true, autoDeleteAfter: TimeSpan.FromSeconds(10)); true, autoDeleteAfter: TimeSpan.FromSeconds(10));
if (_kfChatBot.BotServices.KasinoShop != null) if (_kfChatBot.BotServices.KasinoShop != null)
{ {
await _kfChatBot.BotServices.KasinoShop.ProcessWagerTracking(krashBet.gambler, WagerGame.Krash, krashBet.wager, await _kfChatBot.BotServices.KasinoShop.ProcessWagerTracking(krashBet.Gambler, WagerGame.Krash, krashBet.Wager,
payout, newBalance); payout, newBalance);
} }
await SaveKrashState(theGame); await SaveKrashState(TheGame);
} }
public async Task AddParticipant(GamblerDbModel gambler, decimal wager, decimal multi = -1) public async Task AddParticipant(GamblerDbModel gambler, decimal wager, decimal multi = -1)
{ {
if (theGame == null) if (TheGame == null)
{ {
theGame = await GetKrashState(); TheGame = await GetKrashState();
if (theGame == null) throw new InvalidOperationException("Failed to retrieve state or no krash is in progress"); if (TheGame == null) throw new InvalidOperationException("Failed to retrieve state or no krash is in progress");
_ = RunGame(); _ = RunGame();
} }
if (theGame.bets.Any(x => x.gambler.User.KfId == gambler.User.KfId)) return; if (TheGame.Bets.Any(x => x.Gambler.User.KfId == gambler.User.KfId)) return;
if (!theGame.betsAccepted) return; if (!TheGame.BetsAccepted) return;
KrashBet bet = new KrashBet{gambler = gambler, wager = wager, multi = multi}; var bet = new KrashBet{Gambler = gambler, Wager = wager, Multi = multi};
theGame.bets.Add(bet); TheGame.Bets.Add(bet);
await SaveKrashState(theGame); await SaveKrashState(TheGame);
} }
public async Task StartGame(GamblerDbModel creator, decimal wager, decimal multi = -1) public async Task StartGame(GamblerDbModel creator, decimal wager, decimal multi = -1)
{ {
theGame = new KasinoKrashModel(creator); TheGame = new KasinoKrashModel(creator);
theGame.bets.Add(new KrashBet{gambler = creator, wager = wager, multi = multi}); TheGame.Bets.Add(new KrashBet{Gambler = creator, Wager = wager, Multi = multi});
await SaveKrashState(theGame); await SaveKrashState(TheGame);
_ = RunGame(); _ = RunGame();
} }
public async Task RunGame() //running the actual game public async Task RunGame() //running the actual game
{ {
if (theGame == null) if (TheGame == null)
{ {
await RemoveKrashState(); await RemoveKrashState();
await _kfChatBot.SendChatMessageAsync("Krash error 1", true); await _kfChatBot.SendChatMessageAsync("Krash error 1", true);
return; return;
} }
var msg = await _kfChatBot.SendChatMessageAsync( var msg = await _kfChatBot.SendChatMessageAsync(
$"{theGame.creator.User.FormatUsername()} started a Krash! You have 30 seconds to place your bets.", true); $"{TheGame.Creator.User.FormatUsername()} started a Krash! You have 30 seconds to place your bets.", true);
TimeSpan preGameTimer = TimeSpan.FromSeconds(30); var preGameTimer = TimeSpan.FromSeconds(30);
TimeSpan interval = TimeSpan.FromSeconds(1); var interval = TimeSpan.FromSeconds(1);
var timer = new PeriodicTimer(interval); var timer = new PeriodicTimer(interval);
string bets;
while (await timer.WaitForNextTickAsync(_ct)) //timer before starting the game while (await timer.WaitForNextTickAsync(_ct)) //timer before starting the game
{ {
bets = ""; var bets = "";
foreach (var bet in theGame.bets) bets += $"{bet.gambler.User.FormatUsername()} is betting {bet.wager}[br]"; foreach (var bet in TheGame.Bets) bets += $"{bet.Gambler.User.FormatUsername()} is betting {bet.Wager}[br]";
await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageUuid, await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageUuid,
$"{theGame.creator.User.FormatUsername()} started a Krash! You have {preGameTimer} to place your bets.[br]{bets}"); $"{TheGame.Creator.User.FormatUsername()} started a Krash! You have {preGameTimer} to place your bets.[br]{bets}");
preGameTimer -= interval; preGameTimer -= interval;
if (preGameTimer <= TimeSpan.Zero) if (preGameTimer <= TimeSpan.Zero)
{ {
@@ -147,56 +137,56 @@ public class KasinoKrash : IDisposable
} }
//any bets placed after this point will be cancelled, must wait until the last game finishes to start a new one. //any bets placed after this point will be cancelled, must wait until the last game finishes to start a new one.
theGame.betsAccepted = false; TheGame.BetsAccepted = false;
await SaveKrashState(theGame); await SaveKrashState(TheGame);
//start the display of the game //start the display of the game
//change these to change the speed of the game //change these to change the speed of the game
decimal growthRate = 1.02m; var growthRate = 1.02m;
decimal growthAcceleration = 1.00185m; var growthAcceleration = 1.00185m;
await _kfChatBot.KfClient.DeleteMessageAsync(msg.ChatMessageUuid!); await _kfChatBot.KfClient.DeleteMessageAsync(msg.ChatMessageUuid!);
msg = await _kfChatBot.SendChatMessageAsync($"[center][b][size=200][color=limegreen]{theGame.currentMulti}x"); msg = await _kfChatBot.SendChatMessageAsync($"[center][b][size=200][color=limegreen]{TheGame.CurrentMulti}x");
decimal defaultGrowth = 0.01m; var defaultGrowth = 0.01m;
interval = TimeSpan.FromSeconds(0.1); interval = TimeSpan.FromSeconds(0.1);
timer = new PeriodicTimer(interval); timer = new PeriodicTimer(interval);
while (await timer.WaitForNextTickAsync(_ct)) while (await timer.WaitForNextTickAsync(_ct))
{ {
await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageUuid!, $"[center][b][size=200][color=limegreen]{theGame.currentMulti}x"); await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageUuid!, $"[center][b][size=200][color=limegreen]{TheGame.CurrentMulti}x");
theGame.currentMulti += defaultGrowth; TheGame.CurrentMulti += defaultGrowth;
defaultGrowth *= growthRate; defaultGrowth *= growthRate;
growthRate *= growthAcceleration; growthRate *= growthAcceleration;
if (theGame.currentMulti >= theGame.finalMulti) break; if (TheGame.CurrentMulti >= TheGame.FinalMulti) break;
} }
//at this point the game crashes and everybody who did not cash out or pre bet on a multi will have balance subtracted, winners will be paid out. //at this point the game crashes and everybody who did not cash out or pre bet on a multi will have balance subtracted, winners will be paid out.
await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageUuid!, $"[center][b][size=200][color=red]{theGame.finalMulti}x"); await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageUuid!, $"[center][b][size=200][color=red]{TheGame.FinalMulti}x");
foreach (var bet in theGame.bets) foreach (var bet in TheGame.Bets)
{ {
if (bet.multi <= theGame.finalMulti) if (bet.Multi <= TheGame.FinalMulti)
{ {
//you win //you win
decimal payout = theGame.currentMulti * bet.wager - bet.wager; var payout = TheGame.CurrentMulti * bet.Wager - bet.Wager;
var newBalance = await Money.NewWagerAsync(bet.gambler.Id, bet.wager, payout, WagerGame.Krash); var newBalance = await Money.NewWagerAsync(bet.Gambler.Id, bet.Wager, payout, WagerGame.Krash, ct: _ct);
await _kfChatBot.SendChatMessageAsync( await _kfChatBot.SendChatMessageAsync(
$"{bet.gambler.User.FormatUsername()}, you [color=limegreen][b]won[/b][/color] {await payout.FormatKasinoCurrencyAsync()}!", $"{bet.Gambler.User.FormatUsername()}, you [color=limegreen][b]won[/b][/color] {await payout.FormatKasinoCurrencyAsync()}!",
true, autoDeleteAfter: TimeSpan.FromSeconds(10)); true, autoDeleteAfter: TimeSpan.FromSeconds(10));
if (_kfChatBot.BotServices.KasinoShop != null) if (_kfChatBot.BotServices.KasinoShop != null)
{ {
await _kfChatBot.BotServices.KasinoShop.ProcessWagerTracking(bet.gambler, WagerGame.Krash, bet.wager, await _kfChatBot.BotServices.KasinoShop.ProcessWagerTracking(bet.Gambler, WagerGame.Krash, bet.Wager,
payout, newBalance); payout, newBalance);
} }
} }
else else
{ {
//automatically lose, no pre entered multi or it was greater than the final multi and failed to cash out //automatically lose, no pre entered multi or it was greater than the final multi and failed to cash out
var newBalance = await Money.NewWagerAsync(bet.gambler.Id, bet.wager, -bet.wager, WagerGame.Krash); var newBalance = await Money.NewWagerAsync(bet.Gambler.Id, bet.Wager, -bet.Wager, WagerGame.Krash, ct: _ct);
await _kfChatBot.SendChatMessageAsync( await _kfChatBot.SendChatMessageAsync(
$"{bet.gambler.User.FormatUsername()}, you [color=red][b]lost[/b][/color] {await bet.wager.FormatKasinoCurrencyAsync()}!", $"{bet.Gambler.User.FormatUsername()}, you [color=red][b]lost[/b][/color] {await bet.Wager.FormatKasinoCurrencyAsync()}!",
true, autoDeleteAfter: TimeSpan.FromSeconds(10)); true, autoDeleteAfter: TimeSpan.FromSeconds(10));
if (_kfChatBot.BotServices.KasinoShop != null) if (_kfChatBot.BotServices.KasinoShop != null)
{ {
await _kfChatBot.BotServices.KasinoShop.ProcessWagerTracking(bet.gambler, WagerGame.Krash, bet.wager, await _kfChatBot.BotServices.KasinoShop.ProcessWagerTracking(bet.Gambler, WagerGame.Krash, bet.Wager,
-bet.wager, newBalance); -bet.Wager, newBalance);
} }
} }
} }
@@ -210,26 +200,26 @@ public class KasinoKrash : IDisposable
public class KasinoKrashModel public class KasinoKrashModel
{ {
public GamblerDbModel creator; public GamblerDbModel Creator;
public decimal finalMulti = 0; public decimal FinalMulti = 0;
public decimal currentMulti = 1.01m; public decimal CurrentMulti = 1.01m;
public List<KrashBet> bets = new(); public List<KrashBet> Bets = new();
public decimal HOUSE_EDGE = 0.98m; public decimal HouseEdge = 0.98m;
public bool betsAccepted = true; public bool BetsAccepted = true;
public bool krashAccepted = false; public bool KrashAccepted = false;
public KasinoKrashModel(GamblerDbModel creator) public KasinoKrashModel(GamblerDbModel creator)
{ {
this.creator = creator; this.Creator = creator;
finalMulti = GetLinearWeightedRandom(1.01, 25000); FinalMulti = GetLinearWeightedRandom(1.01, 25000);
} }
private decimal GetLinearWeightedRandom(double minValue, double maxValue) private decimal GetLinearWeightedRandom(double minValue, double maxValue)
{ {
var random = RandomShim.Create(StandardRng.Create()); var random = RandomShim.Create(StandardRng.Create());
double r = random.NextDouble(); // Returns 0.0 to 1.0 var r = random.NextDouble(); // Returns 0.0 to 1.0
// The core 1/x logic // The core 1/x logic
double result = 1.0 / (1.0 - r); var result = 1.0 / (1.0 - r);
// Clamp the result to your specific range // Clamp the result to your specific range
if (result < minValue) result = minValue; if (result < minValue) result = minValue;
@@ -241,9 +231,9 @@ public class KasinoKrash : IDisposable
public class KrashBet public class KrashBet
{ {
public required GamblerDbModel gambler{ get; set;} public required GamblerDbModel Gambler{ get; set;}
public required decimal wager { get; set; } public required decimal Wager { get; set; }
public required decimal multi { get; set; } public required decimal Multi { get; set; }
} }
public void Dispose() public void Dispose()
{ {