Greasy Fork

Greasy Fork is available in English.

Torn Chest Solver (Combo Solver)

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

当前为 2025-12-24 提交的版本,查看 最新版本

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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();
})();