Torn Chest Solver (Combo Solver)

4x3 grid, "Suggested Number" title, and professional donation phrasing.

目前為 2025-12-24 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Torn Chest Solver (Combo Solver)
// @namespace    Gemini.Torn
// @version      5.8
// @description  4x3 grid, "Suggested Number" title, and professional donation phrasing.
// @author       Gemini
// @match        *.torn.com/christmas_town.php*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let storage = JSON.parse(localStorage.getItem('tornSolverData')) || {
        history: [], currentInput: [], x: 20, y: 80, minimized: false
    };
    
    if (storage.x < 0 || storage.x > window.innerWidth) storage.x = 20;
    if (storage.y < 0 || storage.y > window.innerHeight) storage.y = 80;

    function saveData() { localStorage.setItem('tornSolverData', JSON.stringify(storage)); }
    let showHelp = false;

    const style = document.createElement('style');
    style.innerHTML = `
        @keyframes blinker { 50% { opacity: 0.2; color: #fff; } }
        .blinking-link { animation: blinker 1.2s linear infinite; }
        .help-text b { color: #0f0; font-size: 12px; }
        .help-text p { margin: 8px 0; color: #ccc; }
        .num-btn { padding: 12px 0; background: #333; color: #fff; border: none; border-radius: 4px; font-weight: bold; font-size: 16px; cursor: pointer; }
        .num-btn:active { background: #555; }
    `;
    document.head.appendChild(style);

    function createUI() {
        if (document.getElementById("torn-solver-window")) return;
        const container = document.createElement('div');
        container.id = "torn-solver-window";
        container.style = `position: fixed; top: ${storage.y}px; left: ${storage.x}px; z-index: 999999; background: #222; color: #fff; border-radius: 10px; border: 2px solid #444; font-family: sans-serif; width: 250px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); touch-action: none; user-select: none;`;
        
        const header = document.createElement('div');
        header.style = "background: #444; padding: 10px; cursor: move; border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center; touch-action: none;";
        header.innerHTML = `
            <button id="help-toggle" style="background: #0af; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; font-weight: bold; cursor: pointer;">?</button>
            <span style="font-weight: bold; font-size: 10px; color: #ff4444;">COMBO SOLVER</span>
            <button id="min-btn" style="background: #666; color: white; border: none; border-radius: 3px; width: 25px; height: 20px;">${storage.minimized ? '□' : '_'}</button>
        `;
        container.appendChild(header);

        const contentArea = document.createElement('div');
        contentArea.style = "padding: 10px; display: " + (storage.minimized ? "none" : "block");
        container.appendChild(contentArea);
        document.body.appendChild(container);

        const render = () => {
            if (storage.minimized) {
                contentArea.style.display = "none";
                document.getElementById('min-btn').innerText = "□";
                container.style.width = "140px";
            } else if (showHelp) {
                contentArea.style.display = "block";
                container.style.width = "250px";
                contentArea.innerHTML = `
                    <div class="help-text" style="font-size:11px; line-height:1.4; max-height:350px; overflow-y:auto; padding-right:5px;">
                        <b>🌟 STEP 1: LOOK AT THE BOX</b>
                        <p>At the top, this tool shows a "Suggested Number." Type those numbers into the game and into this tool's keypad.</p>
                        
                        <b>🌟 STEP 2: COPY THE COLORS</b>
                        <p>After you guess in the game, the game boxes will turn colors. Tap the boxes in THIS tool to match them!</p>
                        
                        <b>🌟 STEP 3: WHAT COLORS MEAN?</b>
                        <p>⬜ <b>Gray:</b> We haven't guessed yet.<br>
                           🟥 <b>Red:</b> That number is NOT in the code.<br>
                           🟨 <b>Yellow:</b> The number is correct, but in the WRONG spot.<br>
                           🟩 <b>Green:</b> Perfect! This number is correct and in the right spot.</p>
                        
                        <b>🌟 STEP 4: HIT THE "OK" BUTTON</b>
                        <p>Once the colors in this tool match the game colors, hit OK. The tool will calculate the next best guess!</p>

                        <hr style="border:0; border-top:1px solid #444; margin:10px 0;">
                        <div style="font-style:italic; color:#bbb; text-align:center;">
                            Questions, comments, concerns, and donations can be directed to:
                            <br><br>
                            <a href="https://www.torn.com/profiles.php?XID=3262527" target="_blank" class="blinking-link" style="color:#ff4444; text-decoration:none; font-weight:bold; font-size:12px;">Weeb_Phydoe [3262527]</a>
                        </div>
                        <button id="close-help" style="margin-top:10px; width:100%; background:#0af; color:white; border:none; padding:10px; border-radius:4px; font-weight:bold; cursor:pointer;">BACK</button>
                    </div>
                `;
                document.getElementById('close-help').onclick = () => { showHelp = false; render(); };
            } else {
                contentArea.style.display = "block";
                container.style.width = "250px";
                const {suggestion, prob} = solveLogic();
                
                contentArea.innerHTML = `
                    <div style="text-align:center; background:#111; padding:6px; border-radius:5px; margin-bottom:10px;">
                        <span style="font-size:9px; color:#888;">✨ SUGGESTED NUMBER ✨</span><br>
                        <b style="color:#0f0; font-size:22px; letter-spacing:3px;">${suggestion}</b>
                        <div style="font-size:10px; color:#0af; margin-top:3px;">Win Chance: ${prob}%</div>
                    </div>
                    <div style="display:flex; justify-content:center; gap:8px; margin-bottom:10px;">
                        ${[0,1,2].map(i => {
                            const item = storage.currentInput[i] || {num: '-', color: '#444'};
                            return `<div class="input-slot" data-index="${i}" style="width:55px; height:55px; background:${item.color}; display:flex; align-items:center; justify-content:center; border-radius:10px; font-weight:bold; font-size:30px; border:1px solid #666;">${item.num}</div>`;
                        }).join('')}
                    </div>
                    <div style="display:grid; grid-template-columns: repeat(4, 1fr); gap:6px; margin-bottom:10px;">
                        ${[1,2,3,4,5,6,7,8,9,0,'C','OK'].map(k => {
                            let style = "";
                            if (k === 'OK') style = "background:#080;";
                            if (k === 'C') style = "background:#800;";
                            return `<button class="num-btn" data-val="${k}" style="${style}">${k}</button>`;
                        }).join('')}
                    </div>
                    <button id="reset-game-btn" style="width:100%; background:#444; color:#fff; border:none; padding:6px; border-radius:4px; font-size:9px;">NEW GAME</button>
                `;

                contentArea.querySelectorAll('.input-slot').forEach(slot => {
                    slot.onclick = () => {
                        const i = slot.getAttribute('data-index');
                        if (!storage.currentInput[i]) return;
                        const colors = ['#666', '#ff4444', '#ffcc00', '#00aa00'];
                        storage.currentInput[i].color = colors[(colors.indexOf(storage.currentInput[i].color) + 1) % colors.length];
                        saveData(); render();
                    };
                });

                contentArea.querySelectorAll('.num-btn').forEach(btn => btn.onclick = () => {
                    const v = btn.getAttribute('data-val');
                    if (v === 'C') storage.currentInput = [];
                    else if (v === 'OK' && storage.currentInput.length === 3) {
                        storage.history.push([...storage.currentInput]); storage.currentInput = [];
                    } else if (storage.currentInput.length < 3 && !isNaN(v)) {
                        storage.currentInput.push({num: v, color: '#666'});
                    }
                    saveData(); render();
                });
                document.getElementById('reset-game-btn').onclick = () => { storage.history = []; storage.currentInput = []; saveData(); render(); };
            }
        };

        let isDragging = false, startX, startY;
        const dStart = (e) => { const t = e.touches ? e.touches[0] : e; isDragging = true; startX = t.clientX - container.offsetLeft; startY = t.clientY - container.offsetTop; };
        const dMove = (e) => { if (!isDragging) return; const t = e.touches ? e.touches[0] : e; storage.x = t.clientX - startX; storage.y = t.clientY - startY; container.style.left = storage.x + "px"; container.style.top = storage.y + "px"; if (e.cancelable) e.preventDefault(); };
        const dEnd = () => { isDragging = false; saveData(); };
        header.addEventListener('mousedown', dStart); header.addEventListener('touchstart', dStart, {passive: false});
        window.addEventListener('mousemove', dMove); window.addEventListener('touchmove', dMove, {passive: false});
        window.addEventListener('mouseup', dEnd); window.addEventListener('touchend', dEnd);
        document.getElementById('help-toggle').onclick = () => { showHelp = !showHelp; render(); };
        document.getElementById('min-btn').onclick = () => { storage.minimized = !storage.minimized; saveData(); render(); };
        render();
    }

    function solveLogic() {
        let possibilities = [];
        for (let i = 0; i <= 999; i++) {
            let p = i.toString().padStart(3, '0').split('');
            if (new Set(p).size === 3) possibilities.push(p);
        }
        storage.history.forEach(attempt => {
            possibilities = possibilities.filter(p => {
                for (let i = 0; i < 3; i++) {
                    const color = attempt[i].color;
                    const num = attempt[i].num;
                    if (color === '#00aa00' && p[i] !== num) return false;
                    if (color === '#ff4444' && p.includes(num)) return false;
                    if (color === '#ffcc00') { if (!p.includes(num) || p[i] === num) return false; }
                }
                return true;
            });
        });

        const count = possibilities.length;
        const confidence = count === 0 ? 0 : Math.floor(100 / count);

        if (storage.history.length < 3 && count > 1) {
            let tested = new Set();
            storage.history.forEach(att => att.forEach(item => tested.add(item.num)));
            let untried = "123456789".split("").filter(d => !tested.has(d));
            if (untried.length >= 3) return { suggestion: untried.slice(0,3).join(" "), prob: confidence };
        }
        return { suggestion: possibilities[0] ? possibilities[0].join(" ") : "???", prob: confidence };
    }

    setInterval(createUI, 2000);
    createUI();
})();