Greasy Fork is available in English.
Updated Win Chance to "Strategic Probability" using look-ahead filtering logic.
// ==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();
})();