preciousmouse / Binairo solution

// ==UserScript==
// @namespace       https://openuserjs.org/users/preciousmouse
// @name            Binairo solution
// @description     auto solve Binairo problem
// @author          preciousmouse
// @copyright       2020, preciousmouse (https://openuserjs.org/users/preciousmouse)
// @license         MIT
// @version         1.2
// @updateURL       https://openuserjs.org/meta/preciousmouse/Binairo_solution.meta.js
// @downloadURL     https://openuserjs.org/install/preciousmouse/Binairo_solution.user.js
// @supportURL      https://github.com/puzzle-resolution/Binairo
// @require         https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.18.2/babel.js
// @require         https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.16.0/polyfill.js
// @require         http://requirejs.org/docs/release/2.1.5/comments/require.js
// @include         https://cn.puzzle-binairo.com/
// @include         https://cn.puzzle-binairo.com/?*
// @grant none
// ==/UserScript==

// ==OpenUserJS==
// @author preciousmouse
// ==/OpenUserJS==


"use strict";

function submitAnswer() {
    var t;
    null === (t = document.querySelector("#btnReady")) || void 0 === t || t.click();
}

try {
    window.useRobot = !0;
} catch (t) {}

class Binairo {
    constructor(t) {
        this.getBlockType = t => ({
            [-1]: "Space",
            0: "White",
            1: "Black"
        }[t]), this.cloneState = function(t) {
            return t.map((t => t.map((t => JSON.parse(JSON.stringify(t))))));
        }, this.checkBoundary = t => !(t.x < 0 || t.x >= this.graph.length || t.y < 0 || t.y >= this.graph[0].length), 
        this.clonePosition = t => Object.assign({}, t), this.leftPosition = t => ({
            x: t.x,
            y: t.y - 1
        }), this.rightPosition = t => ({
            x: t.x,
            y: t.y + 1
        }), this.topPosition = t => ({
            x: t.x - 1,
            y: t.y
        }), this.bottomPosition = t => ({
            x: t.x + 1,
            y: t.y
        }), this.initState = t => ({
            blockState: t.map((t => t.map((t => this.getBlockType(t)))))
        }), this.available = (t, e, i, s = !1) => {
            const {blockState: o} = t, {x: a, y: n} = e;
            if ("Space" !== o[a][n] && o[a][n] !== i) return !1;
            if (!s) {
                if (o[a].length / 2 - o[a].filter((t => t === i)).length <= 0) return !1;
                if (o.length / 2 - o.slice().map((t => t[n])).filter((t => t === i)).length <= 0) return !1;
            }
            const same = (t, ...e) => e.every((e => e === t));
            return (!this.checkBoundary({
                x: a + 2,
                y: n
            }) || !same(i, o[a + 1][n], o[a + 2][n])) && (!(this.checkBoundary({
                x: a - 1,
                y: n
            }) && this.checkBoundary({
                x: a + 1,
                y: n
            }) && same(i, o[a - 1][n], o[a + 1][n])) && ((!this.checkBoundary({
                x: a - 2,
                y: n
            }) || !same(i, o[a - 2][n], o[a - 1][n])) && ((!this.checkBoundary({
                x: a,
                y: n + 2
            }) || !same(i, o[a][n + 1], o[a][n + 2])) && (this.checkBoundary({
                x: a,
                y: n - 1
            }), (!this.checkBoundary({
                x: a,
                y: n + 1
            }) || !same(i, o[a][n - 1], o[a][n + 1])) && (!this.checkBoundary({
                x: a,
                y: n - 2
            }) || !same(i, o[a][n - 2], o[a][n - 1]))))));
        }, this.updateAllPoints = t => {
            let e = new Set;
            for (let i = 0; i < this.graph.length; i++) for (let s = 0; s < this.graph[0].length; s++) this.updatePoint(t, e, {
                x: i,
                y: s
            });
            return this.clearUpdateQueue(t, e);
        }, this.addPointToQueue = (t, e, i) => this.appendToUpdateQueue(t, e, this.availbleSpace(i).filter((t => t)).map((e => e ? "Space" === t.blockState[e.x][e.y] ? e : this.restSpace(t, e).filter((t => t)) : e)).flat()), 
        this.updatePoint = (t, e, i) => {
            const {blockState: s} = t, {x: o, y: a} = i;
            return "Space" !== s[o][a] || (this.available(t, i, "Black") ? this.available(t, i, "White") || (s[o][a] = "Black", 
            this.addPointToQueue(t, e, i)) : (s[o][a] = "White", this.addPointToQueue(t, e, i))), 
            !0;
        }, this.solvelc = t => {
            const action = e => {
                const i = {
                    Black: "White",
                    White: "Black",
                    Space: "Space"
                }[e], s = t.length / 2 - t.filter((t => t === e)).length;
                if (0 === s) return t.slice().map((t => "Space" === t ? i : t));
                if (s < 0) throw "pRest<0";
                const inBoundary = e => 0 <= e && e < t.length, isSpace = e => "Space" === t[e];
                let o = [], a = [];
                for (let [e, s] of t.entries()) s === i ? (inBoundary(e - 2) && isSpace(e - 2) && isSpace(e - 1) && o.push({
                    s: e - 2,
                    e: e - 1
                }), inBoundary(e - 1) && inBoundary(e + 1) && isSpace(e - 1) && isSpace(e + 1) && o.push({
                    s: e - 1,
                    e: e + 1
                }), inBoundary(e + 2) && isSpace(e + 1) && isSpace(e + 2) && o.push({
                    s: e + 1,
                    e: e + 2
                })) : "Space" === s && inBoundary(e - 1) && inBoundary(e + 1) && isSpace(e - 1) && isSpace(e + 1) && a.push({
                    s: e - 1,
                    e: e + 1
                });
                o.sort(((t, e) => t.s - e.s)), a.sort(((t, e) => t.s - e.s));
                let n = [], r = -1;
                for (let {s: t, e: e} of o.values()) t > r && (n.push({
                    s: t,
                    e: e
                }), r = e);
                let l = new Set(n.map((({s: t, e: e}) => [ ...Array(e - t + 1).keys() ].map((e => e + t)))).flat());
                for (let {s: t, e: e} of a.values()) l.has(t) || l.has(e) || (n.push({
                    s: t,
                    e: e
                }), [ ...Array(e - t + 1).keys() ].map((e => e + t)).map((t => l.add(t))));
                return n.length === s && t.slice().map(((t, e) => "Space" !== t || l.has(e) ? t : i));
            };
            return action("Black") || action("White") || t.slice();
        }, this.updateLine = (t, e, i) => {
            const s = t.blockState[i].slice(), o = this.solvelc(s);
            for (let [s, a] of t.blockState[i].entries()) if ("Space" === a && "Space" !== o[s]) t.blockState[i][s] = o[s], 
            this.addPointToQueue(t, e, {
                x: i,
                y: s
            }); else if (a !== o[s]) return o[s], !1;
            return !0;
        }, this.updateColumn = (t, e, i) => {
            const s = t.blockState.slice().map((t => t[i])), o = this.solvelc(s);
            for (let [s, a] of t.blockState.entries()) {
                const n = a[i];
                if ("Space" === n && "Space" !== o[s]) t.blockState[s][i] = o[s], this.addPointToQueue(t, e, {
                    x: s,
                    y: i
                }); else if (n !== o[s]) return o[s], !1;
            }
            return !0;
        }, this.updateLineRule3 = (t, e, i) => {
            const {blockState: s} = t, o = s[i].slice(), a = o.length / 2 - o.filter((t => "Black" === t)).length, n = o.length / 2 - o.filter((t => "White" === t)).length;
            if (1 === a && 1 === n) {
                const stringify = t => t.slice().map((t => ({
                    Black: 1,
                    White: 0,
                    Space: "n"
                }[t]))).join(""), a = s.filter((t => t.every((t => "Space" !== t)))), [n, r] = o.slice().map(((t, e) => "Space" === t ? e : void 0)).filter((t => void 0 !== t));
                let l = o.slice(), c = o.slice();
                l[n] = "Black", l[r] = "White", c[n] = "White", c[r] = "Black";
                const h = stringify(l), u = stringify(c);
                for (let o of a) {
                    const a = stringify(o);
                    if (a === h) return s[i][n] = "White", s[i][r] = "Black", this.addPointToQueue(t, e, {
                        x: i,
                        y: n
                    }), this.addPointToQueue(t, e, {
                        x: i,
                        y: r
                    }), !0;
                    if (a === u) return s[i][n] = "Black", s[i][r] = "White", this.addPointToQueue(t, e, {
                        x: i,
                        y: n
                    }), this.addPointToQueue(t, e, {
                        x: i,
                        y: r
                    }), !0;
                }
            }
            return !1;
        }, this.updateColumnRule3 = (t, e, i) => {
            const {blockState: s} = t, o = s.slice().map((t => t[i])), a = s.length / 2 - o.filter((t => "Black" === t)).length, n = s.length / 2 - o.filter((t => "White" === t)).length;
            if (1 === a && 1 === n) {
                const stringify = t => t.slice().map((t => ({
                    Black: 1,
                    White: 0,
                    Space: "n"
                }[t]))).join(""), a = s.filter((t => t.every((t => "Space" !== t)))), [n, r] = o.slice().map(((t, e) => "Space" === t ? e : void 0)).filter((t => void 0 !== t));
                let l = o.slice(), c = o.slice();
                l[n] = "Black", l[r] = "White", c[n] = "White", c[r] = "Black";
                const h = stringify(l), u = stringify(c);
                for (let o of a) {
                    const a = stringify(o);
                    if (a === h) return s[n][i] = "White", s[r][i] = "Black", this.addPointToQueue(t, e, {
                        x: n,
                        y: i
                    }), this.addPointToQueue(t, e, {
                        x: r,
                        y: i
                    }), !0;
                    if (a === u) return s[n][i] = "Black", s[r][i] = "White", this.addPointToQueue(t, e, {
                        x: n,
                        y: i
                    }), this.addPointToQueue(t, e, {
                        x: r,
                        y: i
                    }), !0;
                }
            }
            return !1;
        }, this.updateLines = t => {
            let e = new Set;
            for (let i = 0; i < this.graph.length; i++) if (!this.updateLineRule3(t, e, i) && !this.updateLine(t, e, i)) return !1;
            for (let i = 0; i < this.graph[0].length; i++) if (!this.updateColumnRule3(t, e, i) && !this.updateColumn(t, e, i)) return !1;
            return this.clearUpdateQueue(t, e);
        }, this.loop = t => {
            let e = 0;
            for (;;) {
                if (++e, this.verificate(t)) return t;
                if (!this.updateLines(t)) break;
                if (e > 100) break;
            }
            return !1;
        }, this.solve = () => {
            let t = this.initState(this.graph);
            return this.updateAllPoints(t), this.verificate(t) || (t = this.loop({
                blockState: this.cloneState(t.blockState)
            })), this.answer = t ? this.generaterAnawer(t.blockState) : "failed";
        }, this.generaterAnawer = t => t.map((t => t.map((t => ({
            Black: 1,
            White: 0,
            Space: "n"
        }[t]))).join(""))).join(""), this.graph = t;
    }
    aroundTRBL(t, e = (() => !0)) {
        let i = [ void 0, void 0, void 0, void 0 ];
        const s = this.topPosition(t);
        e(this.clonePosition(s)) && (i[0] = s);
        const o = this.rightPosition(t);
        e(this.clonePosition(o)) && (i[1] = o);
        const a = this.bottomPosition(t);
        e(this.clonePosition(a)) && (i[2] = a);
        const n = this.leftPosition(t);
        return e(this.clonePosition(n)) && (i[3] = n), i;
    }
    availbleSpace(t) {
        if (!this.checkBoundary(t)) return [ void 0, void 0, void 0, void 0 ];
        return this.aroundTRBL(t, (t => this.checkBoundary(t)));
    }
    restSpace(t, e) {
        if (!this.checkBoundary(e)) return [ void 0, void 0, void 0, void 0 ];
        return this.aroundTRBL(e, (e => this.checkBoundary(e) && "Space" === t.blockState[e.x][e.y]));
    }
    appendToUpdateQueue(t, e, i) {
        for (let {x: s, y: o} of i) {
            const i = `${s},${o}`;
            this.checkBoundary({
                x: s,
                y: o
            }) && (e.has(i) || "Space" === t.blockState[s][o] && e.add(i));
        }
        return !0;
    }
    clearUpdateQueue(t, e) {
        try {
            for (;e.size > 0; ) {
                const i = [ ...e.keys() ][0];
                e.delete(i);
                const [s, o] = i.split(",").map((t => +t));
                if (!this.updatePoint(t, e, this.clonePosition({
                    x: s,
                    y: o
                }))) return !1;
            }
        } catch (t) {
            return !1;
        }
        return !0;
    }
    verificate(t) {
        const {blockState: e} = t;
        for (let i = 0; i < e.length; i++) for (let s = 0; s < e[0].length; s++) {
            if ("Space" === e[i][s]) return !1;
            if (!this.available(t, {
                x: i,
                y: s
            }, e[i][s], !0)) return !1;
        }
        return !0;
    }
}

(() => {
    const t = `\n        ${Binairo.toString()} \n        onmessage=${(t => {
        const {tasks: e, puzzleSize: i} = JSON.parse(t.data), s = new Binairo(e).solve();
        postMessage(s);
    }).toString()}\n    `, e = task, i = Game.task;
    console.info("task", e, i);
    const s = new Date;
    !function registerWorkerWithBlob(t) {
        const {scriptStr: e, postMessageStr: i, onMessage: s} = t, o = new Blob([ e ], {
            type: "text/javascript"
        }), a = URL.createObjectURL(o), n = new Worker(a);
        n.onmessage = s, n.postMessage(i);
    }({
        scriptStr: t,
        postMessageStr: JSON.stringify({
            tasks: i
        }),
        onMessage: t => {
            const e = new Date, i = t.data;
            console.info("answer", i), console.info("耗时", e.valueOf() - s.valueOf(), "ms"), 
            function replaceAnswer(t) {
                document && $ && ($("#puzzleForm").attr("onsubmit", `console.log('customer onsubmit');\n        Game.saveState();\n        Game.tickTimer();\n        this.jstimerPersonal.value = Game.getTimer();\n        this.ansH.value = '${t}';`), 
                $("#btnReady").off("click"), window.useRobot && $("#robot").attr("value", "1"));
            }(i), window.submit = submitAnswer, window.useRobot && submitAnswer();
        }
    });
})()