☰

Rock Maths Auto Solver PRO

Automatic multiplication solver with digit verification

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Rock Maths Auto Solver PRO
// @namespace    rockmaths
// @version      5.1
// @description  Automatic multiplication solver with digit verification
// @match        *://*/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(() => {
    'use strict';

    if (window.rockMathsPro) return;
    window.rockMathsPro = true;

    let enabled = true;
    let busy = false;
    let lastQuestion = '';
    let observerTimer = null;

    const sleep = ms =>
        new Promise(resolve => setTimeout(resolve, ms));

    function visible(el) {
        if (!el) return false;
        const r = el.getBoundingClientRect();
        const s = getComputedStyle(el);

        return r.width > 0 &&
            r.height > 0 &&
            s.display !== 'none' &&
            s.visibility !== 'hidden';
    }

    function readQuestion() {
        const text = document.body.innerText || '';

        const match = text.match(
            /(\d{1,3})\s*[×x*]\s*(\d{1,3})\s*=/
        );

        if (!match) return null;

        const progress = text.match(/\b(\d+)\s*\/\s*(\d+)\b/);

        const a = Number(match[1]);
        const b = Number(match[2]);

        return {
            answer: String(a * b),
            id: `${a}x${b}:${progress?.[0] || ''}`
        };
    }

    function findKeys() {
        const controls = [
            ...document.querySelectorAll(
                'button,[role="button"],[onclick]'
            )
        ].filter(visible);

        const groups = {};

        for (let n = 0; n <= 9; n++) {
            groups[n] = controls.filter(el =>
                el.textContent.trim() === String(n)
            );

            if (!groups[n].length) return null;
        }

        let root = groups[0][0];

        while (
            root &&
            !Object.values(groups).every(group =>
                group.some(el => root.contains(el))
            )
        ) {
            root = root.parentElement;
        }

        if (!root) return null;

        const keys = {};

        for (let n = 0; n <= 9; n++) {
            keys[n] = groups[n]
                .filter(el => root.contains(el))
                .sort((a, b) => {
                    const x = a.getBoundingClientRect();
                    const y = b.getBoundingClientRect();

                    return x.width * x.height -
                        y.width * y.height;
                })[0];

            if (!keys[n]) return null;
        }

        return { root, keys };
    }

    function findSpecialButtons(root, keys) {
        const digitKeys = Object.values(keys);

        const controls = [
            ...root.querySelectorAll(
                'button,[role="button"],[onclick]'
            )
        ].filter(el =>
            visible(el) &&
            !digitKeys.includes(el) &&
            !digitKeys.some(k => el.contains(k))
        );

        if (controls.length < 2) return null;

        const bottom = controls.filter(el => {
            const r = el.getBoundingClientRect();
            const rr = root.getBoundingClientRect();

            return r.bottom > rr.bottom - 110;
        });

        if (bottom.length < 2) return null;

        bottom.sort((a, b) =>
            a.getBoundingClientRect().left -
            b.getBoundingClientRect().left
        );

        return {
            clear: bottom[0],
            submit: bottom[bottom.length - 1]
        };
    }

    function findAnswerDisplay(root) {
        const rr = root.getBoundingClientRect();

        const candidates = [
            ...document.querySelectorAll('div,span,p')
        ].filter(el => {
            if (!visible(el) || el.children.length > 1) {
                return false;
            }

            const r = el.getBoundingClientRect();

            return r.bottom <= rr.top + 5 &&
                r.right > rr.left + rr.width * 0.55;
        });

        return candidates.sort((a, b) => {
            const x = a.getBoundingClientRect();
            const y = b.getBoundingClientRect();

            return x.width * x.height -
                y.width * y.height;
        })[0] || null;
    }

    function click(el) {
        if (!visible(el)) return false;
        el.click();
        return true;
    }

    async function waitForDisplay(display, expected) {
        const start = Date.now();

        while (Date.now() - start < 1500) {
            if (!enabled) return false;

            if (display.textContent.trim() === expected) {
                return true;
            }

            await sleep(30);
        }

        return false;
    }

    async function solve() {
        if (!enabled || busy) return;

        const q = readQuestion();

        if (!q || q.id === lastQuestion) return;

        const keypad = findKeys();

        if (!keypad) return;

        const special = findSpecialButtons(
            keypad.root,
            keypad.keys
        );

        if (!special) return;

        const display = findAnswerDisplay(keypad.root);

        if (!display) return;

        busy = true;

        try {
            // Clear the current answer.
            if (!click(special.clear)) return;

            await sleep(150);

            let current = readQuestion();

            if (!current || current.id !== q.id) return;

            // Enter and verify every digit.
            let typed = '';

            for (const digit of q.answer) {
                if (!enabled) return;

                current = readQuestion();

                if (!current || current.id !== q.id) return;

                if (!click(keypad.keys[digit])) return;

                typed += digit;

                const verified = await waitForDisplay(
                    display,
                    typed
                );

                if (!verified) {
                    console.warn(
                        '[Rock Maths] Digit verification failed:',
                        typed
                    );
                    return;
                }
            }

            // Verify the complete answer.
            if (display.textContent.trim() !== q.answer) {
                return;
            }

            current = readQuestion();

            if (!current || current.id !== q.id) return;

            await sleep(120);

            if (!enabled) return;

            if (display.textContent.trim() !== q.answer) {
                return;
            }

            // Submit only when verified.
            if (click(special.submit)) {
                lastQuestion = q.id;
            }

            await sleep(400);

        } catch (error) {
            console.error('[Rock Maths]', error);
        } finally {
            busy = false;
        }
    }

    // Create status button.
    const badge = document.createElement('button');

    Object.assign(badge.style, {
        position: 'fixed',
        right: '12px',
        bottom: '12px',
        zIndex: '2147483647',
        padding: '11px 15px',
        border: 'none',
        borderRadius: '9px',
        color: 'white',
        fontWeight: 'bold',
        cursor: 'pointer'
    });

    function updateBadge() {
        badge.textContent = enabled
            ? 'SMART SOLVER: ON'
            : 'SMART SOLVER: OFF';

        badge.style.background = enabled
            ? '#145b49'
            : '#a52d35';
    }

    badge.onclick = () => {
        enabled = !enabled;
        updateBadge();

        if (enabled) solve();
    };

    document.body.appendChild(badge);
    updateBadge();

    // Alt + M toggles the solver.
    document.addEventListener('keydown', event => {
        if (
            event.altKey &&
            event.key.toLowerCase() === 'm'
        ) {
            enabled = !enabled;
            updateBadge();

            if (enabled) solve();
        }
    });

    // Detect new questions automatically.
    const observer = new MutationObserver(() => {
        clearTimeout(observerTimer);
        observerTimer = setTimeout(solve, 70);
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
        characterData: true
    });

    setTimeout(solve, 500);

})();