Neopets: Clockwork Codebreaker Solver

Embeds a Clockwork Codebreaker solver to the top of Jellyneo's page for that game

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name             Neopets: Clockwork Codebreaker Solver
// @namespace        kmtxcxjx
// @version          1.0
// @description      Embeds a Clockwork Codebreaker solver to the top of Jellyneo's page for that game
// @match            https://www.jellyneo.net/?go=clockwork_codebreaker
// @grant            none
// @run-at           document-end
// @icon             https://images.neopets.com/games/aaa/dailydare/2012/post/theme-icon.png
// @license          MIT
// ==/UserScript==

(function () {
    const TIME_LIMIT = 200; // 200 milliseconds per guess maximum
    let digits, length, candidates, currentGuess, colors;

    const COLOR_SETS = {
        1: ["Blank", "Red", "Orange", "Pink", "White"],
        2: ["Blank", "Red", "Orange", "Yellow", "Pink", "Purple", "White"],
        3: ["Blank", "Red", "Orange", "Yellow", "Green", "Blue", "Pink", "Purple", "White"]
    };

    // --- Create in-page console ---
    const parent = document.querySelector('h1');
    const consoleContainer = document.createElement('div');
    consoleContainer.style.border = '1px solid #ccc';
    consoleContainer.style.padding = '10px';
    consoleContainer.style.marginTop = '10px';
    consoleContainer.style.background = '#393939';
    consoleContainer.style.fontFamily = 'monospace';
    consoleContainer.style.fontSize = '25px';
    consoleContainer.style.whiteSpace = 'pre-line';
    parent.insertAdjacentElement('afterend', consoleContainer);

    const output = document.createElement('div');
    output.style.marginBottom = '5px';
    consoleContainer.appendChild(output);

    const input = document.createElement('input');
    input.type = 'text';
    input.style.width = '100%';
    input.placeholder = 'Enter your response here, then press Enter';
    consoleContainer.appendChild(input);
    input.focus();

    function showMessage(msg) {
        // Clear previous messages each time
        output.innerHTML = `<h2>Solver</h2>${msg}`;
        output.scrollTop = output.scrollHeight;
    }

    function getUserInput(callback) {
        input.value = '';
        input.focus();
        input.onkeydown = function(e) {
            if (e.key === 'Enter') {
                const val = input.value.trim();
                callback(val);
            }
        };
    }

    // --- Solver logic ---
    function showGuess(code) {
        // Bold the guess
        return '<b>' + code.split("").map(d => colors[Number(d)]).join("  ") + '</b>';
    }

    function score(a, b) {
        let greens = 0;
        let aCount = {}, bCount = {};
        for (let i = 0; i < a.length; i++) {
            if (a[i] === b[i]) greens++;
            else {
                aCount[a[i]] = (aCount[a[i]] || 0) + 1;
                bCount[b[i]] = (bCount[b[i]] || 0) + 1;
            }
        }
        let yellows = 0;
        for (let d in aCount) yellows += Math.min(aCount[d] || 0, bCount[d] || 0);
        return [greens, yellows];
    }

    function bestGuessTimed() {
        if (!candidates.length) return null;
        let start = performance.now();
        let best = candidates[0];
        let bestScore = Infinity;

        for (let g of candidates) {
            if (performance.now() - start > TIME_LIMIT) break;
            let buckets = {};
            for (let x of candidates) {
                let key = score(g, x).join(',');
                buckets[key] = (buckets[key] || 0) + 1;
            }
            let exp = 0;
            for (let k in buckets) exp += buckets[k] * buckets[k];
            if (exp < bestScore) {
                bestScore = exp;
                best = g;
            }
        }
        return best;
    }

    function promptStep() {
        if (!candidates.length) {
            showMessage("No solutions remain — feedback inconsistency?");
            return startGame();
        }

        if (!currentGuess) currentGuess = candidates[0];

        showMessage(`Try guess:\n${showGuess(currentGuess)}\nEnter: "greens yellows" or "done" to reset.\n(e.g., "1 0" for 1 green, 0 yellow)`);

        getUserInput(resp => {
            if (resp.toLowerCase() === 'done') return startGame();

            let parts = resp.split(" ").map(Number);
            if (parts.length !== 2 || parts.some(isNaN)) {
                showMessage("Invalid input. Use format: greens yellows");
                return promptStep();
            }
            let [g, y] = parts;
            if (g === length) {
                showMessage("Solved!");
                return startGame();
            }

            candidates = candidates.filter(c => {
                let [gg, yy] = score(currentGuess, c);
                return gg === g && yy === y;
            });

            if (!candidates.length) {
                showMessage("No code fits that feedback. Make sure you enter codes correctly. Press enter to restart.");
                getUserInput(_ => startGame());
                return;
            }

            if (candidates.length === 1) {
                showMessage(`Next guess:\n${showGuess(candidates[0])}\nSolved!\nPress Enter to start a new game.`);
                getUserInput(_ => startGame());
                return;
            }


            currentGuess = bestGuessTimed();
            setTimeout(promptStep, 10);
        });
    }

    function startGame() {
        showMessage("\n--- New Game ---\nWhich round are you playing? (1, 2, or 3)");

        getUserInput(roundInput => {
            let round = Number(roundInput);
            if (!COLOR_SETS[round]) {
                showMessage("Invalid round. Enter 1, 2, or 3.");
                return startGame();
            }

            colors = COLOR_SETS[round];
            if (round === 1) { digits = 5; length = 4; }
            else if (round === 2) { digits = 7; length = 5; }
            else { digits = 9; length = 6; }

            const arr = [...Array(digits).keys()].map(String);
            candidates = [];

            function build(prefix) {
                if (prefix.length === length) return candidates.push(prefix);
                for (let d of arr) build(prefix + d);
            }
            build("");

            currentGuess = null;
            setTimeout(promptStep, 10);
        });
    }

    startGame();
})();