☰

Rock Maths Auto Solver PRO

Automatic multiplication solver with digit verification

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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);

})();