BlackJack

  • community
  • minigames
  • by Mad
  • Takaro v0.0.24
  • all
Version
View export JSON

Mad_Blackjack: Casino Gaming on Your Game Server

Add a fully-functional Blackjack card game to your server, letting players gamble their in-game currency through an intuitive command system.

Key Features:

  • Complete Blackjack Experience: Play the classic casino card game directly in your game chat with simple commands
  • Economy Integration: Connect with Takaro's economy system - bet, win, and lose in-game currency

Game Commands:

  • /bjplay - Start a new game with your bet

Game Start

  • /bjstatus - Check your current game status

Game Status View

  • /bjhit - Draw another card

Drawing Cards

  • /bjstand - End your turn

Game Results

Game Features:

  • Clear Visual Interface: Card values and suits display with intuitive symbols
  • Real-time Information: See your hand total, the dealer's visible card, and your current balance
  • In-game Betting: Place wagers directly from your in-game currency balance
  • Standard Casino Rules: Beat the dealer without going over 21
  • Multiple Card Draws: Take as many cards as you dare with the /bjhit command
  • Bust Detection: Immediate feedback when you go over 21 and lose your bet
  • Automated Dealer: When you stand, the dealer follows standard Blackjack rules
  • Transparent Results: See both hands with their totals when the game concludes
  • Automatic Currency Management: Winnings are instantly credited to your account

Perfect For:

  • Adding entertainment options to your server
  • Providing fun ways for players to use in-game currency
  • Increasing player engagement with mini-games
  • Creating a social casino experience in your game

Requirements:

  • Takaro's economy system must be enabled

Configuration 0

Settings you fill in when installing the module on a server.

This module takes no configuration.

Raw config schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": [],
  "additionalProperties": false
}
Raw UI schema
{}

Commands 4

Chat commands players trigger in game.

  • BJstatus

    No help text available

    Command source
    import { takaro, data, TakaroUserError } from '@takaro/helpers';
    
    async function main() {
        const { player } = data;
        const gameServerId = data.gameServerId;
    
        // Return object required by Takaro
        const result = {
            success: true,
            message: 'Status retrieved successfully'
        };
    
        try {
            // Card utilities
            const CardUtils = {
                // Calculate the total value of a hand, accounting for Aces
           >es
                    for (let i = 0; i < aces; i++) {
                        if (total > 21) {
                            total -= 10;
                        }
                    }
    
                    return total;
                },
    
                // Convert hand to a readable string with colored cards and resets
                handToPlayerString(hand) {
                    return hand.map(card => `[8BD3E6]${card.value}${card.suit}[-]`).join(', ');
                }
            };
    
            // Game storage functions
            const GameStorage = {
                // Variable key for game state
                getGameKey(playerId) {
                    return `bj_game_${playerId}`;
                },
    
                // Load game state
                async loadGame(playerId) {
                    const key = this.getGameKey(playerId);
    
                    try {
                        const gameVar = await takaro.variable.variableControllerSearch({
                            filters: {
                                key: [key],
                                playerId: [playerId]
                            }
                        });
    
                        if (gameVar.data.data.length > 0) {
                            return JSON.parse(gameVar.data.data[0].value);
                        }
    
                        return null;
                    } catch (error) {
                        console.error('Failed to load game state:', error);
                        throw new Error('Failed to load game state');
                    }
                }
            };
    
            // Get current game
            const getActiveGame = async (playerId) => {
                const game = await GameStorage.loadGame(playerId);
                if (!game || game.status !== 'active') {
                    throw new Error('You do not have an active game. Start a new game with /bjplay.');
                }
                return game;
            };
    
            // Execute status check
            const game = await getActiveGame(player.id);
    
            // Prepare hand information
            const playerHandString = CardUtils.handToPlayerString(game.playerHand);
            const playerHandValue = CardUtils.calculateHandValue(game.playerHand);
            const dealerUpCardString = `[FF6D6A]${game.dealerHand[0].value}${game.dealerHand[0].suit}[-]`;
    
            // Get player's balance using the economy API
            const playerInfo = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, player.id);
            const playerBalance = playerInfo.data.currency;
    
            await player.pm(`[-]BLACKJACK STATUS
            Bet: ${game.bet}
            Your Hand: ${playerHandString} (Total: ${playerHandValue})
            Dealer's Visible Card: ${dealerUpCardString}
            Your Balance: ${playerBalance}
            Use /bjhit to take another card
            Use /bjstand to end your turn`);
    
            return result;
        } catch (error) {
            console.error('BJstatus error:', error);
            result.success = false;
            result.message = error.message;
    
            try {
                await player.pm(`[-]Error: ${error.message}`);
            } catch {
                // Ignore messaging errors
            }
    
            return result;
        }
    }
    
    // Call the main function with await
    await main();
  • BJplay

    Play Black Jack

    ArgumentTypeDefaultHelp
    bet number 500 bet
    Command source
    import { takaro, data, TakaroUserError } from '@takaro/helpers';
    
    async function main() {
      const { player, arguments: args } = data;
      const gameServerId = data.gameServerId;
    
      // Special player ID to pay when a player loses
      const housePlayerID = "d8ef3f8a-4c8f-4a91-9c44-ab5432f5c6ec";
    
      // Return object required by Takaro
      const result = {
        success: true,
        message: 'Game started successfully'
      };
    
      try {
        // Card utilities
        const CardUtils = {
          // Create a full deck of 52 cards
          createDeck() {
            const suits = ['[0C0C0C]♠[-]', '[D64C4C]♥[-]', '[D64C4C]♦[-]', '[0C0C0C]♣[-]']; // Spades, Hearts, Diamonds, Clubs
            const values = [
              { value: '2', numericValue: 2 },
              { value: '3', numericValue: 3 },
              { value: '4', numericValue: 4 },
              { value: '5', numericValue: 5 },
              { value: '6', numericValue: 6 },
              { value: '7', numericValue: 7 },
              { value: '8', numericValue: 8 },
              { value: '9', numericValue: 9 },
              { value: '10', numericValue: 10 },
              { value: 'J', numericValue: 10 },
              { value: 'Q', numericValue: 10 },
              { value: 'K', numericValue: 10 },
              { value: 'A', numericValue: 11 }
            ];
    
            const deck = [];
            suits.forEach(suit => {
              values.forEach(val => {
                deck.push({
                  suit,
                  value: val.value,
                  numericValue: val.numericValue
                });
              });
            });
    
            return this.shuffleDeck(deck);
          },
    
          // Shuffle the deck using Fisher-Yates algorithm
          shuffleDeck(deck) {
            for (let i = deck.length - 1; i > 0; i--) {
              const j = Math.floor(Math.random() * (i + 1));
              [deck[i], deck[j]] = [deck[j], deck[i]];
            }
            return deck;
          },
    
          // Calculate the total value of a hand, accounting for Aces
          calculateHandValue(hand) {
            let total = hand.reduce((sum, card) => sum + card.numericValue, 0);
            const aces = hand.filter(card => card.value === 'A').length;
    
            // Adjust for Aces
            for (let i = 0; i < aces; i++) {
              if (total > 21) {
                total -= 10;
              }
            }
    
            return total;
          },
    
          // Convert hand to a readable string with colored cards and resets
          handToPlayerString(hand) {
            return hand.map(card => `[8BD3E6]${card.value}${card.suit}[-]`).join(', ');
          },
    
          // Convert hand to a readable string with colored cards and resets
          handToDealerString(hand) {
            return hand.map(card => `[FF6D6A]${card.value}${card.suit}[-]`).join(', ');
          }
        };
    
        // Game storage functions
        const GameStorage = {
          // Variable key for game state
          getGameKey(playerId) {
            return `bj_game_${playerId}`;
          },
    
          // Save game state
          async saveGame(game) {
            const key = this.getGameKey(game.playerId);
            const gameData = JSON.stringify(game);
    
            try {
              // Search for existing game variable
              const existingVar = await takaro.variable.variableControllerSearch({
                filters: {
                  key: [key],
                  playerId: [game.playerId]
                }
              });
    
              if (existingVar.data.data.length > 0) {
                // Update existing variable
                await takaro.variable.variableControllerUpdate(existingVar.data.data[0].id, {
                  value: gameData
                });
              } else {
                // Create new variable
                await takaro.variable.variableControllerCreate({
                  key,
                  value: gameData,
                  playerId: game.playerId
                });
              }
            } catch (error) {
              // Attempt to refund the player's bet since game state couldn't be saved
              if (game.bet && game.playerId) {
                try {
                  // Using gameServerId from parent scope
                  await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(gameServerId, game.playerId, {
                    currency: game.bet
                  });
                  console.log(`Refunded ${game.bet} to player ${game.playerId} due to game state save error`);
                } catch (refundError) {
                  console.error('Failed to refund player after game state save error:', refundError);
                }
              }
    
              console.error('Failed to save game state:', error);
              throw new Error('Failed to save game state. Your bet has been refunded.');
            }
          },
    
          // Load game state
          async loadGame(playerId) {
            const key = this.getGameKey(playerId);
    
            try {
              const gameVar = await takaro.variable.variableControllerSearch({
                filters: {
                  key: [key],
                  playerId: [playerId]
                }
              });
    
              if (gameVar.data.data.length > 0) {
                return JSON.parse(gameVar.data.data[0].value);
              }
    
              return null;
            } catch (error) {
              console.error('Failed to load game state:', error);
              throw new Error('Failed to load game state');
            }
          }
        };
    
        // Game logic
        async function startGame(playerId, bet) {
          // Check if player already has a game
          const existingGame = await GameStorage.loadGame(playerId);
          if (existingGame && existingGame.status === 'active') {
            throw new Error('You already have an active game. Use /bjstatus to see your current game.');
          }
    
          // Create deck and deal initial cards
          const deck = CardUtils.createDeck();
          const playerHand = [deck.pop(), deck.pop()];
          const dealerHand = [deck.pop(), deck.pop()];
    
          // Create new game state
          const game = {
            playerId,
            playerHand,
            dealerHand,
            bet,
            status: 'active',
            lastActivity: Date.now()
          };
    
          // Check for immediate blackjack
          const playerValue = CardUtils.calculateHandValue(playerHand);
          const dealerValue = CardUtils.calculateHandValue(dealerHand);
    
          if (playerValue === 21) {
            // Player has blackjack
            if (dealerValue === 21) {
              // Both have blackjack - push
              game.result = 'push';
              game.status = 'complete';
            } else {
              // Player wins with blackjack
              game.result = 'win';
              game.status = 'complete';
            }
          }
    
          // Save game state
          await GameStorage.saveGame(game);
          return game;
        }
    
        // Validate bet
        if (!args || !args.bet) {
          await player.pm('[-]Please provide a bet amount. Usage: /BJplay [amount]');
          result.success = false;
          result.message = 'Missing bet amount';
          return result;
        }
    
        const bet = parseInt(args.bet);
        if (isNaN(bet) || bet <= 0) {
          await player.pm('[-]Please provide a valid bet amount (a positive number).');
          result.success = false;
          result.message = 'Invalid bet amount';
          return result;
        }
    
        // Using hardcoded values instead of config
        const minBet = 10;
        const maxBet = 10000;
        const blackjackPayout = 1.5;
    
        if (bet < minBet) {
          await player.pm(`[-]Minimum bet is ${minBet}.`);
          result.success = false;
          result.message = `Bet below minimum (${minBet})`;
          return result;
        }
    
        if (bet > maxBet) {
          await player.pm(`[-]Maximum bet is ${maxBet}.`);
          result.success = false;
          result.message = `Bet above maximum (${maxBet})`;
          return result;
        }
    
        // Get current player balance
        const playerInfo = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, player.id);
        const playerBalance = playerInfo.data.currency;
    
        // Check balance
        if (playerBalance < bet) {
          await player.pm(`[-]You don't have enough currency. Your balance: ${playerBalance}`);
          result.success = false;
          result.message = 'Insufficient funds';
          return result;
        }
    
        // Deduct bet using the correct currency API
        await takaro.playerOnGameserver.playerOnGameServerControllerDeductCurrency(gameServerId, player.id, {
          currency: bet
        });
    
        // Start the game using variable storage
        const game = await startGame(player.id, bet);
    
        // Construct game status message
        const playerHandString = CardUtils.handToPlayerString(game.playerHand);
        const playerHandValue = CardUtils.calculateHandValue(game.playerHand);
    
        // For dealer's up card, we use the color with reset
        const dealerUpCardString = `[FF6D6A]${game.dealerHand[0].value}${game.dealerHand[0].suit}[-]`;
    
        // Check for immediate game over
        if (game.status === 'complete') {
          const dealerHandString = CardUtils.handToDealerString(game.dealerHand);
          const dealerHandValue = CardUtils.calculateHandValue(game.dealerHand);
    
          let resultMessage = '';
          let winnings = 0;
    
          switch (game.result) {
            case 'win':
              // Check for blackjack bonus
              if (playerHandValue === 21 && game.playerHand.length === 2) {
                winnings = bet + Math.floor(bet * blackjackPayout);
                resultMessage = `BLACKJACK! You win ${winnings}!`;
              } else {
                winnings = bet * 2;
                resultMessage = `You win ${winnings}!`;
              }
    
              // Add winnings using correct currency API
              await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(gameServerId, player.id, {
                currency: winnings
              });
              break;
    
            case 'push':
              // Return bet using correct currency API
              await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(gameServerId, player.id, {
                currency: bet
              });
              resultMessage = 'It\'s a tie (Push). Your bet has been returned.';
              break;
    
            case 'lose':
              resultMessage = `You lose ${bet}.`;
    
              // Give the bet to the house player
              try {
                await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(gameServerId, housePlayerID, {
                  currency: bet
                });
                console.log(`Transferred ${bet} to house player ${housePlayerID} from player ${player.id}`);
              } catch (transferError) {
                console.error('Failed to transfer bet to house player:', transferError);
              }
              break;
          }
    
          // Send separate messages for better readability (without newlines)
          await player.pm(`[-]GAME OVER`);
          await player.pm(`Your Hand: ${playerHandString} (Total: ${playerHandValue})`);
          await player.pm(`Dealer's Hand: ${dealerHandString} (Total: ${dealerHandValue})`);
          await player.pm(`${resultMessage}`);
        } else {
          // Send separate messages for better readability (without newlines)
          await player.pm(`[-]BLACKJACK GAME STARTED!`);
          await player.pm(`Bet: ${bet}`);
          await player.pm(`Your Hand: ${playerHandString} (Total: ${playerHandValue})`);
          await player.pm(`Dealer's Visible Card: ${dealerUpCardString}`);
          await player.pm(`Use /bjhit to take another card   Use /bjstand to end your turn`);
        }
    
        return result;
      } catch (error) {
        console.error('BJplay error:', error);
        result.success = false;
        result.message = error.message;
    
        try {
          await player.pm(`[-]Error: ${error.message}`);
        } catch {
          // Ignore messaging errors
        }
    
        return result;
      }
    }
    
    // Call the main function with await
    await main();
  • BJstand

    No help text available

    Command source
    import { takaro, data, TakaroUserError } from '@takaro/helpers';
    
    async function main() {
        const { player } = data;
        const gameServerId = data.gameServerId;
    
        // Special player ID to pay when a player loses
        const housePlayerID = "d8ef3f8a-4c8f-4a91-9c44-ab5432f5c6ec";
    
        // Return object required by Takaro
        const result = {
            success: true,
            message: 'Stand successful'
        };
    
        try {
            // Card utilities
            const CardUtils = {
                // Calculate the total value of a hand, accounting for Aces
                calculateHandValue(hand) {
                    let total = hand.reduce((sum, card) => sum + card.numericValue, 0);
                    const aces = hand.filter(card => card.value === 'A').length;
    
                    // Adjust for Aces
                    for (let i = 0; i < aces; i++) {
                        if (total > 21) {
                            total -= 10;
                        }
                    }
    
                    return total;
                },
    
                // Convert hand to a readable string with colored cards and resets
                handToPlayerString(hand) {
                    return hand.map(card => `[8BD3E6]${card.value}${card.suit}[-]`).join(', ');
                },
    
                // Convert hand to a readable string with colored cards and resets
                handToDealerString(hand) {
                    return hand.map(card => `[FF6D6A]${card.value}${card.suit}[-]`).join(', ');
                }
            };
    
            // Game storage functions
            const GameStorage = {
                // Variable key for game state
                getGameKey(playerId) {
                    return `bj_game_${playerId}`;
                },
    
                // Save game state
                async saveGame(game) {
                    const key = this.getGameKey(game.playerId);
                    const gameData = JSON.stringify(game);
    
                    try {
                        // Search for existing game variable
                        const existingVar = await takaro.variable.variableControllerSearch({
                            filters: {
                                key: [key],
                                playerId: [game.playerId]
                            }
                        });
    
                        if (existingVar.data.data.length > 0) {
                            // Update existing variable
                            await takaro.variable.variableControllerUpdate(existingVar.data.data[0].id, {
                                value: gameData
                            });
                        } else {
                            // Create new variable
                            await takaro.variable.variableControllerCreate({
                                key,
                                value: gameData,
                                playerId: game.playerId
                            });
                        }
                    } catch (error) {
                        console.error('Failed to save game state:', error);
                        throw new Error('Failed to save game state');
                    }
                },
    
                // Load game state
                async loadGame(playerId) {
                    const key = this.getGameKey(playerId);
    
                    try {
                        const gameVar = await takaro.variable.variableControllerSearch({
                            filters: {
                                key: [key],
                                playerId: [playerId]
                            }
                        });
    
                        if (gameVar.data.data.length > 0) {
                            return JSON.parse(gameVar.data.data[0].value);
                        }
    
                        return null;
                    } catch (error) {
                        console.error('Failed to load game state:', error);
                        throw new Error('Failed to load game state');
                    }
                }
            };
    
            // Get current game
            const getActiveGame = async (playerId) => {
                const game = await GameStorage.loadGame(playerId);
                if (!game || game.status !== 'active') {
                    throw new Error('You do not have an active game. Start a new game with /bjplay.');
                }
                return game;
            };
    
            // Stand function - dealer plays and resolves the game
            const stand = async (playerId) => {
                // Get active game
                const game = await getActiveGame(playerId);
    
                // No need to create a new deck, use the existing dealer and player hands
                // Dealer draws cards until 17 or higher
                let dealerValue = CardUtils.calculateHandValue(game.dealerHand);
                while (dealerValue < 17) {
                    // If needed, you might want to add a method to draw a card from an existing partial deck
                    // For now, we'll assume the game state contains the full hand
                    game.dealerHand.push({
                        suit: '[FF6D6A]♠[-]', // Default suit, adjust as needed
                        value: '10',
                        numericValue: 10
                    });
                    dealerValue = CardUtils.calculateHandValue(game.dealerHand);
                }
    
                // Determine result
                const playerValue = CardUtils.calculateHandValue(game.playerHand);
    
                if (playerValue > 21) {
                    game.result = 'lose'; // Player busted
                } else if (dealerValue > 21) {
                    game.result = 'win'; // Dealer busted
                } else if (playerValue > dealerValue) {
                    game.result = 'win'; // Player has higher value
                } else if (dealerValue > playerValue) {
                    game.result = 'lose'; // Dealer has higher value
                } else {
                    game.result = 'push'; // Equal values - push
                }
    
                // Mark game as complete
                game.status = 'complete';
    
                // Save updated game
                await GameStorage.saveGame(game);
                return game;
            };
    
            // Execute stand
            const game = await stand(player.id);
    
            // Prepare hand information
            const playerHandString = CardUtils.handToPlayerString(game.playerHand);
            const playerHandValue = CardUtils.calculateHandValue(game.playerHand);
            const dealerHandString = CardUtils.handToDealerString(game.dealerHand);
            const dealerHandValue = CardUtils.calculateHandValue(game.dealerHand);
    
            // Process game result
            let resultMessage = '';
            let winnings = 0;
    
            // Using hardcoded values instead of config
            const blackjackPayout = 1.5;
    
            switch (game.result) {
                case 'win':
                    // Check for blackjack bonus
                    if (playerHandValue === 21 && game.playerHand.length === 2) {
                        winnings = game.bet + Math.floor(game.bet * blackjackPayout);
                        resultMessage = `BLACKJACK! You win ${winnings}!`;
                    } else {
                        winnings = game.bet * 2;
                        resultMessage = `You win ${winnings}!`;
                    }
    
                    // Add winnings using correct currency API
                    await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(gameServerId, player.id, {
                        currency: winnings
                    });
                    break;
    
                case 'push':
                    // Return bet using correct currency API
                    await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(gameServerId, player.id, {
                        currency: game.bet
                    });
                    resultMessage = "It's a tie (Push). Your bet has been returned.";
                    break;
    
                case 'lose':
                    // Transfer bet to house player
                    try {
                        await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(gameServerId, housePlayerID, {
                            currency: game.bet
                        });
                        console.log(`Transferred ${game.bet} to house player ${housePlayerID} from player ${player.id}`);
                    } catch (transferError) {
                        console.error('Failed to transfer bet to house player:', transferError);
                    }
                    resultMessage = `You lose ${game.bet}.`;
                    break;
            }
    
            // Send game results
            await player.pm(`[-]GAME OVER`);
            await player.pm(`Your Hand: ${playerHandString} (Total: ${playerHandValue})}`);
            await player.pm(`Dealer's Hand: ${dealerHandString} (Total: ${dealerHandValue})}`);
            await player.pm(`${resultMessage}`);
    
            return result;
        } catch (error) {
            console.error('BJstand error:', error);
            result.success = false;
            result.message = error.message;
    
            try {
                await player.pm(`[-]Error: ${error.message}`);
            } catch {
                // Ignore messaging errors
            }
    
            return result;
        }
    }
    
    // Call the main function with await
    await main();
  • BJhit

    No help text available

    Command source
    import { takaro, data, TakaroUserError } from '@takaro/helpers';
    
    async function main() {
      const { player } = data;
      const gameServerId = data.gameServerId;
    
      // Return object required by Takaro
      const result = {
        success: true,
        message: 'Hit successful'
      };
    
      try {
        // Card utilities
        const CardUtils = {
          // Calculate the total value of a hand, accounting for Aces
          calculateHandValue(hand) {
            let total = hand.reduce((sum, card) => sum + card.numericValue, 0);
            const aces = hand.filter(card => card.value === 'A').length;
    
            // Adjust for Aces
            for (let i = 0; i < aces; i++) {
              if (total > 21) {
                total -= 10;
              }
            }
    
            return total;
          },
    
          // Convert hand to a readable string with colored cards and resets
          handToPlayerString(hand) {
            return hand.map(card => `[8BD3E6]${card.value}${card.suit}[-]`).join(', ');
          },
    
          // Create a full deck of 52 cards
          createDeck() {
            const suits = ['[0C0C0C]♠[-]', '[D64C4C]♥[-]', '[D64C4C]♦[-]', '[0C0C0C]♣[-]']; // Spades, Hearts, Diamonds, Clubs
            const values = [
              { value: '2', numericValue: 2 },
              { value: '3', numericValue: 3 },
              { value: '4', numericValue: 4 },
              { value: '5', numericValue: 5 },
              { value: '6', numericValue: 6 },
              { value: '7', numericValue: 7 },
              { value: '8', numericValue: 8 },
              { value: '9', numericValue: 9 },
              { value: '10', numericValue: 10 },
              { value: 'J', numericValue: 10 },
              { value: 'Q', numericValue: 10 },
              { value: 'K', numericValue: 10 },
              { value: 'A', numericValue: 11 }
            ];
    
            const deck = [];
            suits.forEach(suit => {
              values.forEach(val => {
                deck.push({
                  suit,
                  value: val.value,
                  numericValue: val.numericValue
                });
              });
            });
    
            return this.shuffleDeck(deck);
          },
    
          // Shuffle the deck using Fisher-Yates algorithm
          shuffleDeck(deck) {
            for (let i = deck.length - 1; i > 0; i--) {
              const j = Math.floor(Math.random() * (i + 1));
              [deck[i], deck[j]] = [deck[j], deck[i]];
            }
            return deck;
          }
        };
    
        // Game storage functions
        const GameStorage = {
          // Variable key for game state
          getGameKey(playerId) {
            return `bj_game_${playerId}`;
          },
    
          // Save game state
          async saveGame(game) {
            const key = this.getGameKey(game.playerId);
            const gameData = JSON.stringify(game);
    
            try {
              // Search for existing game variable
              const existingVar = await takaro.variable.variableControllerSearch({
                filters: {
                  key: [key],
                  playerId: [game.playerId]
                }
              });
    
              if (existingVar.data.data.length > 0) {
                // Update existing variable
                await takaro.variable.variableControllerUpdate(existingVar.data.data[0].id, {
                  value: gameData
                });
              } else {
                // Create new variable
                await takaro.variable.variableControllerCreate({
                  key,
                  value: gameData,
                  playerId: game.playerId
                });
              }
            } catch (error) {
              console.error('Failed to save game state:', error);
              throw new Error('Failed to save game state');
            }
          },
    
          // Load game state
          async loadGame(playerId) {
            const key = this.getGameKey(playerId);
    
            try {
              const gameVar = await takaro.variable.variableControllerSearch({
                filters: {
                  key: [key],
                  playerId: [playerId]
                }
              });
    
              if (gameVar.data.data.length > 0) {
                return JSON.parse(gameVar.data.data[0].value);
              }
    
              return null;
            } catch (error) {
              console.error('Failed to load game state:', error);
              throw new Error('Failed to load game state');
            }
          }
        };
    
        // Get current game
        const getActiveGame = async (playerId) => {
          const game = await GameStorage.loadGame(playerId);
          if (!game || game.status !== 'active') {
            throw new Error('You do not have an active game. Start a new game with /bjplay.');
          }
          return game;
        };
    
        // Hit function - add a card to player's hand
        const hit = async (playerId) => {
          // Get active game
          const game = await getActiveGame(playerId);
    
          // Create a deck (not efficient but works for simplicity)
          const deck = CardUtils.createDeck();
    
          // Deal a new card to player
          game.playerHand.push(deck.pop());
    
          // Update last activity
          game.lastActivity = Date.now();
    
          // Check if player busts
          const playerValue = CardUtils.calculateHandValue(game.playerHand);
          if (playerValue > 21) {
            game.result = 'lose';
            game.status = 'complete';
          }
    
          // Save updated game
          await GameStorage.saveGame(game);
          return game;
        };
    
        // Execute hit
        const game = await hit(player.id);
    
        // Prepare hand information
        const playerHandString = CardUtils.handToPlayerString(game.playerHand);
        const playerHandValue = CardUtils.calculateHandValue(game.playerHand);
    
        // Check if player busted
        if (game.status === 'complete' && game.result === 'lose') {
          // Player loses their bet
          try {
            const housePlayerID = "d8ef3f8a-4c8f-4a91-9c44-ab5432f5c6ec";
            await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(gameServerId, housePlayerID, {
              currency: game.bet
            });
            console.log(`Transferred ${game.bet} to house player ${housePlayerID} from player ${player.id}`);
          } catch (transferError) {
            console.error('Failed to transfer bet to house player:', transferError);
          }
    
          await player.pm(`[-]You drew a card!
    Your Hand: ${playerHandString} (Total: ${playerHandValue})
    BUST! You lose ${game.bet}.`);
        } else {
          await player.pm(`[-]You drew a card!
    Your Hand: ${playerHandString} (Total: ${playerHandValue})
    
    Use /bjhit to take another card
    Use /bjstand to end your turn`);
        }
    
        return result;
      } catch (error) {
        console.error('BJhit error:', error);
        result.success = false;
        result.message = error.message;
    
        try {
          await player.pm(`[-]Error: ${error.message}`);
        } catch {
          // Ignore messaging errors
        }
    
        return result;
      }
    }
    
    // Call the main function with await
    await main();