denis.papec / TheGuardian Crossword checker

// ==UserScript==
// @name         TheGuardian Crossword checker
// @version      3.4
// @description  Check solution! - Check This -> Enter, Empty cell -> shift, Numbers -> Column/Row number, Space -> Change direction
// @author       Denis Papec
// @author       Jason Woods
// @match        https://www.theguardian.com/crosswords/quick/*
// @grant        none
// @license      MIT
// @require      http://code.jquery.com/jquery-3.1.1.min.js
// @require      https://raw.githubusercontent.com/csudcy/jquery.fireworks/master/jquery.fireworks.js
// @require      https://raw.githubusercontent.com/sindresorhus/screenfull.js/gh-pages/src/screenfull.js
// ==/UserScript==

// ==OpenUserJS==
// @author denis.papec
// @author jason.woods
// ==/OpenUserJS==

(function(run) {
    var interval = setInterval(function() {
        var lastControl = $('div.crossword__controls__grid button').last();
        if (lastControl.text() == 'Clear all') {
            clearInterval(interval);
            run();
        }
    }, 1000);
})(function() {
    'use strict';

    var lastControl = $('div.crossword__controls__grid button').last(),
        fullscreenControl = lastControl.clone().prop('id', 'fullscreen').removeClass('button--secondary'),
        randomControl = lastControl.clone().prop('id', 'random').removeClass('button--secondary'),
        overlay = $('<div id="overlay"> </div>'),
        crosswordDiv = $('div.js-crossword'),
        crosswordControls = $('.crossword__controls'),
        checkThisSelector = "button:contains('Check this')",
        checkAllSelector = "button:contains('Check all')",
        highlightedCellSelector = '.crossword__cell--highlighted',
        focusedCellSelector = '.crossword__cell-text--focussed',
        confirmCheckAllSelector = "button:contains('Confirm check all')",
        clueNumbers = $("div.crossword__clue__number"),
        crosswordCellText = $('.crossword__grid text.crossword__cell-text'),
        clickEvent = new Event('click'),
        bruteForceMessage = $('<div class="crossword__clues"><div class="crossword__clue"></div></div>'),
        lastAttemptMessage = $('<div class="crossword__clues"><div class="crossword__clue"></div></div>'),
        harlemSound = false,
        easterEggs = [
            doTheFireworks,
            //doTheHarlemShake,
        ];

    var getRandomCrossword = function() {
        let crossNum = [];

        for (var storageKey in window.localStorage){
            if (storageKey.includes('crosswords.crosswords/quick/')) {
                crossNum.push(storageKey.match(/\d+/g)[0]);
            }
        }

        let maxCrossword = Math.max(...crossNum.map(Number)),
            minCrossword = 9093,
            newCrossword = Math.floor(Math.random() * (maxCrossword - minCrossword)) + minCrossword;

        if (window.localStorage.getItem('crosswords.crosswords/quick/'+newCrossword) !== null) {
            newCrossword = getRandomCrossword();
        }

        return newCrossword;
    }

    lastControl.after(randomControl.text('Random Crossword'));
    lastControl.after(fullscreenControl.text('Fullscreen'));

    randomControl.on('click', () => {
        window.location.href = 'https://www.theguardian.com/crosswords/quick/'+getRandomCrossword();
    });

    fullscreenControl.on('click', () => {
        if (screenfull.enabled) {
            screenfull.request(crosswordDiv[0]);
        }
    });

    crosswordDiv.css({'margin': 'auto', 'width': '100%', 'height': '100%', 'background-color': '#fff', 'padding': '20px'});

    var numberMap = [],
        direction = [],
        letterMap = [],
        lastFocus = null,
        bruteForceMap = {},
        cheatCodes = {
            cruciverbalist: 'getNumberStartedQuickCrosswords()'
        };

    var clueFilter = function(clue) {
        return clueNumbers.filter(function() {
            return $(this).text() === clue;
        });
    };

    var getDirection = function(clue) {
        if (direction.includes(clue)) {
            direction = direction.filter(function(items) { return items !== clue });
            return 0;
        }

        direction.push(clue);
        return 1;
    };

    var clickButton = function(button) {
        crosswordControls.find(button).click();
    };

    var getNumberStartedQuickCrosswords = function() {
        var i = 0;
        for (var storageKey in window.localStorage){
            if (storageKey.includes('crosswords.crosswords/quick/')) {
                i++;
            }
        }
        alert('Total number of resolved crosswords: '+i);
    }

    var cheatCodeDetector = function(newLetter) {
        var foundTrail = false;
        letterMap.push(newLetter);

        for (var cheat in cheatCodes) {
            if (!cheat.includes(letterMap.join(''))) {
                continue;
            }

            foundTrail = true;

            if (cheat !== letterMap.join('')) {
                continue;
            }

            eval(cheatCodes[cheat]);
        }

        if (!foundTrail) {
            letterMap = [];
            letterMap.push(newLetter);
        }
    };

    var checkBruteForce = function(activeCell, didType) {
        var cellX = activeCell.attr('x'),
            cellY = activeCell.attr('y'),
            letter = activeCell.text();
        if (!didType && !letter) {
            // clicked empty cell
            bruteForceMessage.remove();
            return;
        }
        if (!bruteForceMap[cellX]) {
            bruteForceMap[cellX] = {};
        }
        if (!bruteForceMap[cellX][cellY]) {
            bruteForceMap[cellX][cellY] = [];
        }
        if (didType && bruteForceMap[cellX][cellY].indexOf(letter) === -1) {
            bruteForceMap[cellX][cellY].push(letter);
        }
        if (bruteForceMap[cellX][cellY].length > 5) {
            var result = '';
            'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').forEach(function(entry) {
                if (result) {
                    result += ' / ';
                }
                if (bruteForceMap[cellX][cellY].indexOf(entry) === -1) {
                    result += '<span style="opacity: 0.5">' + entry + '</span>';
                } else {
                    result += entry;
                }
            });
            bruteForceMessage.find('.crossword__clue').html('<b>Brute forced so far:</b> ' + result);
            if (bruteForceMessage.parent().length == 0) {
                $('.crossword__clues--wrapper').append(bruteForceMessage);
            }
        } else {
            bruteForceMessage.remove();
        }
    };

    var getCurrentWord = function() {
        var cells = $('.crossword__cell--highlighted ~ .crossword__cell-text'),
            result = '';
        cells.each(function(index, cell) {
            var letter = $(cell).text();
            if (result) {
                result += ' ';
            }
            if (letter) {
                result += letter;
            } else {
                result += '_';
            }
        });
        return result;
    };

    crosswordDiv.click(function(event) {
        lastAttemptMessage.remove();
        var activeCell = $(focusedCellSelector);
        if (activeCell.length === 1) {
            checkBruteForce(activeCell, false);
        }
    });

    crosswordDiv.keyup(function(event) {
        if (event.keyCode === 13) { // enter
            var lastAttempt = getCurrentWord();
            clickButton(checkThisSelector);
            window.requestAnimationFrame(function () {
                var lastAttemptResult = getCurrentWord();
                if (lastAttemptResult != lastAttempt) {
                    lastAttemptMessage.find('.crossword__clue').html('<b>Last attempt:</b> ' + lastAttempt);
                    if (lastAttemptMessage.parent().length == 0) {
                        $('.crossword__clues--wrapper').append(lastAttemptMessage);
                    }
                    return;
                }
                lastAttemptMessage.remove();
                if (isCrosswordFull()) {
                    clickButton(checkAllSelector);
                    window.requestAnimationFrame(function () {
                        clickButton(confirmCheckAllSelector);
                        window.requestAnimationFrame(function () {
                            if (isCrosswordFull()) {
                                if (screenfull.enabled) {
                                    screenfull.exit();
                                }
                                window.requestAnimationFrame(function () {
                                    document.body.scrollIntoView();
                                    window.requestAnimationFrame(function () {
                                        //let egg = easterEggs[Math.floor(Math.random() * easterEggs.length)];
                                        //eval(egg);
                                        doTheFireworks();
                                    });
                                });
                            }
                        });
                    });
                }
            });
        }

        if (event.keyCode === 16) { // shift
            var cell = $(highlightedCellSelector).parent().children('text').filter(':empty').first();
            if (cell.length > 0) {
                cell.parent()[0].dispatchEvent(clickEvent);
            }
        }

        if (event.keyCode === 27) { // escape
            overlay.css('display', 'none');
        }

        if (event.keyCode === 32) { // space
            $(focusedCellSelector).parent()[0].dispatchEvent(clickEvent);
        }

        if (lastFocus.length > 0) {
            checkBruteForce(lastFocus, true);
        }

        if (/[a-zA-Z]/i.test(event.key)) {
            cheatCodeDetector(event.key);
        }
    });

    crosswordDiv.keydown(function(event) {
        if (/[0-9]/i.test(event.key)) {
            event.preventDefault();
            numberMap.push(event.key);
            let elements = clueFilter(numberMap.join(''));

            if (elements.length == 0) {
                numberMap = [event.key];
                elements = clueFilter(event.key);
            }

            elements.first().click();
        }

        lastFocus = $(focusedCellSelector);
    });

    overlay.keyup(closeOverlay);
    $(document).keyup(closeOverlay);

    var closeOverlay = function(event) {
        if (event.keyCode === 27) { // escape
            overlay.css('display', 'none');
        }
    };

    var isCrosswordFull = function (){
        return crosswordCellText.filter(':empty').length == 0;
    };

    var doTheFireworks = function () {
        overlay.css({
            'position': 'fixed', 'top': '0', 'left': '0', 'width': '100%',
            'height': '100%', 'background-color': '#000',
            'filter': 'alpha(opacity=80)', '-moz-opacity': '0.8',
            '-khtml-opacity': '0.8', 'opacity': '0.8', 'z-index': '10000'
        });
        overlay.appendTo(document.body)
        overlay.fireworks();
    };

    var doingTheHarlemShake = false;
    var endTheHarlemShake = function () {
        doingTheHarlemShake = false;
    };
    var doTheHarlemShake = function () {
        if (doingTheHarlemShake) {
            return;
        }
        doingTheHarlemShake = true;
        (function () {
            function c() { var e = document.createElement("link"); e.setAttribute("type", "text/css"); e.setAttribute("rel", "stylesheet"); e.setAttribute("href", f); e.setAttribute("class", l); document.body.appendChild(e) }
            function h() { var e = document.getElementsByClassName(l); for (var t = 0; t < e.length; t++) { document.body.removeChild(e[t]) } }
            function p() { var e = document.createElement("div"); e.setAttribute("class", a); document.body.appendChild(e); setTimeout(function () { document.body.removeChild(e) }, 100) }
            function d(e) { return { height: e.offsetHeight, width: e.offsetWidth } }
            function v(i) { var s = d(i); return s.height > e && s.height < n && s.width > t && s.width < r }
            function m(e) { var t = e; var n = 0; while ( !! t) { n += t.offsetTop; t = t.offsetParent } return n }
            function g() { var e = document.documentElement; if ( !! window.innerWidth) { return window.innerHeight } else if (e && !isNaN(e.clientHeight)) { return e.clientHeight } return 0 }
            function y() { if (window.pageYOffset) { return window.pageYOffset } return Math.max(document.documentElement.scrollTop, document.body.scrollTop) }
            function E(e) { var t = m(e); return t >= w && t <= b + w }
            function S() { if (harlemSound) { S_sound(); } else { S_nosound(); } }
            function S_nosound() { x(k); setTimeout(function () { N(); p(); for (var e = 0; e < O.length; e++) { T(O[e]) } }, 10000); setTimeout(function () { N(); h(); endTheHarlemShake(); }, 30000); }
            function S_sound() { var e = document.createElement("audio"); e.setAttribute("class", l); e.src = i; e.loop = false; e.addEventListener("canplay", function () { setTimeout(function () { x(k) }, 500); setTimeout(function () { N(); p(); for (var e = 0; e < O.length; e++) { T(O[e]) } }, 15500) }, true); e.addEventListener("ended", function () { N(); h(); endTheHarlemShake(); }, true); e.innerHTML = " <p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p> <p>"; document.body.appendChild(e); e.play() }
            function x(e) { e.className += " " + s + " " + o }
            function T(e) { e.className += " " + s + " " + u[Math.floor(Math.random() * u.length)] }
            function N() { var e = document.getElementsByClassName(s); var t = new RegExp("\\b" + s + "\\b"); for (var n = 0; n < e.length;) { e[n].className = e[n].className.replace(t, "") } } var e = 30; var t = 30; var n = 350; var r = 350; var i = "https://s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.mp3"; var s = "mw-harlem_shake_me"; var o = "im_first"; var u = ["im_drunk", "im_baked", "im_trippin", "im_blown"]; var a = "mw-strobe_light"; var f = "https://s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css"; var l = "mw_added_css"; var b = g(); var w = y(); var C = document.getElementsByTagName("*"); var k = null, kpool = document.getElementsByClassName('new-header__logo'); for (var L = 0; L < kpool.length; L++) { var A = kpool[L]; if (v(A)) { if (E(A)) { k = A; break } } } if (A === null) { console.warn("Could not find a node of the right size. Please try a different page."); return } c(); S(); var O = []; for (var L = 0; L < C.length; L++) { var A = C[L]; if (v(A)) { O.push(A) } }
        })();
    };
});