Did some refactoring with blackjack and probably have completely broken it

This commit is contained in:
barelyprofessional
2026-01-02 21:25:36 -06:00
parent fcd057e980
commit b0473d68ab
2 changed files with 71 additions and 128 deletions

View File

@@ -7,6 +7,7 @@ using KfChatDotNetBot.Services;
using KfChatDotNetBot.Settings; using KfChatDotNetBot.Settings;
using KfChatDotNetWsClient.Models.Events; using KfChatDotNetWsClient.Models.Events;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NLog;
using RandN.Compat; using RandN.Compat;
using RandN; using RandN;
@@ -37,6 +38,8 @@ public class BlackjackCommand : ICommand
Flags = RateLimitFlags.NoAutoDeleteCooldownResponse Flags = RateLimitFlags.NoAutoDeleteCooldownResponse
}; };
private ApplicationDbContext _dbContext = new();
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
CancellationToken ctx) CancellationToken ctx)
{ {
@@ -47,17 +50,23 @@ public class BlackjackCommand : ICommand
if (arguments.TryGetValue("amount", out var amountGroup)) if (arguments.TryGetValue("amount", out var amountGroup))
{ {
await StartNewGame(botInstance, user, amountGroup.Value, cleanupDelay, ctx); await StartNewGame(botInstance, user, amountGroup.Value, cleanupDelay, ctx);
return;
} }
else if (arguments.TryGetValue("action", out var actionGroup))
if (arguments.TryGetValue("action", out var actionGroup))
{ {
await ContinueGame(botInstance, user, actionGroup.Value.ToLower(), cleanupDelay, ctx); await ContinueGame(botInstance, user, actionGroup.Value.ToLower(), cleanupDelay, ctx);
return;
} }
throw new InvalidOperationException($"User {user.KfUsername} somehow ran blackjack without an amount or action: {message.MessageRaw}");
} }
private async Task StartNewGame(ChatBot botInstance, UserDbModel user, string amountStr, private async Task StartNewGame(ChatBot botInstance, UserDbModel user, string amountStr,
TimeSpan cleanupDelay, CancellationToken ctx) TimeSpan cleanupDelay, CancellationToken ctx)
{ {
var logger = LogManager.GetCurrentClassLogger();
var wager = Convert.ToDecimal(amountStr); var wager = Convert.ToDecimal(amountStr);
var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx);
@@ -76,55 +85,43 @@ public class BlackjackCommand : ICommand
} }
// Check for existing incomplete blackjack game // Check for existing incomplete blackjack game
await using var db = new ApplicationDbContext(); var existingGame = await _dbContext.Wagers
var existingGame = await db.Wagers .LastOrDefaultAsync(w => w.Gambler.Id == gambler.Id &&
.Where(w => w.Gambler.Id == gambler.Id && w.Game == WagerGame.Blackjack &&
w.Game == WagerGame.Blackjack && !w.IsComplete && w.GameMeta != null,
!w.IsComplete) cancellationToken: ctx);
.OrderByDescending(w => w.Id)
.FirstOrDefaultAsync(ctx);
if (existingGame != null) if (existingGame != null)
{ {
if (existingGame.GameMeta == null) try
{ {
// Mark as complete with loss and continue _ = JsonSerializer.Deserialize<BlackjackGameMetaModel>(existingGame.GameMeta!) ??
db.Attach(existingGame); throw new InvalidOperationException();
}
catch (Exception e)
{
logger.Error($"Caught error when deserializing meta for wager ID {existingGame.Id}");
logger.Error(e);
await botInstance.SendChatMessageAsync(
$"{user.FormatUsername()}, somehow your previous blackjack game state got messed up. Please try again",
true, autoDeleteAfter: cleanupDelay);
existingGame.IsComplete = true; existingGame.IsComplete = true;
existingGame.WagerEffect = -existingGame.WagerAmount; await _dbContext.SaveChangesAsync(ctx);
existingGame.Multiplier = 0m; throw;
await db.SaveChangesAsync(ctx);
} }
else // Check if game has timed out
var timeSinceStart = DateTimeOffset.UtcNow - existingGame.Time;
if (timeSinceStart > GameTimeout)
{ {
var existingGameState = JsonSerializer.Deserialize<BlackjackGameMetaModel>(existingGame.GameMeta); await ForfeitGame(botInstance, user, gambler, existingGame, cleanupDelay, ctx);
return;
if (existingGameState == null)
{
db.Attach(existingGame);
existingGame.IsComplete = true;
existingGame.WagerEffect = -existingGame.WagerAmount;
existingGame.Multiplier = 0m;
await db.SaveChangesAsync(ctx);
}
else
{
// Check if game has timed out
var timeSinceStart = DateTimeOffset.UtcNow - existingGameState.GameStarted;
if (timeSinceStart > GameTimeout)
{
await ForfeitGame(botInstance, user, gambler, existingGame, cleanupDelay, ctx);
}
else
{
await botInstance.SendChatMessageAsync(
$"{user.FormatUsername()}, you already have an active blackjack game. Use !bj hit or !bj stand to continue.",
true, autoDeleteAfter: cleanupDelay);
return;
}
}
} }
await botInstance.SendChatMessageAsync(
$"{user.FormatUsername()}, you already have an active blackjack game. Use !bj hit or !bj stand to continue.",
true, autoDeleteAfter: cleanupDelay);
return;
} }
@@ -140,16 +137,14 @@ public class BlackjackCommand : ICommand
// Create game state // Create game state
var newGameState = new BlackjackGameMetaModel var newGameState = new BlackjackGameMetaModel
{ {
WagerId = 0, // Will be set after wager is created
PlayerHand = playerHand, PlayerHand = playerHand,
DealerHand = dealerHand, DealerHand = dealerHand,
Deck = deck, Deck = deck,
GameStarted = DateTimeOffset.UtcNow,
HasDoubledDown = false HasDoubledDown = false
}; };
// Create incomplete wager // Create incomplete wager
var newBalance = await Money.NewWagerAsync( await Money.NewWagerAsync(
gambler.Id, gambler.Id,
wager, wager,
-wager, // This will be the effect for incomplete wagers -wager, // This will be the effect for incomplete wagers
@@ -161,15 +156,12 @@ public class BlackjackCommand : ICommand
); );
// Update wager ID in game state // Update wager ID in game state
var createdWager = await db.Wagers var createdWager = await _dbContext.Wagers
.Where(w => w.Gambler.Id == gambler.Id && w.Game == WagerGame.Blackjack && !w.IsComplete) .LastOrDefaultAsync(
.OrderByDescending(w => w.Id) w => w.Gambler.Id == gambler.Id && w.Game == WagerGame.Blackjack && !w.IsComplete && w.GameMeta != null,
.FirstAsync(ctx); cancellationToken: ctx) ?? throw new InvalidOperationException();
newGameState.WagerId = createdWager.Id;
db.Attach(createdWager);
createdWager.GameMeta = JsonSerializer.Serialize(newGameState); createdWager.GameMeta = JsonSerializer.Serialize(newGameState);
await db.SaveChangesAsync(ctx); await _dbContext.SaveChangesAsync(ctx);
// Check for immediate blackjacks // Check for immediate blackjacks
var playerValue = BlackjackHelper.CalculateHandValue(playerHand); var playerValue = BlackjackHelper.CalculateHandValue(playerHand);
@@ -207,13 +199,10 @@ public class BlackjackCommand : ICommand
} }
// Find active game // Find active game
await using var db = new ApplicationDbContext(); var activeWager = await _dbContext.Wagers
var activeWager = await db.Wagers .LastOrDefaultAsync(w => w.Gambler.Id == gambler.Id &&
.Where(w => w.Gambler.Id == gambler.Id && w.Game == WagerGame.Blackjack &&
w.Game == WagerGame.Blackjack && !w.IsComplete && w.GameMeta != null, cancellationToken: ctx);
!w.IsComplete)
.OrderByDescending(w => w.Id)
.FirstOrDefaultAsync(ctx);
if (activeWager == null) if (activeWager == null)
{ {
@@ -223,26 +212,19 @@ public class BlackjackCommand : ICommand
return; return;
} }
if (activeWager.GameMeta == null) var currentGameState = JsonSerializer.Deserialize<BlackjackGameMetaModel>(activeWager.GameMeta!);
{
await botInstance.SendChatMessageAsync(
$"{user.FormatUsername()}, your game data is corrupted. Please start a new game.",
true, autoDeleteAfter: cleanupDelay);
return;
}
var currentGameState = JsonSerializer.Deserialize<BlackjackGameMetaModel>(activeWager.GameMeta);
if (currentGameState == null) if (currentGameState == null)
{ {
await botInstance.SendChatMessageAsync( await botInstance.SendChatMessageAsync(
$"{user.FormatUsername()}, your game data is corrupted. Please start a new game.", $"{user.FormatUsername()}, your game data is corrupted. Please start a new game.",
true, autoDeleteAfter: cleanupDelay); true, autoDeleteAfter: cleanupDelay);
activeWager.IsComplete = true;
await _dbContext.SaveChangesAsync(ctx);
return; return;
} }
// Check timeout // Check timeout
var timeSinceStart = DateTimeOffset.UtcNow - currentGameState.GameStarted; var timeSinceStart = DateTimeOffset.UtcNow - activeWager.Time;
if (timeSinceStart > GameTimeout) if (timeSinceStart > GameTimeout)
{ {
await ForfeitGame(botInstance, user, gambler, activeWager, cleanupDelay, ctx); await ForfeitGame(botInstance, user, gambler, activeWager, cleanupDelay, ctx);
@@ -296,8 +278,10 @@ public class BlackjackCommand : ICommand
true, autoDeleteAfter: cleanupDelay); true, autoDeleteAfter: cleanupDelay);
await ResolveGame(botInstance, user, gambler, wager, gameState, false, cleanupDelay, ctx); await ResolveGame(botInstance, user, gambler, wager, gameState, false, cleanupDelay, ctx);
return;
} }
else if (gameState.HasDoubledDown)
if (gameState.HasDoubledDown)
{ {
// Auto-stand after double down hit // Auto-stand after double down hit
await botInstance.SendChatMessageAsync( await botInstance.SendChatMessageAsync(
@@ -306,21 +290,18 @@ public class BlackjackCommand : ICommand
true, autoDeleteAfter: cleanupDelay); true, autoDeleteAfter: cleanupDelay);
await HandleStand(botInstance, user, gambler, wager, gameState, cleanupDelay, ctx); await HandleStand(botInstance, user, gambler, wager, gameState, cleanupDelay, ctx);
return;
} }
else
{ // Continue game
// Continue game wager.GameMeta = JsonSerializer.Serialize(gameState);
await using var db = new ApplicationDbContext(); await _dbContext.SaveChangesAsync(ctx);
db.Attach(wager);
wager.GameMeta = JsonSerializer.Serialize(gameState);
await db.SaveChangesAsync(ctx);
await botInstance.SendChatMessageAsync( await botInstance.SendChatMessageAsync(
$"{user.FormatUsername()} hit and drew {card}[br]" + $"{user.FormatUsername()} hit and drew {card}[br]" +
$"[B]Your hand:[/B] {BlackjackHelper.FormatHand(gameState.PlayerHand)} = {playerValue}[br]" + $"[B]Your hand:[/B] {BlackjackHelper.FormatHand(gameState.PlayerHand)} = {playerValue}[br]" +
$"Use [B]!bj hit[/B] or [B]!bj stand[/B] to continue", $"Use [B]!bj hit[/B] or [B]!bj stand[/B] to continue",
true, autoDeleteAfter: cleanupDelay); true, autoDeleteAfter: cleanupDelay);
}
} }
private async Task HandleStand(ChatBot botInstance, UserDbModel user, GamblerDbModel gambler, private async Task HandleStand(ChatBot botInstance, UserDbModel user, GamblerDbModel gambler,
@@ -372,18 +353,13 @@ public class BlackjackCommand : ICommand
} }
// Double the wager // Double the wager
await using var db = new ApplicationDbContext();
db.Attach(wager);
db.Attach(gambler);
var additionalWager = wager.WagerAmount; var additionalWager = wager.WagerAmount;
gambler.Balance -= additionalWager; gambler.Balance -= additionalWager;
wager.WagerAmount *= 2; wager.WagerAmount *= 2;
wager.WagerEffect -= additionalWager; // Subtract the additional wager wager.WagerEffect -= additionalWager; // Subtract the additional wager
gameState.HasDoubledDown = true; gameState.HasDoubledDown = true;
await db.SaveChangesAsync(ctx); await _dbContext.SaveChangesAsync(ctx);
await botInstance.SendChatMessageAsync( await botInstance.SendChatMessageAsync(
$"{user.FormatUsername()} doubled down! Wager is now {await wager.WagerAmount.FormatKasinoCurrencyAsync()}", $"{user.FormatUsername()} doubled down! Wager is now {await wager.WagerAmount.FormatKasinoCurrencyAsync()}",
@@ -461,30 +437,13 @@ public class BlackjackCommand : ICommand
} }
// Update wager to complete // Update wager to complete
await using var db = new ApplicationDbContext();
db.Attach(wager);
db.Attach(gambler);
wager.IsComplete = true; wager.IsComplete = true;
wager.WagerEffect = finalEffect; wager.WagerEffect = finalEffect;
wager.Multiplier = multiplier; wager.Multiplier = multiplier;
await _dbContext.SaveChangesAsync(ctx);
var balanceAdjustment = finalEffect + wager.WagerAmount; var balanceAdjustment = finalEffect + wager.WagerAmount;
gambler.Balance += balanceAdjustment; await Money.ModifyBalanceAsync(gambler.Id, balanceAdjustment, TransactionSourceEventType.Gambling,
$"Blackjack outcome from wager {wager.Id}", null, ctx);
// Create transaction for the outcome
await db.Transactions.AddAsync(new TransactionDbModel
{
Gambler = gambler,
EventSource = TransactionSourceEventType.Gambling,
Time = DateTimeOffset.UtcNow,
Effect = balanceAdjustment,
Comment = $"Blackjack outcome from wager {wager.Id}",
NewBalance = gambler.Balance,
TimeUnixEpochSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
}, ctx);
await db.SaveChangesAsync(ctx);
// Display result // Display result
var message = $"🃏 {user.FormatUsername()}'s blackjack game:[br]" + var message = $"🃏 {user.FormatUsername()}'s blackjack game:[br]" +
@@ -509,14 +468,8 @@ public class BlackjackCommand : ICommand
private async Task ForfeitGame(ChatBot botInstance, UserDbModel user, GamblerDbModel gambler, private async Task ForfeitGame(ChatBot botInstance, UserDbModel user, GamblerDbModel gambler,
WagerDbModel wager, TimeSpan cleanupDelay, CancellationToken ctx) WagerDbModel wager, TimeSpan cleanupDelay, CancellationToken ctx)
{ {
await using var db = new ApplicationDbContext();
db.Attach(wager);
wager.IsComplete = true; wager.IsComplete = true;
wager.WagerEffect = -wager.WagerAmount; await _dbContext.SaveChangesAsync(ctx);
wager.Multiplier = 0m;
await db.SaveChangesAsync(ctx);
await botInstance.SendChatMessageAsync( await botInstance.SendChatMessageAsync(
$"{user.FormatUsername()}, your blackjack game timed out and you forfeited {await wager.WagerAmount.FormatKasinoCurrencyAsync()}", $"{user.FormatUsername()}, your blackjack game timed out and you forfeited {await wager.WagerAmount.FormatKasinoCurrencyAsync()}",

View File

@@ -2,11 +2,6 @@
public class BlackjackGameMetaModel public class BlackjackGameMetaModel
{ {
/// <summary>
/// The wager ID associated with this game
/// </summary>
public required int WagerId { get; set; }
/// <summary> /// <summary>
/// Player's hand /// Player's hand
/// </summary> /// </summary>
@@ -22,11 +17,6 @@ public class BlackjackGameMetaModel
/// </summary> /// </summary>
public required List<Card> Deck { get; set; } public required List<Card> Deck { get; set; }
/// <summary>
/// When the game was started
/// </summary>
public required DateTimeOffset GameStarted { get; set; }
/// <summary> /// <summary>
/// Whether player has doubled down (can only hit once more) /// Whether player has doubled down (can only hit once more)
/// </summary> /// </summary>