Greasy Fork

Greasy Fork is available in English.

Combination Chest Solver for Torn Christmas Town ( NON-PDA )

Updated Win Chance to "Strategic Probability" using look-ahead filtering logic.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Combination Chest Solver for Torn Christmas Town ( NON-PDA )
// @namespace    torn.christmas.chest.solver
// @version      5.2
// @description  Updated Win Chance to "Strategic Probability" using look-ahead filtering logic.
// @match        https://www.torn.com/christmas_town.php*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    if (!window.location.href.includes('christmas_town.php')) return;

    const digits = "123456789";
    let possibleCodes = [];
    let attemptsUsed = 0;
    let currentFeedback = "";
    let historyState = [];
    const MAX_ATTEMPTS = 4;

    function generateCodes() {
        const codes = [];
        for (let a of digits) {
            for (let b of digits) {
                for (let c of digits) {
                    if (a !== b && b !== c && a !== c) codes.push(a + b + c);
                }
            }
        }
        return codes;
    }

    function score(guess, code) {
        let res = "";
        for (let i = 0; i < 3; i++) {
            if (guess[i] === code[i]) res += "g";
            else if (code.includes(guess[i])) res += "y";
            else res += "r";
        }
        return res;
    }

    function getBestGuess() {
        if (possibleCodes.length === 0) return "---";
        if (possibleCodes.length === 1) return possibleCodes[0];
        let bestCode = possibleCodes[0], maxEntropy = -1;
        const allPossible = generateCodes();
        const candidates = possibleCodes.length < 20 ? allPossible : possibleCodes;
        for (let guess of candidates) {
            let groups = {};
            for (let code of possibleCodes) {
                let res = score(guess, code);
                groups[res] = (groups[res] || 0) + 1;
            }
            let entropy = 0;
            for (let res in groups) {
                let p = groups[res] / possibleCodes.length;
                entropy -= p * Math.log2(p);
            }
            if (possibleCodes.includes(guess)) entropy += 0.01;
            if (entropy > maxEntropy) { maxEntropy = entropy; bestCode = guess; }
        }
        return bestCode;
    }

    // NEW: Calculate probability based on filtering efficiency
    function calculateStrategicProb(count, turns, bestMove) {
        if (count <= turns) return 100;
        if (turns <= 0 || count === 0) return 0;

        // Estimate how many possibilities the best move eliminates on average
        let groups = {};
        for (let code of possibleCodes) {
            let res = score(bestMove, code);
            groups[res] = (groups[res] || 0) + 1;
        }

        let avgRemaining = 0;
        for (let res in groups) {
            avgRemaining += (groups[res] * groups[res]);
        }
        avgRemaining /= count;

        // If filtering leaves us with fewer candidates than remaining turns, we are likely to win
        let strength = (turns / avgRemaining) * 100;
        return Math.min(100, Math.max(Math.floor(strength), Math.floor((turns/count)*100)));
    }

    function getColorScale(pct) {
        return `rgb(${Math.floor(255 * (1 - pct))}, ${Math.floor(255 * pct)}, 50)`;
    }

    function createUI() {
        if (document.getElementById("chestSolverBox")) return;
        const box = document.createElement("div");
        box.id = "chestSolverBox";

        const savedTop = localStorage.getItem('cs_top') || "120px";
        const savedLeft = localStorage.getItem('cs_left') || "calc(100% - 300px)";
        const isMinimized = localStorage.getItem('cs_minimized') === 'true';

        box.style = `position: fixed; top: ${savedTop}; left: ${savedLeft}; width: ${isMinimized ? '180px' : '280px'}; background: #222; color: #fff; border: 1px solid #444; border-radius: 8px; z-index: 999999; box-shadow: 0 4px 15px rgba(0,0,0,0.5); font-family: 'Segoe UI', Arial, sans-serif; overflow: hidden;`;

        box.innerHTML = `
            <style>
                #cs_best_move_container:hover { background: #1a1a1a !important; border-color: #555 !important; }
                .cs_color_btn { flex: 1; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; color: white; font-weight: bold; height: 35px; }
            </style>
            <div id="cs_header" style="background: #333; padding: 10px; border-bottom: 1px solid #444; cursor: move; display: flex; justify-content: space-between; align-items: center; user-select: none;">
                <strong style="color: #ff4545; font-size: 13px;">🎄 Chest Solver Elite</strong>
                <div id="cs_min_btn" style="cursor: pointer; padding: 0 5px; font-weight: bold; color: #aaa; font-size: 18px; line-height: 10px;">${isMinimized ? '+' : '−'}</div>
            </div>
            <div id="cs_content" style="display: ${isMinimized ? 'none' : 'block'};">
                <div id="cs_body" style="padding: 12px;">
                    <div id="cs_error_msg" style="display:none; color: #ff4444; font-size: 11px; text-align: center; margin-bottom: 8px; font-weight: bold; background: rgba(255,68,68,0.1); padding: 5px; border-radius: 4px;"></div>

                    <div id="cs_best_move_container" title="Click to use this move" style="background: #111; padding: 10px; border-radius: 4px; margin-bottom: 12px; border: 1px solid #444; text-align: center; cursor: pointer;">
                        <div style="font-size: 10px; color: #aaa; font-weight: bold; letter-spacing: 1.5px; margin-bottom: 4px;">IDEAL MOVE 🖱️</div>
                        <div id="cs_best_move" style="font-size: 26px; color: #4CAF50; font-weight: bold; letter-spacing: 5px;">---</div>
                    </div>

                    <div style="display: flex; gap: 8px; margin-bottom: 12px;">
                        <div style="flex: 1; background: #2a2a2a; padding: 8px; border-radius: 4px; text-align: center; border: 1px solid #333;"><div style="font-size: 10px; color: #aaa;">TURNS</div><strong id="cs_attempts_left" style="font-size: 16px;">4</strong></div>
                        <div style="flex: 1.8; background: #2a2a2a; padding: 8px; border-radius: 4px; text-align: center; border: 1px solid #333;"><div style="font-size: 10px; color: #aaa;">STRATEGIC PROBABILITY</div><strong id="cs_win_chance" style="font-size: 16px; color: #4CAF50;">100%</strong></div>
                    </div>

                    <input id="cs_guess" maxlength="3" placeholder="ENTER 3-DIGIT COMBO" style="width: 100%; background: #111; color: #fff; border: 1px solid #555; padding: 8px; border-radius: 4px; text-align: center; font-weight: bold; font-size: 15px; margin-bottom: 10px;">

                    <div style="margin-bottom: 15px;">
                        <div style="display: flex; gap: 5px; margin-bottom: 4px; font-size: 8px; text-align: center; font-weight: bold;">
                            <div style="flex: 1; color: #4CAF50;">RIGHT SPOT</div>
                            <div style="flex: 1; color: #FFC107;">WRONG SPOT</div>
                            <div style="flex: 1; color: #F44336;">NOT IN CODE</div>
                        </div>
                        <div style="display: flex; gap: 5px; height: 35px; margin-bottom: 8px;">
                            <button id="btn_g" class="cs_color_btn" style="background: #4CAF50;">G</button>
                            <button id="btn_y" class="cs_color_btn" style="background: #FFC107;">Y</button>
                            <button id="btn_r" class="cs_color_btn" style="background: #F44336;">R</button>
                        </div>
                        <div id="cs_feedback_display" style="height: 30px; background: #111; border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 20px; font-family: monospace; letter-spacing: 8px; font-weight: bold; border: 1px dashed #444;"></div>
                    </div>

                    <div style="display: flex; gap: 5px; margin-bottom: 15px;">
                        <button id="cs_add" style="flex: 2; background: #2a7cff; color: #fff; border: none; padding: 10px; border-radius: 4px; cursor: pointer; font-weight: bold;">SUBMIT DATA</button>
                        <button id="cs_undo" style="flex: 1; background: #555; color: #fff; border: none; padding: 10px; border-radius: 4px; cursor: pointer;">Undo</button>
                        <button id="cs_reset" style="flex: 1; background: #444; color: #ccc; border: none; padding: 10px; border-radius: 4px; cursor: pointer;">Reset</button>
                    </div>

                    <div style="background: #111; padding: 8px; border-radius: 4px; margin-bottom: 10px; border: 1px solid #444;">
                        <div style="display: flex; justify-content: space-around; text-align: center;">
                            <div>
                                <div style="font-size: 8px; color: #aaa; font-weight: bold; margin-bottom: 2px;">KNOWN POSITIONS</div>
                                <div id="cs_pattern" style="font-family: monospace; font-size: 16px; color: #4CAF50; letter-spacing: 5px; font-weight: bold;">_ _ _</div>
                            </div>
                            <div style="border-left: 1px solid #333; padding-left: 10px;">
                                <div style="font-size: 8px; color: #aaa; font-weight: bold; margin-bottom: 2px;">DISCARDED</div>
                                <div id="cs_discarded" style="font-family: monospace; font-size: 11px; color: #F44336; font-weight: bold; max-width: 100px; word-wrap: break-word;">None</div>
                            </div>
                        </div>
                    </div>

                    <div style="font-size: 10px; color: #aaa; font-weight: bold; margin-bottom: 4px; display: flex; justify-content: space-between;">
                        <span>ENTRY HISTORY</span>
                        <span>REMAINING</span>
                    </div>
                    <div id="cs_history" style="max-height: 80px; overflow-y: auto; font-family: monospace; font-size: 11px; background: #111; padding: 8px; border-radius: 4px; border: 1px solid #333; margin-bottom: 10px;"></div>

                    <div id="cs_results_area">
                        <div style="font-size: 11px; margin-bottom: 5px; color: #aaa;">Likely Solutions (<span id="cs_count"></span>):</div>
                        <div id="cs_results" style="font-family: monospace; font-size: 11px; color: #88eeff; display: flex; flex-direction: column; gap: 4px; max-height: 80px; overflow-y: auto;"></div>
                    </div>
                </div>
            </div>
        `;
        document.body.appendChild(box);

        // Draggable logic and all button listeners...
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        const header = document.getElementById("cs_header");
        header.onmousedown = (e) => {
            if (e.target.id === "cs_min_btn") return;
            e.preventDefault();
            pos3 = e.clientX; pos4 = e.clientY;
            document.onmouseup = () => {
                document.onmouseup = null;
                document.onmousemove = null;
                localStorage.setItem('cs_top', box.style.top);
                localStorage.setItem('cs_left', box.style.left);
            };
            document.onmousemove = (e) => {
                e.preventDefault();
                pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY;
                pos3 = e.clientX; pos4 = e.clientY;
                box.style.top = (box.offsetTop - pos2) + "px";
                box.style.left = (box.offsetLeft - pos1) + "px";
            };
        };

        const minBtn = document.getElementById("cs_min_btn");
        const content = document.getElementById("cs_content");
        minBtn.onclick = () => {
            const becomingMinimized = content.style.display !== "none";
            content.style.display = becomingMinimized ? "none" : "block";
            box.style.width = becomingMinimized ? "180px" : "280px";
            minBtn.textContent = becomingMinimized ? "+" : "−";
            localStorage.setItem('cs_minimized', becomingMinimized);
        };

        const gInput = document.getElementById("cs_guess");
        gInput.oninput = updateFeedbackDisplay;

        document.getElementById("btn_g").onclick = () => addColor('g');
        document.getElementById("btn_y").onclick = () => addColor('y');
        document.getElementById("btn_r").onclick = () => addColor('r');
        document.getElementById("cs_add").onclick = addGuess;
        document.getElementById("cs_undo").onclick = undoLast;
        document.getElementById("cs_reset").onclick = resetSolver;
        document.getElementById("cs_best_move_container").onclick = function() {
            const move = document.getElementById("cs_best_move").innerText;
            if (move !== "---") { gInput.value = move; hideError(); updateFeedbackDisplay(); }
        };

        resetSolver();
    }

    function addColor(char) {
        const val = document.getElementById("cs_guess").value;
        if (val.length === 3 && currentFeedback.length < 3) {
            currentFeedback += char;
            updateFeedbackDisplay();
            hideError();
        } else if (val.length < 3) {
            showError("Input 3 digits first.");
        }
    }

    function updateFeedbackDisplay() {
        const display = document.getElementById("cs_feedback_display");
        const val = document.getElementById("cs_guess").value;
        let html = "";
        for (let i = 0; i < 3; i++) {
            let num = val[i] || "_";
            let color = "#555";
            if (currentFeedback[i]) {
                color = currentFeedback[i] === 'g' ? "#4CAF50" : (currentFeedback[i] === 'y' ? "#FFC107" : "#F44336");
            }
            html += `<span style="color:${color}">${num}</span>`;
        }
        display.innerHTML = html;
    }

    function updateTrackers() {
        let pattern = ["_", "_", "_"];
        let discarded = new Set(digits.split(""));
        if (possibleCodes.length > 0) {
            for (let i = 0; i < 3; i++) {
                let char = possibleCodes[0][i];
                if (possibleCodes.every(code => code[i] === char)) pattern[i] = char;
            }
            possibleCodes.forEach(code => {
                for (let char of code) discarded.delete(char);
            });
        }
        document.getElementById("cs_pattern").textContent = pattern.join(" ");
        const dArr = Array.from(discarded).sort();
        document.getElementById("cs_discarded").textContent = dArr.length > 0 ? dArr.join(" ") : "None";
    }

    function addGuess() {
        const gInput = document.getElementById("cs_guess");
        const g = gInput.value;
        const f = currentFeedback;
        const histDiv = document.getElementById("cs_history");

        if (g.length < 3 || f.length < 3) {
            showError(g.length < 3 ? "Need 3 digits." : "Assign 3 colors.");
            return;
        }

        historyState.push({ codes: [...possibleCodes], attempts: attemptsUsed, histHTML: histDiv.innerHTML });
        attemptsUsed++;
        possibleCodes = possibleCodes.filter(c => score(g, c) === f);

        let coloredGuess = "";
        for (let i = 0; i < 3; i++) {
            let color = f[i] === 'g' ? "#4CAF50" : (f[i] === 'y' ? "#FFC107" : "#F44336");
            coloredGuess += `<span style="color:${color}">${g[i]}</span>`;
        }
        const row = `<div style="display:flex; justify-content:space-between; margin-bottom:2px; border-bottom:1px solid #222;">
            <span style="font-weight: bold;">${coloredGuess}</span>
            <span style="color:#888; font-size:10px;">${possibleCodes.length} left</span>
        </div>`;
        histDiv.innerHTML = row + histDiv.innerHTML;

        updateUI();
        gInput.value = "";
        currentFeedback = "";
        updateFeedbackDisplay();
    }

    function undoLast() {
        if (historyState.length === 0) return;
        const last = historyState.pop();
        possibleCodes = last.codes;
        attemptsUsed = last.attempts;
        document.getElementById("cs_history").innerHTML = last.histHTML;
        updateUI();
    }

    function updateUI() {
        const count = possibleCodes.length;
        const left = Math.max(0, MAX_ATTEMPTS - attemptsUsed);
        document.getElementById("cs_attempts_left").textContent = left;
        document.getElementById("cs_count").textContent = count;

        const bestMove = getBestGuess();
        document.getElementById("cs_best_move").textContent = bestMove;

        const winEl = document.getElementById("cs_win_chance");
        let prob = calculateStrategicProb(count, left, bestMove);
        winEl.textContent = prob + "%"; winEl.style.color = getColorScale(prob / 100);

        const resEl = document.getElementById("cs_results");
        resEl.innerHTML = "";
        possibleCodes.slice(0, 20).forEach(c => {
            const row = document.createElement("div");
            row.style = "cursor: pointer; border-bottom: 1px solid #222; padding: 2px;";
            row.onclick = () => { document.getElementById('cs_guess').value = c; updateFeedbackDisplay(); };
            row.innerHTML = `<span>${c}</span>`;
            resEl.appendChild(row);
        });
        updateTrackers();
    }

    function resetSolver() {
        possibleCodes = generateCodes(); attemptsUsed = 0; historyState = []; currentFeedback = "";
        document.getElementById("cs_guess").value = "";
        const h = document.getElementById("cs_history"); if(h) h.innerHTML = "";
        updateUI();
        updateFeedbackDisplay();
    }

    function showError(msg) {
        const err = document.getElementById("cs_error_msg");
        err.textContent = msg; err.style.display = "block";
        setTimeout(() => { err.style.display = "none"; }, 3000);
    }
    function hideError() { document.getElementById("cs_error_msg").style.display = "none"; }

    const observer = new MutationObserver(() => { if (document.body) createUI(); });
    observer.observe(document.documentElement, { childList: true, subtree: true });
    createUI();
})();