Yahtzee Dice Game

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

Yahtzee Dice Game

Post by dragon slayer »

[yahtzee_dice]
{
get=base_item
NAME=Yahtzee dice
ID=0x15F8
COLOR=1150
SCRIPT=7500
WEIGHT=1
}
// ============================================================================
// Yahtzee (UOX3 / SpiderMonkey 1.8.5 / ES5)
// ScriptID: 7500  (your convention)
// Author: ported from RunUO C# version you uploaded by ChatGPT
// Notes:
// - Command: [yahtzee  -> open a solo Yahtzee game gump
// - Optional consumable item: "yahtzee_dice" (DFN snippet below) opens game then deletes itself
// - Uses per-player TempTags to persist state between gump refreshes
// - Uses AddGump for dice art (base 11279 + 1..6) to mirror the C# UI
// - "Hold" uses toggle buttons (we draw a checkmark gump for state) so we don't rely on gumpData iteration
// ============================================================================

/* --------------------------- Config --------------------------- */
var Y_GUMP_W = 265, Y_GUMP_H = 500;
var Y_GUMP_X = 275, Y_GUMP_Y = 50;
var Y_DICE_BASE_GUMP = 11279; // base + [1..6] as per original C# gump art
var Y_BTN_GUMP_UP = 5601, Y_BTN_GUMP_DN = 5605; // RunUO used these; in UOX3 they’re valid gumps too
var Y_TEXT_LEFT = Y_GUMP_X + 20;
var Y_BTN_X = Y_GUMP_X + 86;   // was +80
var Y_SCORE_RIGHT = Y_GUMP_X + 185;
var Y_BTN_Y_ADJ = -2;              // tiny vertical nudge for nicer alignment

// Minimal skin bits
var Y_STRETCH_BAR1 = 3607;
var Y_STRETCH_BAR2 = 3004;
var Y_BG = 9270;
// Add near your button IDs
var BTN_NEWGAME = 99;
var GOLD_TILE_ID = 0x0EED; // gold coins

/* --------------------------- Button IDs --------------------------- */
// Keep order similar to C# enum for familiarity
var BTN_NONE = 0;
var BTN_HOLD1 = 1, BTN_HOLD2 = 2, BTN_HOLD3 = 3, BTN_HOLD4 = 4, BTN_HOLD5 = 5;
var BTN_ROLL = 6;
var BTN_ONES = 7, BTN_TWOS = 8, BTN_THREES = 9, BTN_FOURS = 10, BTN_FIVES = 11, BTN_SIXES = 12;
var BTN_3KIND = 13, BTN_4KIND = 14, BTN_FULLHOUSE = 15, BTN_SSTRAIGHT = 16, BTN_LSTRAIGHT = 17, BTN_YAHTZEE = 18, BTN_CHANCE = 19;
// Close is implicit (button 0)

/* --------------------------- State helpers --------------------------- */
function _tagGetIntArray(pChar, key, len, defVal)
{
    var raw = pChar.GetTempTag(key) || "";
    var out = [];
    if (!raw)
    {
        for (var i = 0; i < len; i++) out.push(defVal);
        return out;
    }
    var parts = raw.split(",");
    for (var j = 0; j < len; j++)
    {
        var v = parseInt(parts[j], 10);
        if (isNaN(v)) v = defVal;
        out.push(v);
    }
    return out;
}
function _tagGetBoolArray(pChar, key, len, defVal)
{
    var raw = pChar.GetTempTag(key) || "";
    var out = [];
    if (!raw)
    {
        for (var i = 0; i < len; i++) out.push(!!defVal);
        return out;
    }
    var parts = raw.split(",");
    for (var j = 0; j < len; j++)
    {
        var v = parts[j] == "1" || parts[j] === "true";
        if (parts[j] == "" || parts[j] == null || parts[j] == undefined) v = !!defVal;
        out.push(v);
    }
    return out;
}
function _tagSetIntArray(pChar, key, arr) { pChar.SetTempTag(key, arr.join(",")); }
function _tagSetBoolArray(pChar, key, arr) { pChar.SetTempTag(key, arr.map(function(b) { return b ? 1 : 0; }).join(",")); }

function _getRoll(pChar) { return parseInt(pChar.GetTempTag("yaht_roll") || "0", 10) || 0; }
function _setRoll(pChar, v) { pChar.SetTempTag("yaht_roll", "" + (v | 0)); }
function _getUsedRoll(pChar) { return (pChar.GetTempTag("yaht_usedRoll") | 0) === 1; }
function _setUsedRoll(pChar, b) { pChar.SetTempTag("yaht_usedRoll", b ? "1" : "0"); }

function _getDice(pChar) { return _tagGetIntArray(pChar, "yaht_dice", 5, 1); }
function _setDice(pChar, a) { _tagSetIntArray(pChar, "yaht_dice", a); }
function _getHolds(pChar) { return _tagGetBoolArray(pChar, "yaht_hold", 5, false); }
function _setHolds(pChar, a) { _tagSetBoolArray(pChar, "yaht_hold", a); }
function _getScores(pChar) { return _tagGetIntArray(pChar, "yaht_scores", 13, -1); }
function _setScores(pChar, a) { _tagSetIntArray(pChar, "yaht_scores", a); }
function _getTotals(pChar) { return _tagGetIntArray(pChar, "yaht_totals", 4, 0); }
function _setTotals(pChar, a) { _tagSetIntArray(pChar, "yaht_totals", a); }

function _resetHolds(pChar)
{
    var h = [false, false, false, false, false];
    _setHolds(pChar, h);
    _setUsedRoll(pChar, true); // mimic original: selecting a category consumes the roll window
}
function _initIfNeeded(pChar)
{
    if (!pChar.GetTempTag("yaht_inited"))
    {
        _setRoll(pChar, 0);
        _setDice(pChar, [1, 1, 1, 1, 1]);
        _setHolds(pChar, [false, false, false, false, false]);
        _setScores(pChar, [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]);
        _setTotals(pChar, [0, 0, 0, 0]);
        _setUsedRoll(pChar, true);
        pChar.SetTempTag("yaht_inited", "1");
    }
}

function Y_CanPick(scores, catIdx, dice)
{
    // normal categories: only if unscored
    if (catIdx !== 11) return (scores[catIdx] | 0) === -1;

    // Yahtzee: unscored -> can pick; scored 50+ -> allow only if another Yahtzee is rolled
    var cur = scores[11] | 0;
    if (cur === -1) return true;
    if (cur >= 50) return Y_CalcYahtzee(dice) === 50;
    return false;
}

/* --------------------------- Scoring Core (ported 1:1) --------------------------- */
// AddDice (upper section)
function Y_AddDice(dice, face)
{
    var s = 0; for (var i = 0; i < 5; i++) if ((dice[i] | 0) === (face | 0)) s += face; return s;
}
// Totals
function Y_CalcTotals(scores, totals)
{
    var score = 0, i = 0;
    for (i = 0; i < 6; i++) { if ((scores[i] | 0) !== -1) score += scores[i] | 0; }
    totals[0] = score;
    totals[1] = (score >= 63) ? 35 : 0; // <-- ensure it zeroes out when <63
    score = 0;
    for (; i < 13; i++) { if ((scores[i] | 0) !== -1) score += scores[i] | 0; }
    totals[2] = score;
    totals[3] = (totals[0] | 0) + (totals[1] | 0) + (totals[2] | 0);
    return totals;
}
// Three/Four of a kind -> sum of all dice if condition
function Y_CalcMultiples(dice, amount)
{
    for (var face = 1; face <= 6; face++)
    {
        var count = 0; for (var i = 0; i < 5; i++) if ((dice[i] | 0) === face)
        {
            count++; if (count >= amount)
            {
                var sum = 0; for (var j = 0; j < 5; j++) sum += dice[j] | 0; return sum;
            }
        }
    }
    return 0;
}
function Y_CalcChance(dice) { var s = 0; for (var i = 0; i < 5; i++) s += dice[i] | 0; return s; }
function _clone(a) { var b = []; for (var i = 0; i < a.length; i++) b[i] = a[i] | 0; return b; }
function _sort(a) { a.sort(function(x, y) { return (x | 0) - (y | 0); }); return a; }
function Y_CalcFullHouse(dice)
{
    var d = _sort(_clone(dice));
    if (((d[0] == d[1] && d[1] == d[2]) && (d[3] == d[4]) && (d[2] != d[3])) ||
        ((d[0] == d[1]) && (d[2] == d[3] && d[3] == d[4]) && (d[1] != d[2]))) return 25;
    return 0;
}
function Y_CalcShortStraight(dice)
{
    var d = _sort(_clone(dice));
    // move duplicates to the end (as in C#)
    for (var i = 0; i < 4; i++)
    {
        if (d[i] == d[i + 1])
        {
            var tmp = d[i];
            for (var k = i; k < 4; k++) d[k] = d[k + 1];
            d[4] = tmp;
        }
    }
    if ((d[0] == 1 && d[1] == 2 && d[2] == 3 && d[3] == 4) ||
        (d[0] == 2 && d[1] == 3 && d[2] == 4 && d[3] == 5) ||
        (d[0] == 3 && d[1] == 4 && d[2] == 5 && d[3] == 6) ||
        (d[1] == 1 && d[2] == 2 && d[3] == 3 && d[4] == 4) ||
        (d[1] == 2 && d[2] == 3 && d[3] == 4 && d[4] == 5) ||
        (d[1] == 3 && d[2] == 4 && d[3] == 5 && d[4] == 6)) return 30;
    return 0;
}
function Y_CalcLongStraight(dice)
{
    var d = _sort(_clone(dice));
    if ((d[0] == 1 && d[1] == 2 && d[2] == 3 && d[3] == 4 && d[4] == 5) ||
        (d[0] == 2 && d[1] == 3 && d[2] == 4 && d[3] == 5 && d[4] == 6)) return 40;
    return 0;
}
function Y_CalcYahtzee(dice)
{
    return (dice[0] == dice[1] && dice[1] == dice[2] && dice[2] == dice[3] && dice[3] == dice[4]) ? 50 : 0;
}
function Y_AllScored(scores)
{
    for (var i = 0; i < 13; i++) if ((scores[i] | 0) === -1) return false;
    return true;
}

/* --------------------------- Reward --------------------------- */
function Y_GiveReward(pChar, total)
{
    // Mimic original reward: (total - 200)*10, min 0
    var toGive = (total - 200) * 10; if (toGive < 0) toGive = 0;
    if (toGive > 0)
    {
        // Create gold in backpack
        // CreateBlankItem(socket, character, amount, itemName, itemID, colour, objectType, inPack)
        CreateBlankItem(pChar.socket, pChar, toGive, "gold coins", GOLD_TILE_ID, 0x0, "ITEM", true); // in pack
    }
    pChar.SysMessage("Congratulations! You scored " + total + " points and received " + toGive + " gold.");
    pChar.EmoteMessage("*Yahtzee! " + total + " points*");
}

/* --------------------------- UI Build --------------------------- */
function Y_SendGump(pSock)
{
    var pUser = pSock.currentChar; if (!ValidateObject(pUser)) return;

    _initIfNeeded(pUser);

    var roll = _getRoll(pUser);
    var dice = _getDice(pUser);
    var holds = _getHolds(pUser);
    var scores = _getScores(pUser);
    var totals = _getTotals(pUser);
    var usedRoll = _getUsedRoll(pUser);

    var g = new Gump();
    g.AddPage(0);

    // background & title
    g.AddBackground(Y_GUMP_X, Y_GUMP_Y, Y_GUMP_W, Y_GUMP_H, Y_BG);
    g.AddTiledGump(Y_GUMP_X + 10, Y_GUMP_Y + 20, Y_GUMP_W - 20, 16, Y_STRETCH_BAR1);
    g.AddTiledGump(Y_GUMP_X + 10, Y_GUMP_Y + 10, Y_GUMP_W - 20, 20, Y_STRETCH_BAR2);
    g.AddText(Y_GUMP_X + 110, Y_GUMP_Y + 10, 31, "Yahtzee");

    // dice row
    var xBase = Y_GUMP_X + 75;
    var xOff = 25;
    for (var d = 0; d < 5; d++)
    {
        var gID = Y_DICE_BASE_GUMP + (dice[d] | 0);
        g.AddGump(xBase + d * xOff, Y_GUMP_Y + 60, gID);
    }

    // Roll block
    g.AddText(Y_GUMP_X + 20, Y_GUMP_Y + 60, 1149, "Roll");
    g.AddText(Y_GUMP_X + 30, Y_GUMP_Y + 80, 1149, "" + roll);
    if (roll < 3)
    {
        // AddButton(x,y, up, ???, page, buttonID)
        g.AddButton(Y_GUMP_X + 50, Y_GUMP_Y + 62, Y_BTN_GUMP_UP, 1, 0, BTN_ROLL);
    }

    // Hold toggles (visual check + button hotspot)
    if (roll < 3)
    {
        g.AddText(Y_GUMP_X + 205, Y_GUMP_Y + 85, 1149, "Hold");
        for (d = 0; d < 5; d++)
        {
            // draw check look (2715 = checked, 2714 = unchecked – same art used in your C#)
            var checkGump = holds[d] ? 2715 : 2714;
            g.AddGump(xBase + d * xOff + 1, Y_GUMP_Y + 85, checkGump);
            // clickable tiny button overlapping the check image to toggle
            var btnId = BTN_HOLD1 + d;
            g.AddButton(xBase + d * xOff, Y_GUMP_Y + 85, Y_BTN_GUMP_UP, 1, 0, btnId);
        }
    }

    // Section labels (left column)
    var yBaseUpper = Y_GUMP_Y + 115;
    var yOff = 20;
    var labels = ["Ones", "Twos", "Threes", "Fours", "Fives", "Sixes"];
    for (var i = 0; i < labels.length; i++)
    {
        g.AddText(Y_GUMP_X + 20, yBaseUpper + i * yOff, 1149, labels[i]);
    }
    g.AddText(Y_GUMP_X + 65, Y_GUMP_Y + 235, 1152, "Upper Total:");
    g.AddText(Y_GUMP_X + 100, Y_GUMP_Y + 255, 1152, "Bonus:");

    // Lower labels
    var yBaseLower = Y_GUMP_Y + 295;
    var lowers = ["Three of a kind", "Four of a kind", "Full House", "Short Straight", "Long Straight", "Yahtzee", "Chance"];
    for (i = 0; i < lowers.length; i++)
    {
        g.AddText(Y_GUMP_X + 20, yBaseLower + i * yOff, 1149, lowers[i]);
    }
    g.AddText(Y_GUMP_X + 63, Y_GUMP_Y + 435, 1152, "Lower Total:");
    g.AddText(Y_GUMP_X + 65, Y_GUMP_Y + 455, 1152, "Grand Total:");

    if (!usedRoll)
    {
        for (i = 0; i < 6; i++)
        {
            if (Y_CanPick(scores, i, dice))
                g.AddButton(Y_BTN_X, yBaseUpper + i * yOff + Y_BTN_Y_ADJ, Y_BTN_GUMP_UP, 1, 0, BTN_ONES + i);
        }
    }

    if (!usedRoll)
    {
        var y = yBaseLower;
        var map = [
            [6, BTN_3KIND, 0],
            [7, BTN_4KIND, 1],
            [8, BTN_FULLHOUSE, 2],
            [9, BTN_SSTRAIGHT, 3],
            [10, BTN_LSTRAIGHT, 4],
            [11, BTN_YAHTZEE, 5],
            [12, BTN_CHANCE, 6]
        ];
        for (var m = 0; m < map.length; m++)
        {
            var idx = map[m][0];
            var btnID = map[m][1];
            var row = map[m][2];
            if (Y_CanPick(scores, idx, dice))
                g.AddButton(Y_BTN_X, y + row * yOff + Y_BTN_Y_ADJ, Y_BTN_GUMP_UP, 1, 0, btnID);
        }
    }

    // Score columns (right side)
    // Upper (6 lines)
    for (i = 0; i < 6; i++)
    {
        var s = scores[i] | 0;
        g.AddText(Y_GUMP_X + 185, yBaseUpper + i * yOff, 1149, (s > -1 ? ("" + s) : "-"));
    }
    // Lower (7 lines)
    for (; i < 13; i++)
    {
        s = scores[i] | 0;
        g.AddText(Y_GUMP_X + 185, yBaseLower + (i - 6) * yOff, 1149, (s > -1 ? ("" + s) : "-"));
    }

    // Totals
    g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 255, 1152, "" + (totals[0] | 0)); // upper total
    g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 275, 1152, "" + (totals[1] | 0)); // bonus
    g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 435, 1152, "" + (totals[2] | 0)); // lower total
    g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 455, 1152, "" + (totals[3] | 0)); // grand total

    // In Y_SendGump, after you draw totals, show when all scored:
    if (Y_AllScored(scores))
    {
        // draw a New Game button/footer
        g.AddButton(Y_GUMP_X + 20, Y_GUMP_Y + 470, Y_BTN_GUMP_UP, 1, 0, BTN_NEWGAME);
        g.AddText(Y_GUMP_X + 55, Y_GUMP_Y + 470, 1152, "New Game");
    }

    g.Send(pSock);
    g.Free();
}

/* --------------------------- Game Actions --------------------------- */
function Y_ToggleHold(pChar, idx)
{
    var h = _getHolds(pChar);
    h[idx] = !h[idx];
    _setHolds(pChar, h);
}
function Y_Roll(pChar)
{
    var roll = _getRoll(pChar);
    if (roll >= 3) return;
    var dice = _getDice(pChar);
    var holds = _getHolds(pChar);
    for (var i = 0; i < 5; i++)
    {
        if (!holds[i] || roll === 0)
        {
            dice[i] = RandomNumber(1, 6); // inclusive
        }
    }
    _setDice(pChar, dice);
    _setRoll(pChar, roll + 1);
    _setUsedRoll(pChar, false);
}
function Y_ScoreCategory(pChar, catBtn)
{
    var dice = _getDice(pChar);
    var scores = _getScores(pChar);

    function setScore(idx, val)
    {
        if ((scores[idx] | 0) === -1)
        {
            scores[idx] = val | 0;
            _setScores(pChar, scores);
            _setRoll(pChar, 0);
            _resetHolds(pChar);
        }
    }

    switch (catBtn)
    {
        case BTN_ONES: setScore(0, Y_AddDice(dice, 1)); break;
        case BTN_TWOS: setScore(1, Y_AddDice(dice, 2)); break;
        case BTN_THREES: setScore(2, Y_AddDice(dice, 3)); break;
        case BTN_FOURS: setScore(3, Y_AddDice(dice, 4)); break;
        case BTN_FIVES: setScore(4, Y_AddDice(dice, 5)); break;
        case BTN_SIXES: setScore(5, Y_AddDice(dice, 6)); break;

        case BTN_3KIND: setScore(6, Y_CalcMultiples(dice, 3)); break;
        case BTN_4KIND: setScore(7, Y_CalcMultiples(dice, 4)); break;
        case BTN_FULLHOUSE: setScore(8, Y_CalcFullHouse(dice)); break;
        case BTN_SSTRAIGHT: setScore(9, Y_CalcShortStraight(dice)); break;
        case BTN_LSTRAIGHT: setScore(10, Y_CalcLongStraight(dice)); break;

        case BTN_YAHTZEE:
            // First Yahtzee fills slot; subsequent Yahtzees add +50 if you roll another 50 (mirrors C#)
            var cur = scores[11] | 0;
            var rolled = Y_CalcYahtzee(dice);
            if (cur === -1)
            {
                setScore(11, rolled);
            } else if (cur >= 50 && rolled === 50)
            {
                scores[11] = cur + 50;
                _setScores(pChar, scores);
                _setRoll(pChar, 0);
                _resetHolds(pChar);
            }
            break;

        case BTN_CHANCE: setScore(12, Y_CalcChance(dice)); break;
    }
    // Update totals
    var totals = Y_CalcTotals(_getScores(pChar), _getTotals(pChar));
    _setTotals(pChar, totals);
}

/* --------------------------- Events --------------------------- */
function onGumpPress(pSock, pButton, gumpData)
{
    var pUser = pSock.currentChar; if (!ValidateObject(pUser)) return;
    if (pButton === 0) { return; } // closed

    _initIfNeeded(pUser);

    if (pButton >= BTN_HOLD1 && pButton <= BTN_HOLD5)
    {
        Y_ToggleHold(pUser, pButton - BTN_HOLD1);
        Y_SendGump(pSock);
        return;
    }
    if (pButton === BTN_ROLL)
    {
        Y_Roll(pUser);
        Y_SendGump(pSock);
        return;
    }

    if (pButton === BTN_NEWGAME)
    {
        Y_ResetGame(pUser);
        _initIfNeeded(pUser);
        Y_SendGump(pSock);
        return;
    }

    // Category pick
    Y_ScoreCategory(pUser, pButton);

    // Totals + end check
    var totals = _getTotals(pUser);
    var scores = _getScores(pUser);
    if (Y_AllScored(scores))
    {
        Y_GiveReward(pUser, totals[3] | 0);
        // Lock UI at end (shows roll=3 and disables picks)
        _setRoll(pUser, 3);
        _setUsedRoll(pUser, true);
    }
    Y_SendGump(pSock);
}

// Reset all state so a brand-new game starts on next open
function Y_ResetGame(pChar)
{
    // Prefer DelTempTag when available; fall back to blanking
    function _del(k)
    {
        if (typeof pChar.DelTempTag === "function") pChar.DelTempTag(k);
        else pChar.SetTempTag(k, "");
    }
    _del("yaht_inited");
    _del("yaht_roll");
    _del("yaht_usedRoll");
    _del("yaht_dice");
    _del("yaht_hold");
    _del("yaht_scores");
    _del("yaht_totals");
    _del("yaht_itemSer"); // optional binding (see #3)
}

// Test command: [yahtzee
/** @param {Socket} socket */
function command_YAHTZEE(socket, cmdString)
{
    if (!socket || !ValidateObject(socket.currentChar)) return;
    var p = (cmdString || "").trim().toLowerCase();
    if (p === "reset")
    {
        Y_ResetGame(socket.currentChar);
    }
    _initIfNeeded(socket.currentChar);
    Y_SendGump(socket);
}

function onUseChecked(pUser, iUsed)
{
    if (ValidateObject(iUsed) && iUsed.isItem)
    {
        var ser = iUsed.serial | 0;
        var last = parseInt(pUser.GetTempTag("yaht_itemSer") || "0", 10) | 0;
        if (last !== ser)
        {
            Y_ResetGame(pUser);
            pUser.SetTempTag("yaht_itemSer", "" + ser);
        }
    }
    _initIfNeeded(pUser);
    Y_SendGump(pUser.socket);
    return false;
}
Post Reply