Text Base blackjack with gump

Got any custom JavaScript additions/tweaks you think other people would like to see? Post 'em here!
Post Reply
dragon slayer
UOX3 Guru
Posts: 776
Joined: Thu Dec 21, 2006 7:37 am
Has thanked: 4 times
Been thanked: 26 times

Text Base blackjack with gump

Post by dragon slayer »

// ============================================================================
// Blackjack (UOX3 / SpiderMonkey 1.8.5 / ES5)
// ScriptID: 7511
// Command:  blackjack
// Style:    Same gump + onGumpPress pattern as the Yahtzee script
// Notes:
// - Per-player TempTags: bj_state, bj_chips, bj_bet, bj_deck, bj_p, bj_d, bj_msg, bj_canDouble
// - Payouts: Blackjack 3:2, regular win 1:1, push returns bet
// - No splits/insurance (easy to add later)
// ============================================================================

/* --------------------------- Tunables --------------------------- */
var BJ_GUMP_ID = 0xB7A1;     // unique per script (like Yahtzee’s)
var BJ_STARTING_CHIPS = 1000;
var BJ_MIN_BET = 10;
var BJ_MAX_BET = 50000;

/* --------------------------- Command Registration --------------------------- */
function CommandRegistration()
{
    // allow normal players to open
    RegisterCommand("blackjack", 0, true);
}

function command_BLACKJACK(socket, cmdString)
{
    var p = socket.currentChar; if (!ValidateObject(p)) return;
    BJ_InitIfNeeded(p);
    BJ_OpenGump(p, "Welcome! Set your bet, then Deal.");
}

/* --------------------------- onGumpPress (Yahtzee-style) --------------------------- */
// EXACT same signature shape we used for Yahtzee:
function onGumpPress(pSock, pButton, gumpData)
{
    var p = pSock.currentChar; if (!ValidateObject(p)) return;
    switch (pButton)
    {
        // bet adjusters
        case 101: BJ_AdjustBet(p, -1000); break;
        case 102: BJ_AdjustBet(p, -100); break;
        case 103: BJ_AdjustBet(p, -10); break;
        case 104: BJ_AdjustBet(p, 10); break;
        case 105: BJ_AdjustBet(p, 100); break;
        case 106: BJ_AdjustBet(p, 1000); break;
        case 107: BJ_SetBet(p, BJ_MIN_BET); break;
        case 108: BJ_MaxBet(p); break;

        // flow
        case 201: BJ_Deal(p); break;
        case 202: BJ_Hit(p); break;
        case 203: BJ_Stand(p); break;
        case 204: BJ_Double(p); break;
        case 205: BJ_NewHand(p); break;

        case 0:
        default: break; // closed or unknown
    }
}

/* --------------------------- Core State --------------------------- */
function BJ_InitIfNeeded(p)
{
    if ((p.GetTempTag("bj_inited") | 0) !== 1)
    {
        p.SetTempTag("bj_inited", 1);
        p.SetTempTag("bj_chips", BJ_STARTING_CHIPS);
        p.SetTempTag("bj_bet", BJ_MIN_BET);
        p.SetTempTag("bj_state", "idle");   // idle | betting | inplay | result
        p.SetTempTag("bj_p", "");
        p.SetTempTag("bj_d", "");
        p.SetTempTag("bj_deck", "");
        p.SetTempTag("bj_canDouble", 0);
        p.SetTempTag("bj_msg", "");
    }
}

function BJ_NewRound(p)
{
    BJ_SetDeck(p, BJ_ShuffledDeck());
    BJ_SetHand(p, "bj_p", []);
    BJ_SetHand(p, "bj_d", []);
    p.SetTempTag("bj_state", "betting");
    p.SetTempTag("bj_msg", "");
    p.SetTempTag("bj_canDouble", 0);
}

function BJ_NewHand(p) { BJ_NewRound(p); BJ_OpenGump(p, "New hand. Adjust bet or Deal."); }

/* --------------------------- Deck/Hand Encoding --------------------------- */
function BJ_ShuffledDeck()
{
    var deck = []; for (var i = 0; i < 52; i++) deck.push(i);
    for (var j = deck.length - 1; j > 0; j--) { var k = RandomNumber(0, j); var t = deck[j]; deck[j] = deck[k]; deck[k] = t; }
    return deck;
}
function BJ_GetDeck(p)
{
    var raw = p.GetTempTag("bj_deck") || ""; if (!raw) return [];
    var s = raw.split(","), out = [];
    for (var i = 0; i < s.length; i++) { var v = parseInt(s[i], 10); if (!isNaN(v)) out.push(v); }
    return out;
}
function BJ_SetDeck(p, arr) { p.SetTempTag("bj_deck", arr.join(",")); }
function BJ_Draw(p) { var d = BJ_GetDeck(p); if (d.length === 0) d = BJ_ShuffledDeck(); var c = d.pop(); BJ_SetDeck(p, d); return c; }

function BJ_GetHand(p, key)
{
    var raw = p.GetTempTag(key) || ""; if (!raw) return [];
    var s = raw.split(","), out = [];
    for (var i = 0; i < s.length; i++) { var v = parseInt(s[i], 10); if (!isNaN(v)) out.push(v); }
    return out;
}
function BJ_SetHand(p, key, arr) { p.SetTempTag(key, arr.join(",")); }

/* --------------------------- Math --------------------------- */
function BJ_Rank(card) { return card % 13; }           // 0..12 (A=0, 10..12 = J/Q/K)
function BJ_Suit(card) { return (card / 13) | 0; }         // 0..3
function BJ_Label(card)
{
    var r = BJ_Rank(card), s = BJ_Suit(card);
    var R = (r === 0 ? "A" : (r >= 10 ? ["J", "Q", "K"][r - 10] : "" + (r + 1)));
    var S = ["♠", "♥", "♦", "♣"][s];
    return R + S;
}
function BJ_HandInfo(cards)
{
    var total = 0, aces = 0, i;
    for (i = 0; i < cards.length; i++)
    {
        var r = BJ_Rank(cards[i]);
        if (r === 0) { total += 11; aces++; }
        else if (r >= 10) total += 10;
        else total += (r + 1);
    }
    while (total > 21 && aces > 0) { total -= 10; aces--; }
    var blackjack = (cards.length === 2 && total === 21);
    var busted = (total > 21);
    var soft = (aces > 0); // any Ace still counted as 11
    return { total: total, blackjack: blackjack, busted: busted, soft: soft };
}

/* --------------------------- Chips & Bet --------------------------- */
function BJ_GetChips(p) { return parseInt(p.GetTempTag("bj_chips"), 10) || 0; }
function BJ_SetChips(p, n) { p.SetTempTag("bj_chips", (n | 0)); }
function BJ_GetBet(p)
{
    var b = parseInt(p.GetTempTag("bj_bet"), 10) || BJ_MIN_BET;
    if (b < BJ_MIN_BET) b = BJ_MIN_BET;
    if (b > BJ_MAX_BET) b = BJ_MAX_BET;
    var chips = BJ_GetChips(p);
    if (b > chips) b = chips;
    return b;
}
function BJ_SetBet(p, b)
{
    var chips = BJ_GetChips(p);
    if (b < BJ_MIN_BET) b = BJ_MIN_BET;
    if (b > BJ_MAX_BET) b = BJ_MAX_BET;
    if (b > chips) b = chips;
    p.SetTempTag("bj_bet", (b | 0));
    BJ_OpenGump(p, "");
}
function BJ_AdjustBet(p, delta) { BJ_SetBet(p, BJ_GetBet(p) + (delta | 0)); }
function BJ_MaxBet(p) { BJ_SetBet(p, Math.max(BJ_MIN_BET, Math.min(BJ_MAX_BET, BJ_GetChips(p)))); }

/* --------------------------- Round Flow --------------------------- */
function BJ_Deal(p)
{
    var state = p.GetTempTag("bj_state") || "idle";
    if (state !== "idle" && state !== "betting" && state !== "result") { BJ_OpenGump(p, ""); return; }

    var chips = BJ_GetChips(p), bet = BJ_GetBet(p);
    if (bet < BJ_MIN_BET) { BJ_OpenGump(p, "Bet below table minimum."); return; }
    if (bet > chips) { BJ_OpenGump(p, "Not enough chips."); return; }

    // lock bet by removing chips, save live bet
    BJ_SetChips(p, chips - bet);
    p.SetTempTag("bj_betLive", bet | 0);

    // (re)shuffle if thin
    if (BJ_GetDeck(p).length < 15) BJ_SetDeck(p, BJ_ShuffledDeck());

    // clear hands
    BJ_SetHand(p, "bj_p", []);
    BJ_SetHand(p, "bj_d", []);

    // initial deal P,D,P,D
    var ph = BJ_GetHand(p, "bj_p"), dh = BJ_GetHand(p, "bj_d");
    ph.push(BJ_Draw(p)); dh.push(BJ_Draw(p));
    ph.push(BJ_Draw(p)); dh.push(BJ_Draw(p));
    BJ_SetHand(p, "bj_p", ph); BJ_SetHand(p, "bj_d", dh);

    p.SetTempTag("bj_state", "inplay");
    p.SetTempTag("bj_canDouble", 1);
    p.SetTempTag("bj_msg", "");

    // naturals
    var pi = BJ_HandInfo(ph), di = BJ_HandInfo(dh);
    if (pi.blackjack || di.blackjack) { BJ_Finish(p, true); return; }

    BJ_OpenGump(p, "");
}

function BJ_Hit(p)
{
    if ((p.GetTempTag("bj_state") || "") !== "inplay") { BJ_OpenGump(p, ""); return; }
    var ph = BJ_GetHand(p, "bj_p"); ph.push(BJ_Draw(p)); BJ_SetHand(p, "bj_p", ph);
    p.SetTempTag("bj_canDouble", 0);
    var pi = BJ_HandInfo(ph);
    if (pi.busted) { BJ_Finish(p, false); return; }
    BJ_OpenGump(p, "");
}

function BJ_Stand(p)
{
    if ((p.GetTempTag("bj_state") || "") !== "inplay") { BJ_OpenGump(p, ""); return; }
    BJ_DealerPlay(p);
    BJ_Finish(p, false);
}

function BJ_Double(p)
{
    if ((p.GetTempTag("bj_state") || "") !== "inplay") { BJ_OpenGump(p, ""); return; }
    if ((p.GetTempTag("bj_canDouble") | 0) !== 1) { BJ_OpenGump(p, "You can’t double now."); return; }

    var bet = parseInt(p.GetTempTag("bj_betLive"), 10) || 0;
    var chips = BJ_GetChips(p);
    if (chips < bet) { BJ_OpenGump(p, "Not enough chips to double."); return; }

    // take extra bet; one draw; then stand
    BJ_SetChips(p, chips - bet);
    p.SetTempTag("bj_betLive", bet * 2);
    p.SetTempTag("bj_canDouble", 0);

    var ph = BJ_GetHand(p, "bj_p"); ph.push(BJ_Draw(p)); BJ_SetHand(p, "bj_p", ph);
    var pi = BJ_HandInfo(ph);
    if (!pi.busted) BJ_DealerPlay(p);
    BJ_Finish(p, false);
}

function BJ_DealerPlay(p)
{
    var dh = BJ_GetHand(p, "bj_d"), di = BJ_HandInfo(dh);
    // dealer hits to 17; hit soft 17
    while (di.total < 17 || (di.total === 17 && di.soft)) { dh.push(BJ_Draw(p)); di = BJ_HandInfo(dh); }
    BJ_SetHand(p, "bj_d", dh);
}

/* --------------------------- Resolution --------------------------- */
function BJ_Finish(p, naturalCheck)
{
    var ph = BJ_GetHand(p, "bj_p"), dh = BJ_GetHand(p, "bj_d");
    var pi = BJ_HandInfo(ph), di = BJ_HandInfo(dh);
    var betLive = parseInt(p.GetTempTag("bj_betLive"), 10) || 0;
    var chips = BJ_GetChips(p);
    var msg = "", payout = 0;

    if (naturalCheck)
    {
        if (pi.blackjack && di.blackjack) { msg = "Push (both Blackjack)."; payout = betLive; }
        else if (pi.blackjack) { msg = "Blackjack! Payout 3:2."; payout = (betLive * 5 / 2) | 0; }
        else if (di.blackjack) { msg = "Dealer Blackjack. You lose."; payout = 0; }
        else { BJ_OpenGump(p, ""); return; }
    }
    else
    {
        if (pi.busted) { msg = "You bust. Dealer wins."; payout = 0; }
        else if (di.busted) { msg = "Dealer busts. You win!"; payout = betLive * 2; }
        else if (pi.total > di.total) { msg = "You win!"; payout = betLive * 2; }
        else if (pi.total < di.total) { msg = "Dealer wins."; payout = 0; }
        else { msg = "Push."; payout = betLive; }
    }

    BJ_SetChips(p, chips + payout);
    p.SetTempTag("bj_state", "result");
    p.SetTempTag("bj_msg", msg + " (" + payout + " returned)");
    p.SetTempTag("bj_canDouble", 0);
    BJ_OpenGump(p, "");
}

/* --------------------------- Gump (Yahtzee-style build/send) --------------------------- */
function BJ_OpenGump(p, inlineMsg)
{
    var sock = p.socket; if (!sock) return;

    var g = new Gump();
    g.gumpID = BJ_GUMP_ID;               // gate clicks like in Yahtzee
    g.AddPage(0);
    g.AddBackground(0, 0, 420, 340, 5054);

    // header
    g.AddText(16, 14, 1152, "BLACKJACK");
    var chips = BJ_GetChips(p), bet = BJ_GetBet(p);
    g.AddText(16, 38, 0x34, "Chips: " + chips);
    g.AddText(180, 38, 0x34, "Bet: " + bet);

    // bet controls (IDs 101..108) – same consistent ID scheme as Yahtzee
    g.AddButton(16, 62, 0x0FA5, 1, 0, 101); g.AddText(38, 64, 0x480, "-1000");
    g.AddButton(86, 62, 0x0FA5, 1, 0, 102); g.AddText(108, 64, 0x480, "-100");
    g.AddButton(156, 62, 0x0FA5, 1, 0, 103); g.AddText(178, 64, 0x480, "-10");

    g.AddButton(226, 62, 0x0FA5, 1, 0, 104); g.AddText(248, 64, 0x480, "+10");
    g.AddButton(286, 62, 0x0FA5, 1, 0, 105); g.AddText(308, 64, 0x480, "+100");
    g.AddButton(346, 62, 0x0FA5, 1, 0, 106); g.AddText(368, 64, 0x480, "+1000");

    g.AddButton(16, 86, 0x0FA5, 1, 0, 107); g.AddText(38, 88, 0x480, "Min");
    g.AddButton(86, 86, 0x0FA5, 1, 0, 108); g.AddText(108, 88, 0x480, "Max");

    // hands
    var ph = BJ_GetHand(p, "bj_p"), dh = BJ_GetHand(p, "bj_d");
    var pi = BJ_HandInfo(ph), di = BJ_HandInfo(dh);
    var state = p.GetTempTag("bj_state") || "idle";
    var hideHole = (state === "inplay"); // conceal dealer’s 2nd card while inplay

    g.AddText(16, 120, 0x34, "Dealer:");
    g.AddText(16, 140, 0x480, BJ_RenderCards(dh, hideHole));
    if (!hideHole) g.AddText(16, 158, 0x59, "Total: " + di.total);

    g.AddText(16, 188, 0x34, "You:");
    g.AddText(16, 208, 0x480, BJ_RenderCards(ph, false));
    g.AddText(16, 226, 0x59, "Total: " + pi.total);

    // action buttons (like Yahtzee: grouped and gated by state)
    if (state === "idle" || state === "betting" || state === "result")
    {
        g.AddButton(300, 250, 0x0FA5, 1, 0, 201); g.AddText(322, 252, 0x480, "Deal");
        if (state === "result")
        {
            g.AddButton(300, 278, 0x0FA5, 1, 0, 205); g.AddText(322, 280, 0x480, "New");
        }
    }
    else if (state === "inplay")
    {
        g.AddButton(300, 236, 0x0FA5, 1, 0, 202); g.AddText(322, 238, 0x480, "Hit");
        g.AddButton(300, 264, 0x0FA5, 1, 0, 203); g.AddText(322, 266, 0x480, "Stand");
        if ((p.GetTempTag("bj_canDouble") | 0) === 1)
        {
            g.AddButton(300, 292, 0x0FA5, 1, 0, 204); g.AddText(322, 294, 0x480, "Double");
        }
    }

    // footer
    var msg = inlineMsg || (p.GetTempTag("bj_msg") || "");
    if (msg) g.AddText(16, 312, 0x25, msg);

    // send exactly like Yahtzee
    g.Send(sock);
    g.Free();
}

function BJ_RenderCards(cards, hideHole)
{
    if (!cards || cards.length === 0) return "(none)";
    var out = [], i;
    for (i = 0; i < cards.length; i++)
    {
        if (hideHole && i === 1) out.push("[??]");
        else out.push("[" + BJ_Label(cards[i]) + "]");
    }
    return out.join(" ");
}


// Put this in the same blackjack.js if you want item double-click to work globally
function onUseChecked(pUser, iUsed)
{
    if (!ValidateObject(pUser) || !ValidateObject(iUsed)) return false;
    // Change to match your table’s sectionID if you only want specific items:
    // if (iUsed.sectionID !== "blackjacktable") return false;
    BJ_InitIfNeeded(pUser);
    BJ_OpenGump(pUser, "");
    return true;
}
Post Reply