Greasy Fork is available in English.
4x3 grid, "Suggested Number" title, logic restricted to digits 1-9.
// ==UserScript==
// @name Torn Chest Solver (No-Zero Edition)
// @namespace Gemini.Torn
// @version 5.9
// @description 4x3 grid, "Suggested Number" title, logic restricted to digits 1-9.
// @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, and concerns 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,'C','OK'].map(k => {
let style = "";
if (k === 'OK') style = "background:#080; grid-column: span 2;";
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 = 111; i <= 999; i++) {
let p = i.toString().split('');
if (!p.includes('0') && 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);
// Turn 1, 2, 3 Sweep (using only 1-9)
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();
})();