Puzz.link Assistance

Do trivial deduction.

当前为 2023-11-05 提交的版本,查看 最新版本

// ==UserScript==
// @name         Puzz.link Assistance
// @version      23.11.4.2
// @description  Do trivial deduction.
// @author       Leaving Leaves
// @match        https://puzz.link/p*/*
// @match        https://pzplus.tck.mn/p*/*
// @match        http://pzv.jp/p*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=github.com
// @grant        none
// @namespace    https://gf.qytechs.cn/users/1192854
// @license      GPL
// ==/UserScript==

(function () {
    'use strict';

    const MAXLOOP = 30;
    const MAXDFSCELLNUM = 200;
    let flg = true;
    let step = false;
    let board;

    //const list
    const CQNUM = {
        quesmark: -2,
        circle: -2, //no number
        block: -2,
        none: -1,
        wcir: 1,
        bcir: 2,
    }

    const CANUM = {
        none: -1,
        //Masyu
        wcir: 1,
        bcir: 2,
    };

    const CQANS = {
        none: 0,
        block: 1,
        //Light and Shadow
        black: 1,
        white: 2,
        //Starbattle
        star: 1,
        //Akari
        light: 1,
        //Shakashaka triangle
        bl: 2,
        br: 3,
        tr: 4,
        tl: 5,
        //Slant
        rslash: 31,
        lslash: 32,
    };

    const CQUES = {
        none: 0,
        //Castle Wall
        gray: 0,
        white: 1,
        black: 2,
        //Icebarn
        ice: 6,
        //Simpleloop
        bwall: 7,
        //Slalom
        vgate: 21,
        hgate: 22,
        //Nurimaze
        cir: 41,
        tri: 42,
    };

    const CQSUB = {
        none: 0,
        dot: 1,
        green: 1,
        //Slitherlink
        yellow: 2,
        //All or Nothing
        gray: 1,
    };

    const QDIR = {
        none: 0,
        //arrow
        up: 1,
        dn: 2,
        lt: 3,
        rt: 4,
    }

    const BQSUB = {
        none: 0,
        cross: 2,
        //Icebarn
        arrow_up: 11,
        arrow_dn: 12,
        arrow_lt: 13,
        arrow_rt: 14,
    };

    const genrelist = [
        [/slither(link)?(_play)?/, SlitherlinkAssist],
        [/yaji[lr]in/, YajilinAssist],
        [/simpleloop/, SimpleloopAssist],
        [/mas[yh]u/, MasyuAssist],
        [/(lightup|akari)/, AkariAssist],
        [/heyawake/, HeyawakeAssist],
        [/shakashaka/, ShakashakaAssist],
        [/ayeheya/, EkawayehAssist],
        [/nothree/, NothreeAssist],
        [/lits/, LitsAssist],
        [/icebarn/, IcebarnAssist],
        [/aquapelago/, AquapelagoAssist],
        [/nurimaze/, NurimazeAssist],
        [/yinyang/, YinyangAssist],
        [/guidearrow/, GuideArrowAssist],
        [/nurikabe/, NurikabeAssist],
        [/gokigen/, SlantAssist],
        [/cbanana/, ChocoBananaAssist],
        [/nurimisaki/, NurimisakiAssist],
        [/castle/, CastleWallAssist],
        [/starbattle/, StarbattleAssist],
        [/slalom/, SlalomAssist],
        [/lightshadow/, LightandShadowAssist],
        [/tapa/, TapaAssist],
        [/(cave|bag)/, CaveAssist],
        [/aqre/, AqreAssist],
        [/nothing/, AllorNothingAssist],
        [/kurodoko/, KurodokoAssist],
        [/hitori/, HitoriAssist],
    ];

    if (genrelist.filter(g => RegExp('\\\?' + g[0].source + '\\\/').test(document.URL)).length === 1) {
        let btn = '<button type="button" class="btn" id="assist" style="display: inline;">Assist</button>';
        let btn2 = '<button type="button" class="btn" id="assiststep" style="display: inline;">Assist Step</button>';
        document.querySelector('#btntrial').insertAdjacentHTML('afterend', btn);
        document.querySelector("#assist").insertAdjacentHTML('afterend', btn2);
        document.querySelector("#assist").addEventListener("click", assist, false);
        document.querySelector("#assiststep").addEventListener("click", assiststep, false);
        window.addEventListener("keypress", (event) => {
            if (event.key === 'q' || (event.key === 'Q')) { assist(); }
            if (event.key === 'w' || (event.key === 'W')) { assiststep(); }
        });
    }

    function assiststep() {
        step = true;
        assist();
        step = false;
    }

    function assist() {
        flg = true;
        board = ui.puzzle.board;
        for (let loop = 0; loop < (step ? 1 : MAXLOOP); loop++) {
            if (!flg) { break; }
            flg = false;
            genrelist.filter(g => RegExp('\\\?' + g[0].source + '\\\/').test(document.URL))[0][1]();
        }
        ui.puzzle.redraw();
        console.log('Assisted.');
    }

    let offset = function (c, dx, dy, dir = 0) {
        dir = (dir % 4 + 4) % 4;
        if (dir === 0) { return board.getobj(c.bx + dx * 2, c.by + dy * 2); }
        if (dir === 1) { return board.getobj(c.bx + dy * 2, c.by - dx * 2); }
        if (dir === 2) { return board.getobj(c.bx - dx * 2, c.by - dy * 2); }
        if (dir === 3) { return board.getobj(c.bx - dy * 2, c.by + dx * 2); }
    }
    let fourside = function (f, a) {
        f(a.top);
        f(a.bottom);
        f(a.left);
        f(a.right);
    };
    let fourside2 = function (f, a, b) {
        f(a.top, b.top);
        f(a.bottom, b.bottom);
        f(a.left, b.left);
        f(a.right, b.right);
    };
    let dir = function (c, d) {
        d = (d % 4 + 4) % 4;
        if (d === 0) return c.top;
        if (d === 1) return c.left;
        if (d === 2) return c.bottom;
        if (d === 3) return c.right;
    }
    let qdirremap = function (qdir) {
        return [-1, 0, 2, 1, 3][qdir];
    }

    //set val
    let add_cross = function (b) {
        if (b === undefined || b.isnull || b.line || b.qsub !== BQSUB.none) { return; }
        if (step && flg) { return; }
        b.setQsub(BQSUB.cross);
        b.draw();
        flg |= b.qsub === BQSUB.cross;
    };
    let add_line = function (b) {
        if (b === undefined || b.isnull || b.line || b.qsub === BQSUB.cross) { return; }
        if (step && flg) { return; }
        b.setLine(1);
        b.draw();
        flg |= b.line;
    };
    let add_arrow = function (b, dir) {
        if (b === undefined || b.isnull || b.qsub === BQSUB.cross) { return; }
        if (step && flg) { return; }
        flg = true;
        b.setQsub(dir);
        b.draw();
    };
    let add_block = function (c, notOnNum = false) {
        if (notOnNum && (c.qnum !== CQNUM.none || c.qnums.length > 0)) { return; }
        if (c === undefined || c.isnull || c.lcnt !== 0 || c.qsub === CQSUB.dot || c.qans !== CQANS.none) { return; }
        if (step && flg) { return; }
        flg = true;
        c.setQans(CQANS.block);
        c.draw();
    };
    let add_light = function (c) {
        add_block(c, true);
    };
    let add_dot = function (c) {
        if (c === undefined || c.isnull || c.qnum !== CQNUM.none || c.qnums.length > 0 || c.qans !== CQANS.none || c.qsub === CQSUB.dot) { return; }
        if (step && flg) { return; }
        flg = true;
        c.setQsub(CQSUB.dot);
        c.draw();
    };
    let add_green = function (c) {
        if (c === undefined || c.isnull || c.qans !== CQANS.none || c.qsub === CQSUB.dot) { return; }
        if (step && flg) { return; }
        flg = true;
        c.setQsub(CQSUB.green);
        c.draw();
    };

    //single rule
    function No2x2Cell(isBlock, setGreen) {
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let templist = [cell, offset(cell, 1, 0), offset(cell, 0, 1), offset(cell, 1, 1)];
            if (templist.some(c => c.isnull)) { continue; }
            templist = templist.filter(c => !isBlock(c));
            if (templist.length === 1) {
                setGreen(templist[0]);
            }
        }
    }

    function No2x2Block() {
        No2x2Cell(
            function (c) { return c.qans === CQANS.block; },
            add_green
        );
    }

    function No2x2Green() {
        No2x2Cell(
            function (c) { return c.qsub === CQSUB.green; },
            add_block
        );
    }

    function CellConnected(isBlock, isGreen, setBlock, setGreen,
        linkedList = function (c, list) { list.push(c); return; },
        isNotPassable = function (c, nb, nc) { return false; },
        OutsideAsBlock = false) {
        //use tarjan to find cut vertex
        let n = 0;
        let ord = new Map();
        let low = new Map();
        let blkn = new Map();
        let dfs = function (c, f = null) {
            if (!c.isnull && isGreen(c) || ord.has(c)) { return; }
            if (c.isnull && !OutsideAsBlock) { return; }
            ord.set(c, n);
            low.set(n, n);
            blkn.set(n, 0);
            n++;
            let cellList = [];
            if (!c.isnull) {
                linkedList(c, cellList);
                cellList.forEach(cl => {
                    ord.set(cl, ord.get(c));
                    blkn.set(ord.get(cl), blkn.get(ord.get(cl)) + isBlock(cl));
                });
            }
            let fn = function (nc, nb) {
                if (isNotPassable(c, nb, nc)) { return; }
                if (nc === f || isGreen(nc)) { return; }
                if (nc.isnull && !OutsideAsBlock) { return; }
                if (ord.get(c) === ord.get(nc)) { return; }
                if (ord.has(nc)) {
                    low.set(ord.get(c), Math.min(low.get(ord.get(c)), ord.get(nc)));
                    return;
                }
                dfs(nc, c);
                let ordc = ord.get(c);
                let ordnc = ord.get(nc);
                low.set(ordc, Math.min(low.get(ordc), low.get(ordnc)));
                if (ordnc > ordc) {
                    blkn.set(ordc, blkn.get(ordc) + blkn.get(ordnc));
                    if (ordc <= low.get(ordnc) && blkn.get(ordnc) > 0) {
                        cellList.forEach(c => setBlock(c));
                    }
                }
            };
            if (!c.isnull) {
                cellList.forEach(c => fourside2(fn, c.adjacent, c.adjborder));
            } else if (c.isnull) {
                for (let i = 0; i < board.cols; i++) {
                    dfs(board.getc(2 * i + 1, board.minby + 1), c);
                    dfs(board.getc(2 * i + 1, board.maxby - 1), c);
                }
                for (let i = 0; i < board.rows; i++) {
                    dfs(board.getc(board.minbx + 1, 2 * i + 1), c);
                    dfs(board.getc(board.maxbx - 1, 2 * i + 1), c);
                }
            }
        };
        if (OutsideAsBlock) {
            dfs(board.getc(0, 0));
        } else {
            for (let i = 0; i < board.cell.length; i++) {
                if (!isBlock(board.cell[i]) || ord.has(board.cell[i])) { continue; }
                dfs(board.cell[i]);
            }
        }
        if (ord.size > 0) {
            for (let i = 0; i < board.cell.length; i++) {
                if (ord.has(board.cell[i]) || isBlock(board.cell[i]) || isGreen(board.cell[i])) { continue; }
                setGreen(board.cell[i]);
            }
        }
    }

    function CellNoLoop(isBlock, isGreen, setGreen) {
        let ord = new Map();
        let n = 0;
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (!isBlock(cell) || ord.has(cell)) { continue; }
            let dfs = function (c) {
                if (c.isnull || !isBlock(c) || ord.has(c)) { return; }
                ord.set(c, n);
                fourside(dfs, c.adjacent);
            }
            dfs(cell);
            n++;
        }
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (isBlock(cell) || isGreen(cell)) { continue; }
            let templist = [offset(cell, -1, 0), offset(cell, 0, -1), offset(cell, 0, 1), offset(cell, 1, 0)];
            templist = templist.filter(c => !c.isnull && isBlock(c));
            templist = templist.map(c => ord.get(c));
            for (let i = 0; i < templist.length; i++) {
                for (let j = i + 1; j < templist.length; j++) {
                    if (templist[i] === templist[j]) {
                        setGreen(cell);
                    }
                }
            }
        }
    }

    function GreenConnectedInCell() {
        CellConnected(
            function (c) { return c.qsub === CQSUB.green; },
            function (c) { return c.qans === CQANS.block; },
            add_green,
            add_block,
        );
    }

    function BlockConnectedInCell() {
        CellConnected(
            function (c) { return c.qans === CQANS.block; },
            function (c) { return c.qsub === CQSUB.green; },
            add_block,
            add_green,
        );
    }

    function GreenNoLoopInCell() {
        CellNoLoop(
            function (c) { return c.qsub === CQSUB.green; },
            function (c) { return c.qans === CQANS.block; },
            add_block
        );
    }

    function BlockNotAdjacent() {
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qans !== CQANS.block) { continue; }
            fourside(add_green, cell.adjacent);
        }
    }

    function SingleLoopInCell(pathable = function (c) { return true; }, inPath = function (c) { return false; },
        setNotPath = function (c) { }) {
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (!pathable(cell)) {
                fourside(add_cross, cell.adjborder);
            }
            let emptynum = 0;
            let linenum = 0;
            let adjcell = cell.adjacent;
            let adjline = cell.adjborder;
            let fn = function (c, b) {
                if (!pathable(c)) { add_cross(b); }
                if (!c.isnull && pathable(c) && b.qsub !== BQSUB.cross) { emptynum++; }
                linenum += b.line;
            };
            fourside2(fn, adjcell, adjline);
            //no branch
            if (linenum === 2 && cell.ques !== CQUES.ice) {
                fourside(add_cross, adjline);
            }
            //no deadend
            if (emptynum <= 1) {
                fourside(add_cross, adjline);
                setNotPath(cell);
            }
            //2 degree path
            if (emptynum === 2 && (linenum === 1 || cell.qsub === CQSUB.dot || inPath(cell))) {
                fourside(add_line, adjline);
            }
        }
        //avoid forming multiple loop
        for (let i = 0; i < board.border.length; i++) {
            let border = board.border[i];
            if (border.qsub !== BQSUB.none || border.line) { continue; }
            let cr1 = border.sidecell[0];
            let cr2 = border.sidecell[1];
            if (cr1.ques === CQUES.ice || cr2.ques === CQUES.ice) { continue; }
            if (cr1.path !== null && cr1.path === cr2.path && board.linegraph.components.length > 1) {
                add_cross(border);
            }
        }
    }

    function NumberRegion(isBlock, isGreen, setBlock, setGreen, OneNumPerRegion = true, NoGreenNum = true) {
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            //don't block region exit
            let templist = [offset(cell, -1, -1), offset(cell, -1, 0), offset(cell, -1, 1), offset(cell, 0, -1),
            offset(cell, 0, 1), offset(cell, 1, -1), offset(cell, 1, 0), offset(cell, 1, 1)];
            if (!isBlock(cell) && !isGreen(cell) && templist.filter(c => isGreen(c) || c.isnull).length >= 2) {
                for (let d = 0; d < 4; d++) {
                    let ncell = dir(cell.adjacent, d);
                    if (isGreen(ncell)) { continue; }
                    let cellList = [];
                    let dfs = function (c) {
                        if (cellList.length > MAXDFSCELLNUM) { return; }
                        if (c.isnull || isGreen(c) || c === cell || cellList.indexOf(c) !== -1) { return; }
                        cellList.push(c);
                        fourside(dfs, c.adjacent);
                    }
                    dfs(ncell);
                    if (cellList.length > MAXDFSCELLNUM) { continue; }
                    let templist = cellList.filter(c => c.qnum !== CQNUM.none && (NoGreenNum || isBlock(c)));
                    //extend region without num
                    if (templist.length === 0 && cellList.some(c => isBlock(c)) && OneNumPerRegion) {
                        setBlock(cell);
                    }
                    //extend region with less cells
                    if (templist.length >= 1 && templist[0].qnum !== CQNUM.quesmark && templist[0].qnum > cellList.length) {
                        setBlock(cell);
                    }
                }
            }
            //finished region
            if (cell.qnum > 0) {
                let cellList = [];
                let dfs = function (c) {
                    if (cellList.length > cell.qnum) { return; }
                    if (c.isnull || !isBlock(c) || cellList.indexOf(c) !== -1) { return; }
                    cellList.push(c);
                    fourside(dfs, c.adjacent);
                }
                dfs(cell);
                if (cellList.length === cell.qnum) {
                    cellList.forEach(c => fourside(setGreen, c.adjacent));
                }
            }
            //finished surrounded region
            if (cell.qnum > 0) {
                let cellList = [];
                let dfs = function (c) {
                    if (cellList.length > cell.qnum) { return; }
                    if (c.isnull || isGreen(c) || cellList.indexOf(c) !== -1) { return; }
                    cellList.push(c);
                    fourside(dfs, c.adjacent);
                }
                dfs(cell);
                if (cell.qnum !== CQNUM.quesmark && cell.qnum === cellList.length) {
                    cellList.forEach(c => setBlock(c));
                }
            }
            //not connect two region
            for (let d1 = 0; d1 < 4; d1++) {
                for (let d2 = d1 + 1; d2 < 4; d2++) {
                    if (isBlock(cell) || isGreen(cell)) { continue; }
                    let cell1 = dir(cell.adjacent, d1);
                    let cell2 = dir(cell.adjacent, d2);
                    if (cell1.isnull || cell2.isnull || !isBlock(cell1) || !isBlock(cell2)) { continue; }
                    let cellList1 = [];
                    let cellList2 = [];
                    let dfs = function (c, list) {
                        if (c.isnull || !isBlock(c) || list.indexOf(c) !== -1) { return; }
                        list.push(c);
                        dfs(c.adjacent.top, list);
                        dfs(c.adjacent.bottom, list);
                        dfs(c.adjacent.left, list);
                        dfs(c.adjacent.right, list);
                    }
                    dfs(cell1, cellList1);
                    dfs(cell2, cellList2);
                    if (cellList1.indexOf(cell2) !== -1) { continue; }
                    let templist1 = cellList1.filter(c => c.qnum !== CQNUM.none);
                    let templist2 = cellList2.filter(c => c.qnum !== CQNUM.none);
                    if (templist1.length >= 1 && templist2.length >= 1) {
                        if (templist1[0].qnum !== CQNUM.quesmark && templist2[0].qnum !== CQNUM.quesmark && templist1[0].qnum !== templist2[0].qnum || OneNumPerRegion) {
                            setGreen(cell);
                        }
                    }
                    if (templist1.length + templist2.length >= 1) {
                        let qnum = (templist1.length >= 1 ? templist1[0] : templist2[0]).qnum;
                        if (qnum !== CQNUM.quesmark && cellList1.length + cellList2.length + 1 > qnum) {
                            setGreen(cell);
                        }
                    }
                    if (cell.qnum >= 0 && cellList1.length + cellList2.length + 1 > cell.qnum) {
                        setGreen(cell);
                    }
                }
            }
            //cell and region
            for (let d = 0; d < 4; d++) {
                if (isBlock(cell) || isGreen(cell) || cell.qnum === CQNUM.none) { continue; }
                let ncell = dir(cell.adjacent, d);
                if (ncell.isnull || !isBlock(ncell)) { continue; }
                let cellList = [];
                let dfs = function (c) {
                    if (c.isnull || !isBlock(c) || cellList.indexOf(c) !== -1) { return; }
                    cellList.push(c);
                    fourside(dfs, c.adjacent);
                }
                dfs(ncell, cellList);
                let templist = cellList.filter(c => c.qnum !== CQNUM.none);
                if (templist.length >= 1 && (templist[0].qnum !== CQNUM.quesmark && cell.qnum !== CQNUM.quesmark && templist[0].qnum !== cell.qnum || OneNumPerRegion)) {
                    setGreen(cell);
                }
                if (cell.qnum !== CQNUM.quesmark && cellList.length + 1 > cell.qnum) {
                    setGreen(cell);
                }
            }
        }
    }

    function SightNumber(isBlock, isGreen, setBlock, setGreen) {
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let qnum = cell.qnum;
            if (cell.qnum !== CQNUM.none) {
                setBlock(cell);
            }
            if (cell.qnum !== CQNUM.none && cell.qnum !== CQNUM.quesmark) {
                let seennum = (isBlock(cell) ? 1 : 0);
                let farthest = [0, 0, 0, 0];
                //count seen green cells
                for (let d = 0; d < 4; d++) {
                    let pcell = dir(cell.adjacent, d);
                    while (!pcell.isnull && isBlock(pcell)) {
                        farthest[d]++;
                        seennum++;
                        pcell = dir(pcell.adjacent, d);
                    }
                    while (!pcell.isnull && !isGreen(pcell)) {
                        farthest[d]++;
                        pcell = dir(pcell.adjacent, d);
                    }
                }
                //not extend too much
                for (let d = 0; d < 4; d++) {
                    let pcell = dir(cell.adjacent, d);
                    while (!pcell.isnull && isBlock(pcell)) {
                        pcell = dir(pcell.adjacent, d);
                    }
                    if (pcell.isnull || isGreen(pcell)) { continue; }
                    let tcell = pcell;
                    pcell = dir(pcell.adjacent, d);
                    let n = 0;
                    while (!pcell.isnull && isBlock(pcell)) {
                        n++;
                        pcell = dir(pcell.adjacent, d);
                    }
                    if (n + seennum + 1 > qnum) {
                        setGreen(tcell);
                    }
                }
                //must extend this way
                let maxn = farthest.reduce(function (a, b) { return a + b; }) + (isBlock(cell) ? 1 : 0);
                for (let d = 0; d < 4; d++) {
                    for (let j = 1; j <= qnum - maxn + farthest[d]; j++) {
                        add_green(offset(cell, 0, -j, d));
                    }
                }
            }
        }
    }

    //assist for certain genre
    function AllorNothingAssist() {
        let add_color = function (c, color) {
            if (c.isnull || c.qsub !== CQSUB.none) { return; }
            if (step && flg) { return; }
            flg = 1;
            c.setQsub(color);
            c.draw();
        };
        let add_gray = function (c) { add_color(c, CQSUB.gray); };
        let add_yellow = function (c) { add_color(c, CQSUB.yellow); };
        let add_border_cross = function (b) { if (b.ques) { add_cross(b); } };
        SingleLoopInCell(
            function (c) { return c.qsub !== CQSUB.gray; },
            function (c) { return c.qsub === CQSUB.yellow; },
            add_gray,
        );
        for (let i = 0; i < board.roommgr.components.length; i++) {
            let room = board.roommgr.components[i];
            let list = [];
            for (let j = 0; j < room.clist.length; j++) {
                list.push(room.clist[j]);
            }
            let nbnum = function (c) {
                let templist = [offset(c, -1, 0), offset(c, 1, 0), offset(c, 0, -1), offset(c, 0, 1)];
                return templist.filter(nc => !nc.isnull && c.room === nc.room).length;
            };
            let list2 = list.filter(c => nbnum(c) !== 1);
            let listodd = list.filter(c => (c.bx + c.by) % 4 === 2);
            let listeven = list.filter(c => (c.bx + c.by) % 4 === 0);
            if (list.length - list2.length === 2) {
                list2.forEach(c => fourside(add_border_cross, c.adjborder));
            }
            if (listeven.length === listodd.length + 1) {
                listodd.forEach(c => fourside(add_border_cross, c.adjborder));
            }
            if (listodd.length === listeven.length + 1) {
                listeven.forEach(c => fourside(add_border_cross, c.adjborder));
            }
            if (list.some(c => c.lcnt > 0 || c.qsub === CQSUB.yellow)) {
                list.forEach(c => add_yellow(c));
                let templist = list.filter(c => nbnum(c) === 1);
                let fn = function (b, nc) {
                    if (!nc.isnull && room === nc.room) {
                        add_line(b);
                    }
                };
                templist.forEach(c => fourside2(fn, c.adjborder, c.adjacent));
            }
            if (list.some(c => c.qsub === CQSUB.gray) || list.length - list2.length > 2 ||
                Math.abs(listodd.length - listeven.length) > 1) {
                list.forEach(c => add_gray(c));
                list.forEach(c => fourside(add_cross, c.adjborder));
                list.forEach(c => fourside(add_yellow, c.adjacent));
            }
        }
    }

    function AqreAssist() {
        BlockConnectedInCell();
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let fn = function (c1, c2, c3) {
                if (c1.isnull || c2.isnull || c3.isnull) { return; }
                if (c1.qans === CQANS.block && c2.qans === CQANS.block && c3.qans === CQANS.block) {
                    add_green(cell);
                }
                if (c1.qsub === CQSUB.green && c2.qsub === CQSUB.green && c3.qsub === CQSUB.green) {
                    add_block(cell);
                }
            };
            if (cell.qsub === CQSUB.none && cell.qans === CQANS.none) {
                fn(offset(cell, -3, 0), offset(cell, -2, 0), offset(cell, -1, 0),);
                fn(offset(cell, -2, 0), offset(cell, -1, 0), offset(cell, 1, 0),);
                fn(offset(cell, -1, 0), offset(cell, 1, 0), offset(cell, 2, 0),);
                fn(offset(cell, 1, 0), offset(cell, 2, 0), offset(cell, 3, 0),);
                fn(offset(cell, 0, -3), offset(cell, 0, -2), offset(cell, 0, -1),);
                fn(offset(cell, 0, -2), offset(cell, 0, -1), offset(cell, 0, 1),);
                fn(offset(cell, 0, -1), offset(cell, 0, 1), offset(cell, 0, 2),);
                fn(offset(cell, 0, 1), offset(cell, 0, 2), offset(cell, 0, 3),);
            }
        }
        for (let i = 0; i < board.roommgr.components.length; i++) {
            let room = board.roommgr.components[i];
            let qnum = room.top.qnum;
            if (qnum === CQNUM.none || qnum === CQNUM.quesmark) { continue; }
            let list = [];
            for (let j = 0; j < room.clist.length; j++) {
                list.push(room.clist[j]);
            }
            if (list.filter(c => c.qans === CQANS.block).length === qnum) {
                list.forEach(c => add_green(c));
            }
            if (qnum - list.filter(c => c.qans === CQANS.block).length ===
                list.filter(c => c.qans === CQANS.none && c.qsub === CQSUB.none).length) {
                list.forEach(c => add_block(c));
            }
        }
    }

    function KurodokoAssist() {
        GreenConnectedInCell();
        BlockNotAdjacent();
        SightNumber(
            function (c) { return c.qsub === CQSUB.green; },
            function (c) { return c.qans === CQANS.block; },
            add_green,
            add_block,
        );
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qnum !== CQNUM.none) {
                add_green(cell);
            }
        }
    }

    function HitoriAssist() {
        GreenConnectedInCell();
        BlockNotAdjacent();
        let uniq = new Map();
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            uniq.set(cell, true);
        }
        let fn = function(a) {
            let vis = new Map();
            for (let cell of a) {
                if (cell.qnum === CQNUM.none) continue;
                if (cell.qsub === CQSUB.green) vis.set(cell.qnum, cell);
            }
            for (let cell of a) {
                if (cell.qnum === CQNUM.none) continue;
                if (vis.has(cell.qnum)) add_block(cell);
            }

            let cnt = new Map();
            for (let cell of a) {
                if (cell.qnum === CQNUM.none || cell.qans === CQANS.block) continue;
                let c = cnt.has(cell.qnum) ? cnt.get(cell.qnum) : 0;
                c++;
                cnt.set(cell.qnum, c);
            }
            for (let cell of a) {
                if (cell.qnum === CQNUM.none || cell.qans === CQANS.block) continue;
                if (cnt.get(cell.qnum) >= 2) uniq.set(cell, false);
            }

            // aba
            for (let i = 0; i < a.length-2; i++) {
                if (a[i].qnum === CQNUM.none || a[i].qnum !== a[i+2].qnum) continue;
                add_green(a[i+1]);
            }

            // a..aa
            for (let i = 0; i < a.length-1; i++) {
                if (a[i].qnum === CQNUM.none || a[i].qnum !== a[i+1].qnum) continue;
                for (let j = 0; j < a.length; j++) {
                    if (j !== i && j !== i+1 && a[j].qnum === a[i].qnum) add_block(a[j]);
                }
            }
        };
        for (let i = 0; i < board.rows; i++) {
            let a = [];
            for (let j = 0; j < board.cols; j++) {
                a.push(board.getc(2 * j + 1, 2 * i + 1));
            }
            fn(a);
        }
        for (let j = 0; j < board.cols; j++) {
            let a = [];
            for (let i = 0; i < board.rows; i++) {
                a.push(board.getc(2 * j + 1, 2 * i + 1));
            }
            fn(a);
        }
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (uniq.get(cell)) add_green(cell);
        }
    }

    function CaveAssist() {
        GreenConnectedInCell();
        CellConnected(
            function (c) { return c.qans === CQANS.block; },
            function (c) { return c.qsub === CQSUB.green; },
            add_block,
            add_green,
            function (c, list) { list.push(c); return; },
            function (c, nb, nc) { return false; },
            true
        );
        SightNumber(
            function (c) { return c.qsub === CQSUB.green; },
            function (c) { return c.qans === CQANS.block; },
            add_green,
            add_block,
        );
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qnum !== CQNUM.none) {
                add_green(cell);
            }
            //checker
            if (cell.qsub === CQSUB.none && cell.qans === CQANS.none) {
                let fn = function (c, c1, c2, c12) {
                    if (c1.isnull || c2.isnull || c12.isnull) { return; }
                    if (c1.qsub === CQSUB.green && c2.qsub === CQSUB.green && c12.qans === CQANS.block) {
                        add_green(c);
                    }
                    if (c1.qans === CQANS.block && c2.qans === CQANS.block && c12.qsub === CQSUB.green) {
                        add_block(c);
                    }
                };
                for (let d = 0; d < 4; d++) {
                    fn(cell, offset(cell, 1, 0, d), offset(cell, 0, 1, d), offset(cell, 1, 1, d));
                }
            }
        }
    }

    function TapaAssist() {
        No2x2Cell(
            function (c) { return c.qans === CQANS.block; },
            add_dot,
        );
        CellConnected(
            function (c) { return c.qans === CQANS.block; },
            function (c) { return c.qsub === CQSUB.dot || c.qnums.length > 0; },
            add_block,
            add_dot,
        );
        let check = function (qnums, s) {
            if (s === "11111111") { return qnums.length === 1 && qnums[0] === 8 || qnums[0] === CQNUM.quesmark; }
            while (s[0] !== '0') {
                s = s.slice(1) + s[0];
            }
            s = s.split('0').filter(s => s.length > 0).map(s => s.length);
            if (s.length === 0) { s = [0]; }
            if (s.length !== qnums.length) { return false; }
            for (let i = 0; i < qnums.length; i++) {
                if (s.indexOf(qnums[i]) === -1) { continue; }
                s.splice(s.indexOf(qnums[i]), 1);
            }
            return s.length === qnums.filter(n => n === CQNUM.quesmark).length;
        };
        let isEmpty = function (c) {
            return !c.isnull && c.qans === CQANS.none && c.qsub === CQSUB.none && c.qnums.length === 0;
        };
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qnums.length === 0) { continue; }
            let list = [offset(cell, -1, -1), offset(cell, 0, -1), offset(cell, 1, -1), offset(cell, 1, 0),
            offset(cell, 1, 1), offset(cell, 0, 1), offset(cell, -1, 1), offset(cell, -1, 0)];
            let mask = parseInt(list.map(c => isEmpty(c) ? "1" : "0").join(""), 2);
            let blk = parseInt(list.map(c => (!c.isnull && c.qans === CQANS.block ? "1" : "0")).join(""), 2);
            let setb = 0b11111111, setd = 0b00000000, n = 0;
            for (let j = mask; j >= 0; j--) {
                j &= mask;
                if (check(cell.qnums, (j | blk).toString(2).padStart(8, '0'))) {
                    setb &= (j | blk);
                    setd |= (j | blk);
                    n++;
                }
            }
            if (n === 0) {
                add_block(cell);
                continue;
            }
            setb = setb.toString(2).padStart(8, '0');
            setd = setd.toString(2).padStart(8, '0');
            for (let j = 0; j < 8; j++) {
                if (setb[j] === '1') {
                    add_block(list[j], true);
                }
                if (setd[j] === '0') {
                    add_dot(list[j]);
                }
            }
        }
    }

    function LightandShadowAssist() {
        let add_black = function (c) {
            if (c.isnull || c.qans !== CQANS.none) { return; }
            if (step && flg) { return; }
            flg = 1;
            c.setQans(CQANS.black);
            c.draw();
        };
        let add_white = function (c) {
            if (c.isnull || c.qans !== CQANS.none) { return; }
            if (step && flg) { return; }
            flg = 1;
            c.setQans(CQANS.white);
            c.draw();
        };
        NumberRegion(
            function (c) { return c.qans === CQANS.black; },
            function (c) { return c.qans === CQANS.white; },
            add_black,
            add_white,
            true,
            false,
        );
        NumberRegion(
            function (c) { return c.qans === CQANS.white; },
            function (c) { return c.qans === CQANS.black; },
            add_white,
            add_black,
            true,
            false,
        );
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qnum !== CQNUM.none && cell.ques === 1) {
                add_black(cell);
            }
            if (cell.qnum !== CQNUM.none && cell.ques === 0) {
                add_white(cell);
            }
        }
    }

    function SlalomAssist() {
        SingleLoopInCell(
            function (c) { return c.ques !== 1; },
            function (c) { return c.bx === board.startpos.bx && c.by === board.startpos.by; }
        );
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.ques === 1) {
                fourside(add_cross, cell.adjborder);
            }
            if (cell.ques === CQUES.vgate && (cell.adjacent.top.isnull || cell.adjacent.top.ques !== CQUES.vgate)) {
                let list = [];
                let pcell = cell;
                while (!pcell.isnull && pcell.ques === CQUES.vgate) {
                    add_cross(pcell.adjborder.top);
                    add_cross(pcell.adjborder.bottom);
                    list.push(pcell);
                    pcell = pcell.adjacent.bottom;
                }
                if (list.filter(c => c.adjborder.left.line).length === 1) {
                    list.forEach(c => add_cross(c.adjborder.left));
                }
                if (list.filter(c => c.adjborder.left.qsub !== BQSUB.cross).length === 1) {
                    list.forEach(c => add_line(c.adjborder.left));
                }
            }
            if (cell.ques === CQUES.hgate && (cell.adjacent.left.isnull || cell.adjacent.left.ques !== CQUES.hgate)) {
                let list = [];
                let pcell = cell;
                while (!pcell.isnull && pcell.ques === CQUES.hgate) {
                    add_cross(pcell.adjborder.left);
                    add_cross(pcell.adjborder.right);
                    list.push(pcell);
                    pcell = pcell.adjacent.right;
                }
                if (list.filter(c => c.adjborder.top.line).length === 1) {
                    list.forEach(c => add_cross(c.adjborder.top));
                }
                if (list.filter(c => c.adjborder.top.qsub !== BQSUB.cross).length === 1) {
                    list.forEach(c => add_line(c.adjborder.top));
                }
            }
        }
    }

    function StarbattleAssist() {
        let add_cir = function (b) {
            if (b === undefined || b.isnull || b.line || b.qsub !== BQSUB.none) { return; }
            if (step && flg) { return; }
            b.setQsub(1);
            b.draw();
            flg |= b.qsub === BQSUB.cross;
        };
        let starcount = board.starCount.count;
        let add_star = add_block;
        for (let i = 0; i < board.roommgr.components.length; i++) {
            let room = board.roommgr.components[i];
            let cellList = [];
            for (let j = 0; j < room.clist.length; j++) {
                cellList.push(room.clist[j]);
            }
            //finish room
            if (cellList.filter(c => c.qans === CQANS.star).length === starcount) {
                cellList.forEach(c => add_dot(c));
            }
            if (cellList.filter(c => c.qsub !== CQSUB.dot).length === starcount) {
                cellList.forEach(c => add_star(c));
            }
        }
        for (let i = 0; i < board.rows; i++) {
            let hcellList = [];
            let vcellList = [];
            for (let j = 0; j < board.cols; j++) {
                hcellList.push(board.getc(2 * i + 1, 2 * j + 1));
                vcellList.push(board.getc(2 * j + 1, 2 * i + 1));
            }
            //finish row/col
            if (hcellList.filter(c => c.qans === CQANS.star).length === starcount) {
                hcellList.forEach(c => add_dot(c));
            }
            if (hcellList.filter(c => c.qsub !== CQSUB.dot).length === starcount) {
                hcellList.forEach(c => add_star(c));
            }
            if (vcellList.filter(c => c.qans === CQANS.star).length === starcount) {
                vcellList.forEach(c => add_dot(c));
            }
            if (vcellList.filter(c => c.qsub !== CQSUB.dot).length === starcount) {
                vcellList.forEach(c => add_star(c));
            }
        }
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qans === CQANS.star) {
                for (let dx = -1; dx <= 1; dx++) {
                    for (let dy = -1; dy <= 1; dy++) {
                        add_dot(offset(cell, dx, dy));
                    }
                }
            }
        }
        for (let i = 0; i < board.cross.length; i++) {
            let cross = board.cross[i];
            if (cross.qsub !== 1) { continue; }
            for (let d = 0; d < 4; d++) {
                if (offset(cross, .5, .5, d).qsub === CQSUB.dot && offset(cross, -.5, .5, d).qsub === CQSUB.dot) {
                    add_cir(offset(cross, 0, -.5, d));
                    cross.setQsub(0);
                    cross.draw();
                }
            }
        }
        for (let i = 0; i < board.border.length; i++) {
            let border = board.border[i];
            if (border.qsub !== 1) { continue; }
            if (border.isvert) {
                add_dot(offset(border, -.5, -1));
                add_dot(offset(border, +.5, -1));
                add_dot(offset(border, -.5, +1));
                add_dot(offset(border, +.5, +1));
            } else {
                add_dot(offset(border, -1, -.5));
                add_dot(offset(border, -1, +.5));
                add_dot(offset(border, +1, -.5));
                add_dot(offset(border, +1, +.5));
            }
            for (let j = 0; j <= 1; j++) {
                if (border.sidecell[j].qsub === CQSUB.dot) {
                    add_star(border.sidecell[1 - j]);
                }
            }
        }
    }

    function CastleWallAssist() {
        SingleLoopInCell(
            function (c) { return c.qnum === CQNUM.none; },
        );
        //add invisible qsub at cross
        const INLOOP = 1, OUTLOOP = 2;
        let add_inout = function (cr, qsub) {
            if (cr.isnull || cr.qsub !== 0) { return; }
            cr.setQsub(qsub);
        }
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qnum !== CQNUM.none) {
                //add qsub around b/w clue
                if (cell.ques === CQUES.black || cell.ques === CQUES.white) {
                    for (let d = 0; d < 4; d++) {
                        add_inout(offset(cell, .5, .5, d), (cell.ques === CQUES.black ? OUTLOOP : INLOOP));
                    }
                }
                //finish clue
                if (cell.qnum !== CQNUM.quesmark) {
                    let d = qdirremap(cell.qdir);
                    let borderlist = [];
                    let pcell = dir(cell.adjacent, d);
                    let qnum = cell.qnum;
                    while (!pcell.isnull && (pcell.qnum < 0 || pcell.qdir !== cell.qdir)) {
                        let b = dir(pcell.adjborder, d);
                        if (!b.isnull && b.sidecell[0].qnum === CQNUM.none && b.sidecell[1].qnum === CQNUM.none) {
                            borderlist.push(b);
                        }
                        pcell = dir(pcell.adjacent, d);
                    }
                    if (!pcell.isnull) {
                        qnum -= pcell.qnum;
                    }
                    if (borderlist.filter(b => b.line).length === qnum) {
                        borderlist.forEach(b => add_cross(b));
                    }
                    if (borderlist.filter(b => b.qsub === BQSUB.none).length === qnum) {
                        borderlist.forEach(b => add_line(b));
                    }
                }
            }
        }
        for (let i = 0; i < board.cross.length; i++) {
            let cross = board.cross[i];
            //outloop at side
            if (cross.bx === board.minbx || cross.bx === board.maxbx || cross.by === board.minby || cross.by === board.maxby) {
                add_inout(cross, OUTLOOP);
            }
            //no checker
            if (cross.qsub === 0) {
                let fn = function (cr, cr1, cr2, cr12) {
                    if (cr1.isnull || cr2.isnull || cr12.isnull) { return; }
                    if (cr1.qsub === 0 || cr2.qsub === 0 || cr12.qsub === 0) { return; }
                    if (cr1.qsub === cr2.qsub && cr1.qsub !== cr12.qsub) {
                        add_inout(cr, cr1.qsub);
                    }
                };
                for (let d = 0; d < 4; d++) {
                    fn(cross, offset(cross, 1, 0, d), offset(cross, 0, 1, d), offset(cross, 1, 1, d));
                }
            }
            //add line between different i/o
            if (cross.qsub !== 0) {
                let fn = function (cr1, cr2, b) {
                    if (cr1.isnull || cr2.isnull) { return; }
                    if (cr1.qsub === 0 || cr2.qsub === 0) { return; }
                    if (cr1.qsub === cr2.qsub) {
                        add_cross(b);
                    }
                    if (cr1.qsub !== cr2.qsub) {
                        add_line(b);
                    }
                };
                for (let d = 0; d < 4; d++) {
                    fn(cross, offset(cross, 1, 0, d), offset(cross, .5, 0, d));
                }
            }
            //extend i/o through cross/line
            if (cross.qsub !== 0) {
                let fn = function (cr1, cr2, b) {
                    if (cr2.isnull) { return; }
                    if (b.isnull || b.qsub === BQSUB.cross || b.sidecell[0].qnum !== CQNUM.none || b.sidecell[1].qnum !== CQNUM.none) {
                        add_inout(cr2, cr1.qsub);
                    }
                    if (!b.isnull && b.line) {
                        add_inout(cr2, INLOOP + OUTLOOP - cr1.qsub);
                    }
                };
                for (let d = 0; d < 4; d++) {
                    fn(cross, offset(cross, 1, 0, d), offset(cross, .5, 0, d));
                }
            }
        }
    }

    function NurimisakiAssist() {
        let isEmpty = function (c) { return !c.isnull && c.qnum === CQNUM.none && c.qsub === CQSUB.none && c.qans === CQANS.none; }
        let isDot = function (c) { return !c.isnull && c.qsub === CQSUB.dot; }
        let isDotEmpty = function (c) { return isEmpty(c) || isDot(c); }
        let isBlock = function (c) { return !c.isnull && c.qans === CQANS.block; }
        let isBorderBlock = function (c) { return c.isnull || c.qans === CQANS.block; }
        let isConnectBlock = function (c) { return isBorderBlock(c) || c.qnum !== CQNUM.none; }
        let isCircle = function (c) { return !c.isnull && c.qnum !== CQNUM.none; }
        let isDotCircle = function (c) { return isDot(c) || isCircle(c); }

        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let blocknum = 0;
            let dotnum = 0;
            let fn = function (c) {
                if (isBorderBlock(c)) { blocknum++; }
                if (isDot(c)) { dotnum++; }
            };
            fourside(fn, cell.adjacent);

            //no clue pattern

            //add dot
            if (isEmpty(cell)) {
                for (let d = 0; d < 4; d++) {
                    //cannot place block with 2x2 block rule
                    if (isEmpty(offset(cell, 0, -1, d)) && isBlock(offset(cell, 1, 0, d)) && isBlock(offset(cell, 1, -1, d)) &&
                        (isBorderBlock(offset(cell, 0, -2, d)) || isBorderBlock(offset(cell, -1, -1, d)))) {
                        add_dot(cell);
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isBlock(offset(cell, -1, 0, d)) && isBlock(offset(cell, -1, -1, d)) &&
                        (isBorderBlock(offset(cell, 0, -2, d)) || isBorderBlock(offset(cell, 1, -1, d)))) {
                        add_dot(cell);
                    }
                    else if (isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 0, -1, d)) && isBlock(offset(cell, 1, -1, d)) &&
                        (isBorderBlock(offset(cell, 2, 0, d)) || isBorderBlock(offset(cell, 1, 1, d))) &&
                        (isBorderBlock(offset(cell, 0, -2, d)) || isBorderBlock(offset(cell, -1, -1, d)))) {
                        add_dot(cell);
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, -1, d)) && isBlock(offset(cell, 1, 0, d)) &&
                        isBorderBlock(offset(cell, -1, -1, d)) && isBorderBlock(offset(cell, 0, -2, d)) && offset(cell, 1, -2, d).isnull) {
                        add_dot(cell);
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, -1, -1, d)) && isBlock(offset(cell, -1, 0, d)) &&
                        isBorderBlock(offset(cell, 1, -1, d)) && isBorderBlock(offset(cell, 0, -2, d)) && offset(cell, -1, -2, d).isnull) {
                        add_dot(cell);
                    }
                    //cannot place block with 2x2 dot rule
                    else if (isBlock(offset(cell, 1, 0, d)) && isBlock(offset(cell, 1, 1, d)) && isDot(offset(cell, -1, 2, d)) &&
                        isEmpty(offset(cell, 0, 1, d)) && isDotEmpty(offset(cell, -1, 1, d)) && isDotEmpty(offset(cell, 0, 2, d))) {
                        add_dot(cell);
                    }
                    else if (isBlock(offset(cell, -1, 0, d)) && isBlock(offset(cell, -1, 1, d)) && isDot(offset(cell, 1, 2, d)) &&
                        isEmpty(offset(cell, 0, 1, d)) && isDotEmpty(offset(cell, 1, 1, d)) && isDotEmpty(offset(cell, 0, 2, d))) {
                        add_dot(cell);
                    }
                    //cannot place block with 2x2 block rule and 2x2 dot rule
                    else if (isDotEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 0, -2, d)) &&
                        isBlock(offset(cell, 1, -1, d)) && isBlock(offset(cell, 1, -2, d)) && isBorderBlock(offset(cell, 0, -3, d))) {
                        add_dot(cell);
                    }
                    else if (isDotEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 0, -2, d)) &&
                        isBlock(offset(cell, -1, -1, d)) && isBlock(offset(cell, -1, -2, d)) && isBorderBlock(offset(cell, 0, -3, d))) {
                        add_dot(cell);
                    }
                    else if (isDotEmpty(offset(cell, -1, 0, d)) && isEmpty(offset(cell, -1, -1, d)) &&
                        isBlock(offset(cell, 0, -1, d)) && isBorderBlock(offset(cell, -1, 1, d)) && isBorderBlock(offset(cell, -1, -2, d))) {
                        add_dot(cell);
                    }
                    else if (isDotEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
                        isBlock(offset(cell, 0, -1, d)) && isBorderBlock(offset(cell, 1, 1, d)) && isBorderBlock(offset(cell, 1, -2, d))) {
                        add_dot(cell);
                    }
                    //cannot place block with 2x3 border pattern
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, -1, d)) && isEmpty(offset(cell, 2, -1, d)) &&
                        isDotEmpty(offset(cell, 1, 0, d)) && isDotEmpty(offset(cell, 2, 0, d)) &&
                        isBorderBlock(offset(cell, -1, -1, d)) && isBorderBlock(offset(cell, 3, -1, d)) &&
                        isBorderBlock(offset(cell, 3, 0, d)) && offset(cell, 0, -2, d).isnull) {
                        add_dot(cell);
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, -1, -1, d)) && isEmpty(offset(cell, -2, -1, d)) &&
                        isDotEmpty(offset(cell, -1, 0, d)) && isDotEmpty(offset(cell, -2, 0, d)) &&
                        isBorderBlock(offset(cell, 1, -1, d)) && isBorderBlock(offset(cell, -3, -1, d)) &&
                        isBorderBlock(offset(cell, -3, 0, d)) && offset(cell, 0, -2, d).isnull) {
                        add_dot(cell);
                    }
                }
            }
            if (cell.qsub === CQSUB.dot) {
                //dot cannot be deadend
                if (blocknum === 2) {
                    fourside(add_dot, cell.adjacent);
                }
                for (let d = 0; d < 4; d++) {
                    //avoid 2x2 dot
                    if (isBorderBlock(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 0, 1, d)) &&
                        isEmpty(offset(cell, -1, 0, d)) && isDot(offset(cell, 1, 1, d))) {
                        add_dot(offset(cell, -1, 0, d));
                    }
                    else if (isBorderBlock(offset(cell, 0, -1, d)) && isEmpty(offset(cell, -1, 0, d)) && isEmpty(offset(cell, 0, 1, d)) &&
                        isEmpty(offset(cell, 1, 0, d)) && isDot(offset(cell, -1, 1, d))) {
                        add_dot(offset(cell, 1, 0, d));
                    }
                    //dot cannot be deadend with 2x2 dot rule
                    else if (isBorderBlock(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isBorderBlock(offset(cell, 2, 0, d)) &&
                        isBorderBlock(offset(cell, 1, -1, d)) && isEmpty(offset(cell, -1, 0, d))) {
                        add_dot(offset(cell, -1, 0, d));
                    }
                    else if (isBorderBlock(offset(cell, 0, -1, d)) && isEmpty(offset(cell, -1, 0, d)) && isBorderBlock(offset(cell, -2, 0, d)) &&
                        isBorderBlock(offset(cell, -1, -1, d)) && isEmpty(offset(cell, 1, 0, d))) {
                        add_dot(offset(cell, 1, 0, d));
                    }
                }
            }

            //add block
            if (isEmpty(cell)) {
                //block deadend
                if (blocknum >= 3) {
                    add_block(cell);
                }
                for (let d = 0; d < 4; d++) {
                    //cannot dot with 2x2 dot rule
                    if (isBorderBlock(offset(cell, -1, 0, d)) && isBorderBlock(offset(cell, 2, 0, d)) && isEmpty(offset(cell, 1, 0, d)) &&
                        offset(cell, 0, -1, d).isnull && offset(cell, 1, -1, d).isnull) {
                        add_block(cell);
                    }
                    else if (isBorderBlock(offset(cell, 1, 0, d)) && isBorderBlock(offset(cell, 0, -1, d)) && isDot(offset(cell, -1, 1, d)) &&
                        isDotEmpty(offset(cell, -1, 0, d)) && isDotEmpty(offset(cell, 0, 1, d))) {
                        add_block(cell);
                    }
                }
            }

            //clue pattern

            //any circle clue
            if (cell.qnum !== CQNUM.none) {
                //clue deadend check
                if (blocknum === 3) {
                    fourside(add_dot, cell.adjacent);
                }
                else if (dotnum === 1) {
                    fourside(add_block, cell.adjacent);
                }
                for (let d = 0; d < 4; d++) {
                    //avoid 2x2 block pattern
                    if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
                        isBlock(offset(cell, 0, -2, d)) && isBlock(offset(cell, 1, -2, d))) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
                        isBlock(offset(cell, 2, 0, d)) && isBlock(offset(cell, 2, -1, d))) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
                        isEmpty(offset(cell, 0, -2, d)) && isBlock(offset(cell, 1, -2, d)) &&
                        (isBorderBlock(offset(cell, 0, -3, d)) || isBorderBlock(offset(cell, -1, -2, d)))) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
                        isEmpty(offset(cell, 2, 0, d)) && isBlock(offset(cell, 2, -1, d)) &&
                        (isBorderBlock(offset(cell, 3, 0, d)) || isBorderBlock(offset(cell, 2, 1, d)))) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                    //avoid 2x2 block and 2x2 dot apttern
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isDotEmpty(offset(cell, 1, -1, d)) &&
                        isEmpty(offset(cell, 1, -2, d)) && isBlock(offset(cell, 0, -2, d)) && isBorderBlock(offset(cell, 1, -3, d))) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isDotEmpty(offset(cell, 1, -1, d)) &&
                        isEmpty(offset(cell, 2, -1, d)) && isBlock(offset(cell, 2, 0, d)) && isBorderBlock(offset(cell, 3, -1, d))) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                    //avoid border 2x2 block
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
                        isDotEmpty(offset(cell, 2, 0, d)) && isEmpty(offset(cell, 2, -1, d)) && isBorderBlock(offset(cell, 3, 0, d)) &&
                        isBorderBlock(offset(cell, 3, -1, d)) && offset(cell, 0, -2, d).isnull) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
                        isDotEmpty(offset(cell, 0, -2, d)) && isEmpty(offset(cell, 1, -2, d)) && isBorderBlock(offset(cell, 0, -3, d)) &&
                        isBorderBlock(offset(cell, 1, -3, d)) && offset(cell, 2, 0, d).isnull) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                    //avoid border 2x3 pattern
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
                        isDotEmpty(offset(cell, 2, 0, d)) && isDotEmpty(offset(cell, 2, -1, d)) && isDotEmpty(offset(cell, 3, 0, d)) &&
                        isEmpty(offset(cell, 3, -1, d)) && isBorderBlock(offset(cell, 4, 0, d)) &&
                        isBorderBlock(offset(cell, 4, -1, d)) && offset(cell, 0, -2, d).isnull) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                    else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
                        isDotEmpty(offset(cell, 0, -2, d)) && isDotEmpty(offset(cell, 1, -2, d)) && isDotEmpty(offset(cell, 0, -3, d)) &&
                        isEmpty(offset(cell, 1, -3, d)) && isBorderBlock(offset(cell, 0, -4, d)) &&
                        isBorderBlock(offset(cell, 1, -4, d)) && offset(cell, 2, 0, d).isnull) {
                        add_block(offset(cell, 0, 1, d));
                        add_block(offset(cell, -1, 0, d));
                    }
                }
            }
            if (isEmpty(cell)) {
                for (let d = 0; d < 4; d++) {
                    //cannot place block with 2x2 white
                    if (isBlock(offset(cell, 1, 0, d)) && isBlock(offset(cell, 1, 1, d)) && isCircle(offset(cell, -1, 2, d)) &&
                        isEmpty(offset(cell, 0, 1, d)) && isEmpty(offset(cell, -1, 1, d)) && isEmpty(offset(cell, 0, 2, d))) {
                        add_dot(cell);
                    }
                    else if (isBlock(offset(cell, -1, 0, d)) && isBlock(offset(cell, -1, 1, d)) && isCircle(offset(cell, 1, 2, d)) &&
                        isEmpty(offset(cell, 0, 1, d)) && isEmpty(offset(cell, 1, 1, d)) && isEmpty(offset(cell, 0, 2, d))) {
                        add_dot(cell);
                    }
                    //cannot place dot with 2x2 white
                    else if (isBorderBlock(offset(cell, 1, 0, d)) && isBorderBlock(offset(cell, 0, -1, d)) && isCircle(offset(cell, -1, 1, d)) &&
                        isEmpty(offset(cell, -1, 0, d)) && isEmpty(offset(cell, 0, 1, d))) {
                        add_block(cell);
                    }
                }
            }
            if (cell.qsub === CQSUB.dot) {
                for (let d = 0; d < 4; d++) {
                    //avoid 2x2 white
                    if (isBorderBlock(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 0, 1, d)) &&
                        isDotEmpty(offset(cell, -1, 0, d)) && isCircle(offset(cell, 1, 1, d))) {
                        add_dot(offset(cell, -1, 0, d));
                        add_block(offset(cell, 2, 1, d));
                        add_block(offset(cell, 1, 2, d));
                    }
                    else if (isBorderBlock(offset(cell, 0, -1, d)) && isEmpty(offset(cell, -1, 0, d)) && isEmpty(offset(cell, 0, 1, d)) &&
                        isDotEmpty(offset(cell, 1, 0, d)) && isCircle(offset(cell, -1, 1, d))) {
                        add_dot(offset(cell, 1, 0, d));
                        add_block(offset(cell, -2, 1, d));
                        add_block(offset(cell, -1, 2, d));
                    }
                }
            }

            //circle clue with number
            if (cell.qnum >= 2) {
                for (let d = 0; d < 4; d++) {
                    if (isEmpty(offset(cell, 0, -1, d))) {
                        //avoid eyesight too long
                        if (isDotCircle(offset(cell, 0, -cell.qnum, d))) {
                            add_block(offset(cell, 0, -1, d));
                        }
                        //situation for clue at the end
                        else if (isCircle(offset(cell, 0, -cell.qnum + 1, d)) &&
                            offset(cell, 0, -cell.qnum + 1, d).qnum !== CQNUM.circle && offset(cell, 0, -cell.qnum + 1, d).qnum !== cell.qnum) {
                            add_block(offset(cell, 0, -1, d));
                        }
                    }
                    if (isEmpty(offset(cell, 0, -1, d))) {
                        for (let j = 2; j < cell.qnum; j++) {
                            //eyesight not enough long
                            if (j !== cell.qnum - 1 && isConnectBlock(offset(cell, 0, -j, d))) {
                                add_block(offset(cell, 0, -1, d));
                                break;
                            }
                            if (isBorderBlock(offset(cell, 0, -j, d))) {
                                add_block(offset(cell, 0, -1, d));
                                break;
                            }
                            //avoid 2x2 dot
                            if (isDot(offset(cell, 1, -j + 1, d)) && isDot(offset(cell, 1, -j, d))) {
                                add_block(offset(cell, 0, -1, d));
                                break;
                            }
                            if (isDot(offset(cell, -1, -j + 1, d)) && isDot(offset(cell, -1, -j, d))) {
                                add_block(offset(cell, 0, -1, d));
                                break;
                            }
                        }
                    }
                    //extend eyesight
                    if (isDot(offset(cell, 0, -1, d))) {
                        for (let j = 2; j < cell.qnum; j++) {
                            add_dot(offset(cell, 0, -j, d));
                        }
                        add_block(offset(cell, 0, -cell.qnum, d));
                    }
                }
            }
        }
        //2x2 rules
        No2x2Cell(
            function (c) { return c.qans === CQANS.block; },
            add_dot
        );
        No2x2Cell(
            function (c) { return c.qsub === CQSUB.dot; },
            add_block
        );
        CellConnected(isDot, isConnectBlock, add_dot, add_block);
    }

    function ChocoBananaAssist() {
        NumberRegion(
            function (c) { return c.qans === CQANS.block; },
            function (c) { return c.qsub === CQSUB.green; },
            add_block,
            add_green,
            false,
            false
        );
        NumberRegion(
            function (c) { return c.qsub === CQSUB.green; },
            function (c) { return c.qans === CQANS.block; },
            add_green,
            add_block,
            false,
            false
        );
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qnum === 1 || cell.qnum === 2) {
                add_block(cell);
            }
            let templist = [cell, offset(cell, 0, 1), offset(cell, 1, 0), offset(cell, 1, 1)];
            if (templist.filter(c => c.qans === CQANS.block).length === 3) {
                templist.forEach(c => add_block(c));
            }
            let fn = function (c, c1, c2, c12) {
                if (c1.isnull || c2.isnull || c12.isnull) { return; }
                if (c1.qans === CQANS.block && c2.qans === CQANS.block && c12.qsub === CQSUB.green) {
                    add_green(c);
                }
                if (c1.qans === CQANS.block && c2.qsub === CQSUB.green && c12.qans === CQANS.block) {
                    add_green(c);
                }
                if (c1.qsub === CQSUB.green && c2.qans === CQANS.block && c12.qans === CQANS.block) {
                    add_green(c);
                }
            };
            for (let d = 0; d < 4; d++) {
                fn(cell, offset(cell, 1, 0, d), offset(cell, 0, 1, d), offset(cell, 1, 1, d));
            }
            if (cell.qsub === CQSUB.green) {
                let templist = [offset(cell, 1, 0), offset(cell, 0, 1), offset(cell, -1, 0), offset(cell, 0, -1)];
                templist = templist.filter(c => !c.isnull && c.qans !== CQANS.block);
                if (templist.length === 1) {
                    let ncell = templist[0];
                    add_green(ncell);
                    let templist2 = [offset(ncell, 1, 0), offset(ncell, 0, 1), offset(ncell, -1, 0), offset(ncell, 0, -1)];
                    templist2 = templist2.filter(c => !c.isnull && c.qans !== CQANS.block && c !== cell);
                    if (templist2.length === 1) {
                        add_green(templist2[0]);
                    }
                }
            }
        }
    }

    function SlantAssist() {
        let add_slash = function (c, qans) {
            if (c === undefined || c.isnull || c.qans !== CQANS.none) { return; }
            if (step && flg) { return; }
            flg = true;
            c.setQans(qans % 2 === 0 ? CQANS.lslash : CQANS.rslash);
            c.draw();
        };
        let isNotSide = function (c) {
            return c.bx > board.minbx && c.bx < board.maxbx && c.by > board.minby && c.by < board.maxby;
        }
        for (let i = 0; i < board.cross.length; i++) {
            let cross = board.cross[i];
            let adjcellList = [[board.getc(cross.bx - 1, cross.by - 1), CQANS.rslash, CQANS.lslash],
            [board.getc(cross.bx - 1, cross.by + 1), CQANS.lslash, CQANS.rslash],
            [board.getc(cross.bx + 1, cross.by - 1), CQANS.lslash, CQANS.rslash],
            [board.getc(cross.bx + 1, cross.by + 1), CQANS.rslash, CQANS.lslash]];
            adjcellList = adjcellList.filter(c => !c[0].isnull);
            //finish clue
            if (cross.qnum >= 0) {
                if (adjcellList.filter(c => c[0].qans === c[1]).length === cross.qnum) {
                    adjcellList.forEach(c => add_slash(c[0], c[2]));
                }
                if (adjcellList.filter(c => c[0].qans !== c[2]).length === cross.qnum) {
                    adjcellList.forEach(c => add_slash(c[0], c[1]));
                }
            }
            //diagonal 1 & 1
            if (cross.qnum === 1 && isNotSide(cross)) {
                for (let d = 0; d < 4; d++) {
                    let cross2 = offset(cross, 1, 1, d);
                    if (cross2.qnum === 1 && isNotSide(cross2)) {
                        add_slash(offset(cross, .5, .5, d), CQANS.lslash + d);
                    }
                }
            }
            //1 & 1
            if (cross.qnum === 1) {
                for (let d = 0; d < 4; d++) {
                    if (offset(cross, 0, 1, d).isnull || offset(cross, 0, -1, d).isnull) { continue; }
                    if (!offset(cross, 1, 0, d).isnull && offset(cross, 1, 0, d).qnum === 1) {
                        add_slash(offset(cross, -0.5, -.5, d), CQANS.lslash + d);
                        add_slash(offset(cross, -0.5, +.5, d), CQANS.rslash + d);
                        add_slash(offset(cross, +1.5, -.5, d), CQANS.rslash + d);
                        add_slash(offset(cross, +1.5, +.5, d), CQANS.lslash + d);
                    }
                }
            }
            //3 & 3
            if (cross.qnum === 3) {
                for (let d = 0; d < 4; d++) {
                    if (!offset(cross, 1, 0, d).isnull && offset(cross, 1, 0, d).qnum === 3 && isNotSide(offset(cross, 1, 0, d))) {
                        add_slash(offset(cross, -0.5, -.5, d), CQANS.rslash + d);
                        add_slash(offset(cross, -0.5, +.5, d), CQANS.lslash + d);
                        add_slash(offset(cross, +1.5, -.5, d), CQANS.lslash + d);
                        add_slash(offset(cross, +1.5, +.5, d), CQANS.rslash + d);
                    }
                }
            }
        }
        //no loop
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qans !== CQANS.none) { continue; }
            let cross1, cross2;
            cross1 = board.getobj(cell.bx - 1, cell.by - 1);
            cross2 = board.getobj(cell.bx + 1, cell.by + 1);
            if (cross1.path !== null && cross1.path === cross2.path) {
                add_slash(cell, CQANS.lslash);
            }
            cross1 = board.getobj(cell.bx - 1, cell.by + 1);
            cross2 = board.getobj(cell.bx + 1, cell.by - 1);
            if (cross1.path !== null && cross1.path === cross2.path) {
                add_slash(cell, CQANS.rslash);
            }
        }
    }

    function NurikabeAssist() {
        for (let i = 0; i < board.cell.length; i++) {
            //add dot on num
            let cell = board.cell[i];
            if (cell.qnum !== CQNUM.none) {
                add_green(cell);
            }
            //surrounded white cell
            let templist = [offset(cell, 1, 0, 0), offset(cell, 1, 0, 1), offset(cell, 1, 0, 2), offset(cell, 1, 0, 3)];
            if (cell.qnum === CQNUM.none && templist.filter(c => c.isnull || c.qans === CQANS.block).length === 4) {
                add_block(cell);
            }
        }
        flg = 0;
        BlockConnectedInCell();
        No2x2Block();
        NumberRegion(
            function (c) { return c.qsub === CQSUB.dot; },
            function (c) { return c.qans === CQANS.block; },
            add_green,
            add_block
        );
        //unreachable cell
        {
            let list = [];
            for (let i = 0; i < board.cell.length; i++) {
                let cell = board.cell[i];
                if (cell.qnum !== CQNUM.none) {
                    list.push(cell);
                    if (cell.qnum === CQNUM.quesmark) { continue; }
                    for (let dx = -cell.qnum + 1; dx <= cell.qnum - 1; dx++) {
                        for (let dy = -cell.qnum + Math.abs(dx) + 1; dy <= cell.qnum - Math.abs(dx) - 1; dy++) {
                            let c = offset(cell, dx, dy);
                            if (c.isnull || list.indexOf(c) !== -1) { continue; }
                            list.push(c);
                        }
                    }
                }
            }
            if (!list.some(c => c.qnum === CQNUM.quesmark)) {
                for (let i = 0; i < board.cell.length; i++) {
                    let cell = board.cell[i];
                    if (list.indexOf(cell) === -1) {
                        add_block(cell);
                    }
                }
            }
        }
        //remove the dot on num because it looks weird
        if (!step) {
            for (let i = 0; i < board.cell.length; i++) {
                let cell = board.cell[i];
                if (cell.qnum !== CQNUM.none) {
                    cell.setQsub(CQSUB.none);
                    cell.draw();
                }
            }
        }
    }

    function GuideArrowAssist() {
        BlockNotAdjacent();
        GreenConnectedInCell();
        GreenNoLoopInCell();
        let goalcell = board.getc(board.goalpos.bx, board.goalpos.by);
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell === goalcell) {
                add_green(cell);
                continue;
            }
            if (cell.qnum !== CQNUM.none) {
                add_green(cell);
                if (cell.qnum !== CQNUM.quesmark) {
                    let d = qdirremap(cell.qnum);
                    add_green(dir(cell.adjacent, d));
                }
                continue;
            }
        }
        // direction consistency
        {
            let vis = new Map();
            let dfs = function(c, d) {
                if (vis.has(c)) return;
                vis.set(c, d);
                for (let d1 = 0; d1 < 4; d1++) {
                    if (d1 === d) continue;
                    let c1 = dir(c.adjacent, d1);
                    if (c1 === undefined || c1.isnull || c1.qsub !== CQSUB.green) continue;
                    dfs(c1, (d1+2)%4);
                }
            };
            dfs(goalcell, -1);
            for (let i = 0; i < board.cell.length; i++) {
                let cell = board.cell[i];
                if (cell.qnum === CQNUM.none || cell.qnum === CQNUM.quesmark) continue;
                dfs(cell, qdirremap(cell.qnum));
            }
            for (let i = 0; i < board.cell.length; i++) {
                let cell = board.cell[i];
                let adjcell = cell.adjacent;
                if (cell.qsub !== CQSUB.none || cell.qans !== CQANS.none) continue;

                let cnt = 0;
                let fn = function (c) {
                    if (!c.isnull && c.qsub === CQSUB.green && vis.has(c)) { cnt++; }
                };
                fourside(fn, adjcell);
                if (cnt >= 2) add_block(cell);
            }
        }
        // single out
        {
            let vis = new Map();
            vis.set(goalcell, -1);
            for (let i = 0; i < board.cell.length; i++) {
                let cell = board.cell[i];
                let d = (function () {
                    if (cell.qnum === CQNUM.none || cell.qnum === CQNUM.quesmark) {
                        let cnt = 0;
                        let dd = -1;
                        for (let d1 = 0; d1 < 4; d1++) {
                            let c1 = dir(cell.adjacent, d1);
                            if (c1 === undefined || c1.isnull || c1.qans === CQANS.block) continue;
                            cnt++;
                            dd = d1;
                        }
                        if (cnt === 1) return dd;
                        return -1;
                    }
                    return qdirremap(cell.qnum);;
                })();
                if (d === -1) continue;
                while (true) {
                    if (vis.has(cell)) break;
                    vis.set(cell, d);
                    cell = dir(cell.adjacent, d);
                    add_green(cell);
                    let cnt = 0;
                    let dd = -1;
                    for (let d1 = 0; d1 < 4; d1++) {
                        let c1 = dir(cell.adjacent, d1);
                        if (c1 === undefined || c1.isnull || c1.qans === CQANS.block) continue;
                        if (vis.has(c1) && vis.get(c1) === (d1+2)%4) continue;
                        cnt++;
                        dd = d1;
                    }
                    if (cnt !== 1) break;
                    d = dd;
                }
            }
        }
    }

    function YinyangAssist() {
        let add_color = function (c, color) {
            if (c === undefined || c.isnull || c.anum !== CANUM.none || color !== CANUM.wcir && color !== CANUM.bcir) { return; }
            if (step && flg) { return; }
            flg = true;
            c.setAnum(color);
            c.draw();
        };
        let add_black = function (c) {
            add_color(c, CANUM.bcir);
        };
        let add_white = function (c) {
            add_color(c, CANUM.wcir);
        };
        CellConnected(
            function (c) { return c.anum === CANUM.wcir; },
            function (c) { return c.anum === CANUM.bcir; },
            add_white,
            add_black,
        );
        CellConnected(
            function (c) { return c.anum === CANUM.bcir; },
            function (c) { return c.anum === CANUM.wcir; },
            add_black,
            add_white,
        );
        No2x2Cell(
            function (c) { return c.anum === CANUM.wcir; },
            add_black
        );
        No2x2Cell(
            function (c) { return c.anum === CANUM.bcir; },
            add_white
        );
        //cell at side is grouped when both sides are even
        if (board.rows % 2 === 0 && board.cols % 2 === 0) {
            for (let i = 1; i + 1 < board.rows; i += 2) {
                let cell1 = board.getc(board.minbx + 1, 2 * i + 1);
                let cell2 = board.getc(board.minbx + 1, 2 * i + 3);
                if (cell1.anum !== CANUM.none || cell2.anum !== CANUM.none) {
                    add_color(cell1, cell2.anum);
                    add_color(cell2, cell1.anum);
                }
                cell1 = board.getc(board.maxbx - 1, 2 * i + 1);
                cell2 = board.getc(board.maxbx - 1, 2 * i + 3);
                if (cell1.anum !== CANUM.none || cell2.anum !== CANUM.none) {
                    add_color(cell1, cell2.anum);
                    add_color(cell2, cell1.anum);
                }
            }
            for (let i = 1; i + 1 < board.cols; i += 2) {
                let cell1 = board.getc(2 * i + 1, board.minby + 1);
                let cell2 = board.getc(2 * i + 3, board.minby + 1);
                if (cell1.anum !== CANUM.none || cell2.anum !== CANUM.none) {
                    add_color(cell1, cell2.anum);
                    add_color(cell2, cell1.anum);
                }
                cell1 = board.getc(2 * i + 1, board.maxby - 1);
                cell2 = board.getc(2 * i + 3, board.maxby - 1);
                if (cell1.anum !== CANUM.none || cell2.anum !== CANUM.none) {
                    add_color(cell1, cell2.anum);
                    add_color(cell2, cell1.anum);
                }
            }
        }
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qnum !== CQNUM.none) {
                add_color(cell, cell.qnum);
            }
            //WbB
            //W.W
            if (cell.anum === CANUM.none) {
                for (let d = 0; d < 4; d++) {
                    let templist = [offset(cell, 1, -1, d), offset(cell, 1, 1, d), offset(cell, 0, -1, d), offset(cell, 0, 1, d)];
                    if (!templist.some(c => c.isnull || c.anum === CANUM.none) &&
                        templist[0].anum === templist[1].anum && templist[2].anum !== templist[3].anum) {
                        add_color(cell, CANUM.bcir + CANUM.wcir - templist[0].anum);
                    }
                }
            }
            //checker pattern
            if (cell.anum === CANUM.none) {
                let fn = function (c, c1, c2, c12) {
                    if (c1.isnull || c2.isnull || c12.isnull) { return; }
                    if (c1.anum === CANUM.none || c2.anum === CANUM.none || c12.anum === CANUM.none) { return; }
                    if (c1.anum === c2.anum && c1.anum !== c12.anum) {
                        add_color(c, c1.anum);
                    }
                };
                for (let d = 0; d < 4; d++) {
                    fn(cell, offset(cell, 1, 0, d), offset(cell, 0, 1, d), offset(cell, 1, 1, d));
                }
            }
        }
        //outside
        {
            let firstcell = board.cell[0];
            let cellList = [];
            for (let j = 0; j < board.rows; j++) { cellList.push(offset(firstcell, 0, j)); }
            for (let i = 1; i < board.cols - 1; i++) { cellList.push(offset(firstcell, i, board.rows - 1)); }
            for (let j = board.rows - 1; j >= 0; j--) { cellList.push(offset(firstcell, board.cols - 1, j)); }
            for (let i = board.cols - 2; i > 0; i--) { cellList.push(offset(firstcell, i, 0)); }
            let len = cellList.length;
            if (cellList.some(c => c.anum === CANUM.bcir) && cellList.some(c => c.anum === CANUM.wcir)) {
                for (let i = 0; i < len; i++) {
                    if (cellList[i].anum === CANUM.none || cellList[(i + 1) % len].anum !== CANUM.none) { continue; }
                    for (let j = (i + 1) % len; j != i; j = (j + 1) % len) {
                        if (cellList[j].anum === CANUM.bcir + CANUM.wcir - cellList[i].anum) { break; }
                        if (cellList[j].anum === CANUM.none) { continue; }
                        if (cellList[j].anum === cellList[i].anum) {
                            for (let k = i; k != j; k = (k + 1) % len) {
                                add_color(cellList[k], cellList[i].anum);
                            }
                        }
                    }
                }
            }
        }
    }

    function NurimazeAssist() {
        No2x2Block();
        No2x2Green();
        CellConnected(
            function (c) { return c.qsub === CQSUB.green; },
            function (c) { return c.qans === CQANS.block; },
            add_green,
            add_block,
            function (c, list) {
                let clist = c.room.clist;
                for (let i = 0; i < clist.length; i++) {
                    list.push(clist[i]);
                }
                return;
            },
        );
        CellConnected(
            function (c) {
                let startcell = board.getc(board.startpos.bx, board.startpos.by);
                let goalcell = board.getc(board.goalpos.bx, board.goalpos.by);
                return c === startcell || c === goalcell || c.ques === CQUES.cir;
            },
            function (c) { return c.qans === CQANS.block || c.ques === CQUES.tri; },
            add_green,
            function () { },
            function (c, list) {
                list = [];
                let clist = c.room.clist;
                for (let i = 0; i < clist.length; i++) {
                    list.push(clist[i]);
                }
                return;
            },
        );
        let startcell = board.getc(board.startpos.bx, board.startpos.by);
        let goalcell = board.getc(board.goalpos.bx, board.goalpos.by);
        let cirnum = 0;
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            cirnum += cell.ques === CQUES.cir;
        }
        for (let i = 0; i < board.roommgr.components.length; i++) {
            let room = board.roommgr.components[i];
            let cellList = [];
            for (let j = 0; j < room.clist.length; j++) {
                cellList.push(room.clist[j]);
            }
            if (cellList.some(c => c.qsub === CQSUB.green || c.ques === CQUES.cir || c.ques === CQUES.tri || c.lcnt > 0) ||
                room === startcell.room || room === goalcell.room) {
                cellList.forEach(c => add_green(c));
                continue;
            }
            if (cellList.some(c => c.qans === CQANS.block)) {
                cellList.forEach(c => add_block(c));
                continue;
            }
            let cirnum1 = cirnum, cirnum2 = cirnum;
            let templist = [];
            cellList.forEach(c => {
                let fn = function (c) {
                    if (c.isnull || templist.indexOf(c) !== -1) { return; }
                    templist.push(c);
                }
                let list = [offset(c, -1, 0), offset(c, 0, -1), offset(c, 0, 1), offset(c, 1, 0)];
                list.forEach(c => fn(c));
            });
            templist = templist.filter(c => !c.isnull && c.qsub === CQSUB.green);
            if (templist.length < 2) { continue; }
            //no loop
            let fn1 = function (c) {
                let dfslist = [];
                let dfs = function (c) {
                    if (c.isnull || c.qsub !== CQSUB.green || dfslist.indexOf(c) !== -1) { return; }
                    dfslist.push(c);
                    fourside(dfs, c.adjacent);
                };
                dfs(c);
                if (dfslist.some(c => c === startcell)) {
                    cirnum1 = dfslist.filter(c => c.ques === CQUES.cir).length;
                }
                if (dfslist.some(c => c === goalcell)) {
                    cirnum2 = dfslist.filter(c => c.ques === CQUES.cir).length;
                }
                return dfslist.filter(c => templist.indexOf(c) !== -1).length;
            };
            let templist2 = templist.map(c => fn1(c));
            if (templist2.some(n => n > 1)) {
                cellList.forEach(c => add_block(c));
                continue;
            }
            //not enough cir
            if (cirnum1 + cirnum2 < cirnum) {
                cellList.forEach(c => add_block(c));
                continue;
            }
            //no branch for line
            let fn2 = function (c) {
                let res = 0;
                let dfslist = [];
                let dfs = function (c) {
                    if (c.isnull || c.qsub !== CQSUB.green || dfslist.indexOf(c) !== -1) { return; }
                    if (c === startcell || c === goalcell || c.ques === CQUES.cir || c.lcnt > 0) {
                        res += c === startcell;
                        res += c === goalcell;
                        res += (c.ques === CQUES.cir && c.lcnt === 0);
                        res += c.lcnt;
                        res += (dfslist.some(c => c.ques === CQUES.tri) ? 2 : 0);
                        return;
                    }
                    dfslist.push(c);
                    fourside(dfs, c.adjacent);
                    dfslist.pop();
                }
                dfs(c);
                return Math.min(res, 2);
            }
            templist2 = templist.map(c => fn2(c));
            if (templist2.reduce(function (a, b) { return a + b; }) > 2) {
                cellList.forEach(c => add_block(c));
                continue;
            }
        }
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.ques === CQUES.cir) {
                let templist = [offset(cell, -1, 0), offset(cell, 1, 0), offset(cell, 0, -1), offset(cell, 0, 1)];
                templist = templist.filter(c => !c.isnull && c.qans !== CQANS.block);
                if (templist.length === 2) {
                    templist.forEach(c => add_green(c));
                }
            }
            //surrounded by block
            {
                let templist = [offset(cell, -1, 0), offset(cell, 1, 0), offset(cell, 0, -1), offset(cell, 0, 1)];
                if (templist.filter(c => c.isnull || c.qans === CQANS.block).length === 4) {
                    add_block(cell);
                }
            }
            //no 2*2
            {
                let templist = [cell, offset(cell, 1, 0), offset(cell, 0, 1), offset(cell, 1, 1)];
                if (!templist.some(c => c.isnull) && !templist.some(c => c.qsub === CQSUB.green)) {
                    let templist2 = templist.filter(c => !c.qans);
                    if (templist2.length > 0 && !templist2.some(c => c.room !== templist2[0].room)) {
                        add_green(templist2[0]);
                    }
                }
                if (!templist.some(c => c.qans)) {
                    let templist2 = templist.filter(c => c.qsub !== CQSUB.green);
                    if (templist2.length > 0 && !templist2.some(c => c.room !== templist2[0].room)) {
                        add_block(templist2[0]);
                    }
                }
            }
        }
        //line
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let adjcell = cell.adjacent;
            let adjline = cell.adjborder;
            if (cell.qans === CQANS.block || cell.ques === CQUES.tri) {
                fourside(add_cross, adjline);
            }
            if (cell.qans !== CQANS.block) {
                let emptynum = 0;
                let linenum = 0;
                let fn = function (c, b) {
                    if (!c.isnull && b.qsub !== BQSUB.cross) { emptynum++; }
                    linenum += b.line;
                };
                fourside2(fn, adjcell, adjline);
                if (linenum > 0) {
                    add_green(cell);
                }
                //no branch
                if (linenum === 2 || linenum === 1 && (cell === startcell || cell === goalcell)) {
                    fourside(add_cross, adjline);
                }
                //no deadend
                if (emptynum === 1) {
                    if (cell !== startcell && cell !== goalcell) {
                        fourside(add_cross, adjline);
                    } else {
                        let fn = function (c, b) {
                            if (!c.isnull && b.qsub !== BQSUB.cross) {
                                add_line(b);
                            }
                        }
                        fourside2(fn, adjcell, adjline);
                    }
                }
                //2 degree path
                if (emptynum === 2 && cell !== startcell && cell !== goalcell && (linenum === 1 || cell.ques === CQUES.cir)) {
                    let fn = function (c, b) {
                        add_line(b);
                        if (!b.isnull && b.line) {
                            add_green(c);
                        }
                    };
                    fourside2(fn, adjcell, adjline);
                }
                //extend line
                emptynum = 0;
                linenum = 0;
                fourside2(fn, adjcell, adjline);
                if (linenum === 1 && cell !== startcell && cell !== goalcell ||
                    linenum === 0 && (cell === startcell || cell === goalcell || cell.ques === CQUES.cir)) {
                    let fn = function (c, b, list) {
                        if (c.isnull || c.qsub !== CQSUB.green || list.indexOf(c) !== -1) { return; }
                        if (b !== null && b.line) { return; }
                        list.push(c);
                        if (c.lcnt === 1 || c.ques === CQUES.cir || c === startcell || c === goalcell) {
                            for (let j = 1; j < list.length; j++) {
                                let cell1 = list[j - 1];
                                let cell2 = list[j];
                                let border = board.getb((cell1.bx + cell2.bx) / 2, (cell1.by + cell2.by) / 2);
                                add_line(border);
                                add_green(cell1);
                                add_green(cell2);
                            }
                        }
                        fn(c.adjacent.top, c.adjborder.top, list);
                        fn(c.adjacent.bottom, c.adjborder.bottom, list);
                        fn(c.adjacent.left, c.adjborder.left, list);
                        fn(c.adjacent.right, c.adjborder.right, list);
                        list.pop();
                    }
                    fn(cell, null, []);
                }
            }
        }
    }

    function AquapelagoAssist() {
        No2x2Green();
        BlockNotAdjacent();
        GreenConnectedInCell();
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            if (cell.qnum !== CQNUM.none) {
                add_block(cell);
            }
            if (cell.qnum > 0) {
                let templist = [];
                let fn = function (c) {
                    if (c.qans !== CQANS.block) { return; }
                    if (templist.indexOf(c) !== -1) { return; }
                    templist.push(c);
                    fn(offset(c, -1, -1));
                    fn(offset(c, -1, +1));
                    fn(offset(c, +1, -1));
                    fn(offset(c, +1, +1));
                }
                fn(cell);
                if (templist.length === cell.qnum) {
                    templist.forEach(c => {
                        add_green(offset(c, -1, -1));
                        add_green(offset(c, -1, +1));
                        add_green(offset(c, +1, -1));
                        add_green(offset(c, +1, +1));
                    });
                }
            }
        }
    }

    function IcebarnAssist() {
        SingleLoopInCell();
        //add cross outside except IN and OUT
        {
            let inp = [board.arrowin.bx, board.arrowin.by];
            let outp = [board.arrowout.bx, board.arrowout.by];
            add_line(board.getb(inp[0], inp[1]));
            add_line(board.getb(outp[0], outp[1]));
            let minbx = board.minbx + 2;
            let minby = board.minby + 2;
            let maxbx = board.maxbx - 2;
            let maxby = board.maxby - 2;
            for (let j = minbx + 1; j < maxbx; j += 2) {
                add_cross(board.getb(j, minby));
                add_cross(board.getb(j, maxby));
            }
            for (let j = minby + 1; j < maxby; j += 2) {
                add_cross(board.getb(minbx, j));
                add_cross(board.getb(maxbx, j));
            }
        }
        for (let i = 0; i < board.border.length; i++) {
            let border = board.border[i];
            if (border.qdir != QDIR.none) {
                add_arrow(border, border.qdir + 10);   //from qdir to bqsub
                add_line(border);
            }
        }
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let adjline = cell.adjborder;
            if (cell.ques === CQUES.ice) {
                for (let d = 0; d < 4; d++) {
                    if (dir(adjline, d).qsub === BQSUB.cross) {
                        add_cross(dir(adjline, d + 2));
                    }
                    if (dir(adjline, d).line) {
                        add_line(dir(adjline, d + 2));
                        if (dir(adjline, d).qsub !== BQSUB.none) {
                            add_arrow(dir(adjline, d + 2), dir(adjline, d).qsub);
                        }
                    }
                }
            }
            if (cell.lcnt === 2 && cell.ques !== CQUES.ice) {
                let templist = [[adjline.top, BQSUB.arrow_up, BQSUB.arrow_dn], [adjline.bottom, BQSUB.arrow_dn, BQSUB.arrow_up],
                [adjline.left, BQSUB.arrow_lt, BQSUB.arrow_rt], [adjline.right, BQSUB.arrow_rt, BQSUB.arrow_lt]];
                templist = templist.filter(b => b[0].line);
                if (templist.filter(b => b[0].qsub === BQSUB.none).length === 1) {
                    if (templist[0][0].qsub !== BQSUB.none) {
                        templist = [templist[1], templist[0]];
                    }
                    if (templist[1][0].qsub === templist[1][1]) {
                        add_arrow(templist[0][0], templist[0][2]);
                    }
                    if (templist[1][0].qsub === templist[1][2]) {
                        add_arrow(templist[0][0], templist[0][1]);
                    }
                }
            }
            if (cell.lcnt === 1 && cell.ques !== CQUES.ice) {
                for (let d = 0; d < 4; d++) {
                    let ncell = dir(cell.adjacent, d);
                    while (!ncell.isnull && ncell.ques === CQUES.ice) {
                        ncell = dir(ncell.adjacent, d);
                    }
                    if (ncell.isnull || ncell.lcnt !== 1 || dir(ncell.adjborder, d + 2).line) { continue; }
                    let fn = function (c) {
                        let adjline = c.adjborder;
                        let templist = [[adjline.top, BQSUB.arrow_up, BQSUB.arrow_dn], [adjline.bottom, BQSUB.arrow_dn, BQSUB.arrow_up],
                        [adjline.left, BQSUB.arrow_lt, BQSUB.arrow_rt], [adjline.right, BQSUB.arrow_rt, BQSUB.arrow_lt]];
                        templist = templist.filter(b => b[0].line);
                        if (templist.length !== 1) { return 0; }
                        if (templist[0][0].qsub === templist[0][1]) { return 1; }
                        if (templist[0][0].qsub === templist[0][2]) { return 2; }
                        return 0;
                    }
                    if (fn(cell) && fn(cell) === fn(ncell)) {
                        add_cross(dir(adjline, d));
                    }
                }
            }
        }
    }

    function LitsAssist() {
        No2x2Block();
        for (let i = 0; i < board.roommgr.components.length; i++) {
            let room = board.roommgr.components[i];
            let templist = [];
            for (let j = 0; j < room.clist.length; j++) {
                templist.push(room.clist[j]);
            }
            if (templist.filter(c => c.qsub !== CQSUB.dot).length === 4) {
                templist.forEach(c => add_block(c));
            }
            if (templist.filter(c => c.qans === CQANS.block).length === 4) {
                templist.forEach(c => add_dot(c));
            }
            for (let j = 0; j < room.clist.length; j++) {
                if (room.clist[j].qsub === CQSUB.dot) { continue; }
                let templist2 = [];
                let fn = function (c) {
                    if (c.room !== room || c.qsub === CQSUB.dot || templist2.indexOf(c) !== -1) { return; }
                    templist2.push(c);
                    fourside(fn, c.adjacent);
                }
                fn(room.clist[j]);
                if (templist2.length < 4) {
                    templist2.forEach(c => add_dot(c));
                }
                if (room.clist[j].qans !== CQANS.block) { continue; }
                templist2 = [];
                fn = function (c, step = 3) {
                    if (step < 0 || c.room !== room) { return; }
                    templist2.push(c);
                    fn(c.adjacent.top, step - 1);
                    fn(c.adjacent.bottom, step - 1);
                    fn(c.adjacent.left, step - 1);
                    fn(c.adjacent.right, step - 1);
                }
                fn(room.clist[j]);
                templist.forEach(c => {
                    if (templist2.indexOf(c) === -1) {
                        add_dot(c);
                    }
                });
            }
        }
    }

    function NothreeAssist() {
        BlockNotAdjacent();
        GreenConnectedInCell();
        for (let i = 0; i < board.dots.length; i++) {
            let dot = board.dots[i].piece;
            if (dot.qnum !== 1) { continue; }
            let cellList = [];
            if (dot.bx % 2 === 1 && dot.by % 2 === 1) {
                cellList.push(board.getc(dot.bx, dot.by));
            }
            if (dot.bx % 2 === 0 && dot.by % 2 === 1) {
                cellList.push(board.getc(dot.bx - 1, dot.by));
                cellList.push(board.getc(dot.bx + 1, dot.by));
            }
            if (dot.bx % 2 === 1 && dot.by % 2 === 0) {
                cellList.push(board.getc(dot.bx, dot.by - 1));
                cellList.push(board.getc(dot.bx, dot.by + 1));
            }
            if (dot.bx % 2 === 0 && dot.by % 2 === 0) {
                cellList.push(board.getc(dot.bx - 1, dot.by - 1));
                cellList.push(board.getc(dot.bx + 1, dot.by - 1));
                cellList.push(board.getc(dot.bx - 1, dot.by + 1));
                cellList.push(board.getc(dot.bx + 1, dot.by + 1));
            }
            let blocknum = 0;
            let emptynum = 0;
            cellList.forEach(c => {
                blocknum += c.qans === CQANS.block;
                emptynum += c.qans !== CQANS.block && c.qsub !== CQSUB.dot;
            });
            if (blocknum === 0 && emptynum === 1) {
                cellList.forEach(c => add_block(c));
            }
            if (blocknum === 1) {
                cellList.forEach(c => add_green(c));
            }
        }
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            for (let d = 0; d < 4; d++) {
                let fn = function (list) {
                    if (!list.some(c => c.isnull) && list.filter(c => c.qans === CQANS.block).length === 2) {
                        list.forEach(c => add_green(c));
                    }
                }
                //O.O.O
                fn([cell, offset(cell, 2, 0, d), offset(cell, 4, 0, d)]);
                //O..O..O
                fn([cell, offset(cell, 3, 0, d), offset(cell, 6, 0, d)]);
                //O...O...O
                fn([cell, offset(cell, 4, 0, d), offset(cell, 8, 0, d)]);
                //OXXXXOX?XXO
                for (let l = 5; l * 2 < Math.max(board.cols, board.rows); l++) {
                    let templist1 = [cell, offset(cell, l, 0, d), offset(cell, 2 * l, 0, d)];
                    if (templist1.some(c => c.isnull)) { continue; }
                    templist1 = templist1.filter(c => c.qans !== CQANS.block);
                    let templist2 = [];
                    for (let j = 1; j < 2 * l; j++) {
                        if (j === l) { continue; }
                        templist2.push(offset(cell, j, 0, d));
                    }
                    if (templist2.some(c => c.qans === CQANS.block)) { continue; }
                    templist2 = templist2.filter(c => c.qsub !== CQSUB.dot);
                    if (templist1.length === 0 && templist2.length === 1) {
                        add_block(templist2[0]);
                    }
                    if (templist1.length === 1 && templist2.length === 0) {
                        add_green(templist1[0]);
                    }
                }
            }
        }
    }

    function EkawayehAssist() {
        HeyawakeAssist();
        CellConnected(
            function (c) { return c.qsub === CQSUB.green; },
            function (c) { return c.qans === CQANS.block; },
            add_green,
            add_block,
            function (c, list) {
                list.push(c);
                let tx = c.room.clist.getRectSize().x1 + c.room.clist.getRectSize().x2;
                let ty = c.room.clist.getRectSize().y1 + c.room.clist.getRectSize().y2;
                let bc = board.getc(tx - c.bx, ty - c.by);
                if (bc !== c && c.qsub !== CQSUB.green && bc.qsub !== CQSUB.green) {
                    list.push(bc);
                }
            },
        );
        for (let i = 0; i < board.roommgr.components.length; i++) {
            let room = board.roommgr.components[i];
            let qnum = room.top.qnum;
            let rows = room.clist.getRectSize().rows;
            let cols = room.clist.getRectSize().cols;
            let tx = room.clist.getRectSize().x1 + room.clist.getRectSize().x2;
            let ty = room.clist.getRectSize().y1 + room.clist.getRectSize().y2;
            if (rows % 2 === 1 && cols % 2 === 0) {
                add_green(board.getc(tx / 2 - 1, ty / 2));
                add_green(board.getc(tx / 2 + 1, ty / 2));
            }
            if (rows % 2 === 0 && cols % 2 === 1) {
                add_green(board.getc(tx / 2, ty / 2 - 1));
                add_green(board.getc(tx / 2, ty / 2 + 1));
            }
            if (rows % 2 === 1 && cols % 2 === 1) {
                if (qnum >= 0 && qnum % 2 === 0) {
                    add_green(board.getc(tx / 2, ty / 2));
                }
                if (qnum >= 0 && qnum % 2 === 1) {
                    add_block(board.getc(tx / 2, ty / 2));
                }
            }
            for (let j = 0; j < room.clist.length; j++) {
                let cell = room.clist[j];
                if (cell.qsub === CQSUB.green) {
                    add_green(board.getc(tx - cell.bx, ty - cell.by));
                }
                if (cell.qans === CQANS.block) {
                    add_block(board.getc(tx - cell.bx, ty - cell.by));
                }
            }
        }
    }

    function ShakashakaAssist() {
        let isEmpty = function (c) {
            return !c.isnull && c.qnum === CQNUM.none && c.qsub === CQSUB.none && c.qans === CQANS.none;
        };
        //draw triangle
        let add_triangle = function (c, ndir) { //0 = bl, 1 = br, 2 = tr, 3 = tl
            if (c === undefined || c.isnull || !isEmpty(c)) { return; }
            if (step && flg) { return; }
            flg = true;
            ndir = (ndir % 4 + 4) % 4;
            c.setQans(ndir + 2);
            c.draw();
        };
        //check blocking edge
        let isEdge = function (c, ndir) { //0 = left, 1 = bottom, 2 = right, 3 = top
            ndir = (ndir % 4 + 4) % 4;
            let tdir = (ndir + 3) % 4;
            return c.isnull || c.qnum !== CQNUM.none || c.qans === tdir + 2 || c.qans === ndir + 2;
        };
        //check dot area if stick edge
        let isDotEdge = function (c) {
            if (c.qsub !== CQSUB.dot) { return false; }
            let temp = false;
            let dfslist = [c];
            let dfs = function (c, ndir) {
                if (isEmpty(c) || dfslist.indexOf(c) !== -1) { return; }
                if (isEdge(c, ndir + 2)) { temp = true; return; }
                if (c.qsub === CQSUB.dot) {
                    dfslist.push(c);
                    dfs(offset(c, -1, 0), 0);
                    dfs(offset(c, 0, 1), 1);
                    dfs(offset(c, 1, 0), 2);
                    dfs(offset(c, 0, -1), 3);
                }
            };
            for (let d = 0; d < 4; d++) {
                dfs(dir(c.adjacent, d + 1), d);
            }
            return temp;
        };
        //check blocking edge including dot
        let isEdgeEx = function (c, ndir) { //0 = left, 1 = bottom, 2 = right, 3 = top
            return isEdge(c, ndir) || isDotEdge(c);
        };
        //check blocking corner including dot
        let isCorner = function (c, ndir) { //0 = bl, 1 = br, 2 = tr, 3 = tl
            ndir = (ndir % 4 + 4) % 4;
            return c.isnull || c.qnum !== CQNUM.none || c.qans === ndir + 2 || isDotEdge(c);
        };
        //check blocking sharp including dot
        let isSharp = function (c, ndir) { //0 = bl, 1 = br, 2 = tr, 3 = tl
            ndir = (ndir % 4 + 4) % 4;
            return isEdgeEx(c, ndir) || c.qans === (ndir + 1) % 4 + 2;
        };
        //if can place triangle of specific direction
        let isntTri = function (c, ndir) { //0 = bl, 1 = br, 2 = tr, 3 = tl
            ndir = (ndir % 4 + 4) % 4;
            if (!isEmpty(c) && c.qans !== ndir + 2) { return true; }
            if (c.qans === ndir + 2) { return false; }
            let temp = offset(c, 1, 0, ndir);
            if (isEdgeEx(temp, ndir) || temp.qans === (ndir + 2) % 4 + 2) { return true; }
            temp = offset(c, 0, -1, ndir);
            if (isEdgeEx(temp, ndir + 1) || temp.qans === (ndir + 2) % 4 + 2) { return true; }
            temp = offset(c, -1, 0, ndir);
            if (!temp.isnull && (temp.qans === (ndir + 3) % 4 + 2 || temp.qans === ndir + 2)) { return true; }
            temp = offset(c, 0, 1, ndir);
            if (!temp.isnull && (temp.qans === ndir + 2 || temp.qans === (ndir + 1) % 4 + 2)) { return true; }
            temp = offset(c, 1, -1, ndir);
            if (!temp.isnull && isSharp(temp, ndir)) { return true; }
            temp = offset(c, -1, -1, ndir);
            if (!temp.isnull && temp.qans === (ndir + 3) % 4 + 2) { return true; }
            temp = offset(c, -1, 1, ndir);
            if (!temp.isnull && temp.qans === ndir + 2) { return true; }
            temp = offset(c, 1, 1, ndir);
            if (!temp.isnull && temp.qans === (ndir + 1) % 4 + 2) { return true; }
            temp = offset(c, 2, 0, ndir);
            if (!temp.isnull && temp.qans === (ndir + 1) % 4 + 2) { return true; }
            temp = offset(c, 0, -2, ndir);
            if (!temp.isnull && temp.qans === (ndir + 3) % 4 + 2) { return true; }
            temp = offset(c, 2, -1, ndir);
            if (!temp.isnull && temp.qans === (ndir + 2) % 4 + 2) { return true; }
            temp = offset(c, 1, -2, ndir);
            if (!temp.isnull && temp.qans === (ndir + 2) % 4 + 2) { return true; }
            return false;
        };
        //extend of isntTri including some complex logic
        let isntTriEx = function (c, ndir) { //0 = bl, 1 = br, 2 = tr, 3 = tl
            if (isntTri(c, ndir)) { return true; }
            let templist = [offset(c, -1, 0, ndir), offset(c, 0, 1, ndir), offset(c, -2, 0, ndir),
            offset(c, 0, 2, ndir), offset(c, -1, 1, ndir)];
            if (isEmpty(templist[0]) && isEmpty(templist[1]) && isEdgeEx(templist[2], ndir + 2) &&
                isEdgeEx(templist[3], ndir + 3) && isEmpty(templist[4]) && isntTri(templist[4], ndir + 2)) {
                return true;
            }
            return false;
        }

        //start assist
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let adjcell = cell.adjacent;
            let trinum = 0;
            let emptynum = 0;
            let fn = function (c) {
                if (!c.isnull && c.qans >= 2) { trinum++; }
                if (isEmpty(c)) { emptynum++; }
            };
            fourside(fn, adjcell);

            //add dot

            //cannot place any triangle
            {
                let temp = true;
                for (let d = 0; d < 4; d++) {
                    temp &= isntTriEx(cell, d);
                }
                if (temp) { add_dot(cell); }
            }
            //fill rectangle
            {
                let templist = [cell, offset(cell, 1, 0), offset(cell, 0, 1), offset(cell, 1, 1)];
                if (!templist.some(c => c.isnull)) {
                    templist = templist.filter(c => c.qsub !== CQSUB.dot);
                    if (templist.length === 1) {
                        add_dot(templist[0]);
                    }
                }
            }
            //dot by clue
            if (trinum === cell.qnum) {
                fourside(add_dot, adjcell);
            }
            //pattern with clue 1
            if (cell.qnum === 1) {
                for (let d = 0; d < 4; d++) {
                    let tempcell = offset(cell, -1, 0, d);
                    let tempstate = !tempcell.isnull && (isEmpty(tempcell) || tempcell.qsub === CQSUB.dot);
                    tempcell = offset(cell, 0, 1, d);
                    tempstate &= !tempcell.isnull && (isEmpty(tempcell) || tempcell.qsub === CQSUB.dot);
                    tempcell = offset(cell, -1, 1, d);
                    tempstate &= tempcell.qnum === CQNUM.none && isntTriEx(tempcell, d + 2);
                    if (tempstate) { add_dot(offset(cell, 1, 0, d)); add_dot(offset(cell, 0, -1, d)); }
                }
            }

            //add triangle

            //cannot form non-rectangle
            if (isEmpty(cell)) {
                for (let d = 0; d < 4; d++) {
                    let templist = [offset(cell, -1, 0, d), offset(cell, 0, 1, d), offset(cell, -1, 1, d)];
                    let templist_dot = templist.filter(c => !c.isnull && !isEmpty(c) && c.qsub === CQSUB.dot);
                    let templist_ndot = templist.filter(c => !c.isnull && !isEmpty(c) && c.qsub !== CQSUB.dot);
                    if (templist_dot.length === 2 && templist_ndot.length === 1) {
                        let temp = templist_ndot[0];
                        if (templist.indexOf(temp) === 0 && isCorner(temp, d + 1)) { add_triangle(cell, d); break; }
                        else if (templist.indexOf(temp) === 1 && isCorner(temp, d + 3)) { add_triangle(cell, d); break; }
                        else if (templist.indexOf(temp) === 2 && isCorner(temp, d + 2)) { add_triangle(cell, d); break; }
                    }
                }
            }
            //triangle by clue
            if (emptynum === cell.qnum - trinum) {
                for (let d = 0; d < 4; d++) {
                    let adj = dir(adjcell, d);
                    if (isEmpty(adj)) {
                        if (isntTriEx(adj, d)) { add_triangle(adj, d + 1); }
                        else if (isntTriEx(adj, d + 1)) { add_triangle(adj, d); }
                    }
                }
            }
            //side extend
            if (cell.qans >= 2) {
                let ndir = cell.qans - 2;
                //rectangle needs turn or cannot turn
                if (isntTriEx(offset(cell, -1, -1, ndir), ndir)) { add_triangle(offset(cell, 0, -1, ndir), ndir + 3); }
                else if (isntTriEx(offset(cell, 0, -1, ndir), ndir + 3)) { add_triangle(offset(cell, -1, -1, ndir), ndir); }
                if (isntTriEx(offset(cell, 1, 1, ndir), ndir)) { add_triangle(offset(cell, 1, 0, ndir), ndir + 1); }
                else if (isntTriEx(offset(cell, 1, 0, ndir), ndir + 1)) { add_triangle(offset(cell, 1, 1, ndir), ndir); }
                //only one opposite side position
                if (isEdgeEx(offset(cell, 2, -1, ndir), ndir)) { add_triangle(offset(cell, 1, -1, ndir), ndir + 2); }
                else if (isEdgeEx(offset(cell, 1, -2, ndir), ndir + 1)) { add_triangle(offset(cell, 1, -1, ndir), ndir + 2); }
                else if (isSharp(offset(cell, 2, -2, ndir), ndir)) { add_triangle(offset(cell, 1, -1, ndir), ndir + 2); }
                //rectangle opposite side extend
                let temp = cell;
                while (!offset(temp, -1, -1, ndir).isnull && offset(temp, -1, -1, ndir).qans === cell.qans) {
                    temp = offset(temp, -1, -1, ndir);
                }
                let turn1 = offset(temp, 0, -1, ndir);
                if (turn1.qans === (ndir + 3) % 4 + 2) {
                    let temp = cell;
                    while (!offset(temp, 1, 1, ndir).isnull && offset(temp, 1, 1, ndir).qans === cell.qans) {
                        temp = offset(temp, 1, 1, ndir);
                    }
                    let turn2 = offset(temp, 1, 0, ndir);
                    if (turn2.qans === (ndir + 1) % 4 + 2) {
                        turn1 = offset(turn1, 1, -1, ndir);
                        turn2 = offset(turn2, 1, -1, ndir);
                        while ((!turn1.isnull && turn1.qans === (ndir + 3) % 4 + 2) ||
                            (!turn2.isnull && turn2.qans === (ndir + 1) % 4 + 2)) {
                            add_triangle(turn1, ndir + 3);
                            add_triangle(turn2, ndir + 1);
                            turn1 = offset(turn1, 1, -1, ndir);
                            turn2 = offset(turn2, 1, -1, ndir);
                        }
                    }
                }
            }
            //2x2 pattern
            if (isEmpty(cell)) {
                for (let d = 0; d < 4; d++) {
                    let templist = [offset(cell, -1, 0, d), offset(cell, 0, -1, d), offset(cell, 0, 1, d),
                    offset(cell, -1, -1, d), offset(cell, 0, -2, d)];
                    if (!templist[0].isnull && templist[0].qsub === CQSUB.dot && isEmpty(templist[1]) &&
                        isEdge(templist[2], d + 3) && isEdge(templist[3], d + 1) && isEdgeEx(templist[4], d + 1)) {
                        add_triangle(cell, d);
                        break;
                    }
                    templist = [offset(cell, 0, 1, d), offset(cell, 1, 0, d), offset(cell, -1, 0, d),
                    offset(cell, 1, 1, d), offset(cell, 2, 0, d)];
                    if (!templist[0].isnull && templist[0].qsub === CQSUB.dot && isEmpty(templist[1]) &&
                        isEdge(templist[2], d + 2) && isEdge(templist[3], d) && isEdgeEx(templist[4], d)) {
                        add_triangle(cell, d);
                        break;
                    }
                }
            }
        }
    }

    function HeyawakeAssist() {
        GreenConnectedInCell();
        BlockNotAdjacent();
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let adjcell = cell.adjacent;
            let blocknum = 0;
            let fn = function (c) {
                blocknum += c.isnull || c.qans === CQANS.block;
            };
            fourside(fn, adjcell);
            //no two facing doors
            for (let d = 0; d < 4; d++) {
                if (cell.qsub !== CQSUB.green) { break; }
                let pcell = dir(cell.adjacent, d);
                let bordernum = 0;
                let emptycellList = [cell];
                while (!pcell.isnull && pcell.qans !== CQANS.block && bordernum < 2) {
                    if (dir(pcell.adjborder, d + 2).ques) {
                        bordernum++;
                    }
                    emptycellList.push(pcell);
                    pcell = dir(pcell.adjacent, d);
                }
                emptycellList = emptycellList.filter(c => c.qsub !== CQSUB.green);
                if (bordernum === 2 && emptycellList.length === 1) {
                    add_block(emptycellList[0]);
                }
            }
        }
        const MAXSIT = 10;
        const MAXAREA = 100;
        for (let i = 0; i < board.roommgr.components.length; i++) {
            let room = board.roommgr.components[i];
            let qnum = room.top.qnum;
            if (qnum === CQNUM.none || qnum === CQNUM.quesmark) { continue; }
            let list = [];
            let surlist = [];
            let sitnum = 0;
            let cst = new Map();
            let apl = new Map();
            for (let j = 0; j < room.clist.length; j++) {
                let cell = room.clist[j];
                list.push(cell);
                cst.set(cell, (cell.qans === CQANS.block ? "BLK" : (cell.qsub === CQSUB.green ? "GRN" : "UNK")));
                apl.set(cell, (cell.qans === CQANS.block ? "BLK" : (cell.qsub === CQSUB.green ? "GRN" : "UNK")));
            }
            if (qnum === list.filter(c => c.qans === CQANS.block).length) {
                list.forEach(c => add_green(c));
                continue;
            }
            //randomly chosen approximate formula
            if (list.length > MAXAREA &&
                (qnum - list.filter(c => c.qans === CQANS.block).length + 1) < list.filter(c => c.qans === CQANS.none && c.qsub === CQSUB.none).length) { continue; }
            if ((qnum - list.filter(c => c.qans === CQANS.block).length) * 2 + 5 <
                list.filter(c => c.qans === CQANS.none && c.qsub === CQSUB.none).length) { continue; }
            list.forEach(c => {
                let templist = [offset(c, -1, 0), offset(c, 0, -1), offset(c, 1, 0), offset(c, 0, 1)];
                templist.forEach(c => {
                    if (c.isnull || c.room === room || surlist.indexOf(c) !== -1) { return; }
                    if (c.qsub === CQSUB.green || c.qans === CQANS.block) { return; }
                    surlist.push(c);
                    apl.set(c, "GRN");
                });
            });
            let dfs = function (i, blknum) {
                if (sitnum > MAXSIT) { return; }
                if (i === list.length) {
                    if (blknum !== qnum) { return; }
                    let templist = [];
                    let templist2 = [];
                    if (list.some(c => {
                        if (cst.get(c) === "BLK") { return false; }
                        if (templist.indexOf(c) !== -1) { return false; }
                        let n = 0;
                        let olist = [];
                        let dfs = function (c) {
                            if (c.isnull || templist.indexOf(c) !== -1) { return false; }
                            if (c.room !== room) {
                                if (c.qans === CQANS.block) { return false; }
                                olist.push(c);
                                return true;
                            }
                            if (cst.get(c) === "BLK") { return false; }
                            templist.push(c);
                            n++;
                            let res = 0;
                            res |= dfs(offset(c, -1, 0));
                            res |= dfs(offset(c, 0, -1));
                            res |= dfs(offset(c, 1, 0));
                            res |= dfs(offset(c, 0, 1));
                            return res;
                        };
                        let res = dfs(c);
                        if (olist.length === 1) { templist2.push(olist[0]); }
                        if (!res && n + qnum < list.length) { return true; }
                        return false;
                    })) { return; };
                    list.forEach(c => {
                        if (apl.get(c) !== "UNK" && apl.get(c) !== cst.get(c)) { apl.set(c, "AMB"); }
                        if (apl.get(c) === "UNK") { apl.set(c, cst.get(c)); }
                    });
                    surlist.forEach(c => {
                        if (templist2.indexOf(c) !== -1) { return; }
                        let templist = [offset(c, -1, 0), offset(c, 0, -1), offset(c, 1, 0), offset(c, 0, 1)];
                        if (templist.some(c => !c.isnull && c.room === room && cst.get(c) === "BLK")) { return; }
                        apl.set(c, "AMB");
                    });
                    sitnum++;
                    return;
                }
                if (cst.get(list[i]) !== "UNK") { dfs(i + 1, blknum); return; }
                let templist = [offset(list[i], -1, 0), offset(list[i], 0, -1), offset(list[i], 1, 0), offset(list[i], 0, 1)];
                if (blknum < qnum && !templist.some(c => !c.isnull && c.qans === CQANS.block || cst.has(c) && cst.get(c) === "BLK")) {
                    cst.set(list[i], "BLK");
                    dfs(i + 1, blknum + 1);
                    cst.set(list[i], "UNK");
                }
                cst.set(list[i], "GRN");
                dfs(i + 1, blknum);
                cst.set(list[i], "UNK");
            };
            dfs(0, list.filter(c => c.qans === CQANS.block).length);
            if (sitnum > MAXSIT) { continue; }
            list.forEach(c => {
                if (apl.get(c) === "BLK") {
                    add_block(c);
                }
                if (apl.get(c) === "GRN") {
                    add_green(c);
                }
            });
            surlist.forEach(c => {
                if (apl.get(c) === "GRN") {
                    add_green(c);
                }
            });
        }
    }

    function AkariAssist() {
        let isEmpty = function (c) { return !c.isnull && c.qnum === CQNUM.none && c.qans !== CQANS.light && c.qsub !== CQSUB.dot; };
        let isNotLight = function (c) { return c.isnull || c.qnum !== CQNUM.none || c.qsub === CQSUB.dot; }
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let adjcell = cell.adjacent;
            let emptynum = 0;
            let lightnum = 0;
            //add dot where lighted
            if (cell.qlight && cell.qans !== CQANS.light) {
                add_dot(cell);
            }
            //only one place can light
            if (cell.qnum === CQNUM.none && !cell.qlight) {
                let emptycellList = [];
                if (cell.qsub !== CQSUB.dot) {
                    emptycellList.push(cell);
                }
                for (let d = 0; d < 4; d++) {
                    let pcell = dir(cell.adjacent, d);
                    while (!pcell.isnull && pcell.qnum === CQNUM.none) {
                        emptycellList.push(pcell);
                        pcell = dir(pcell.adjacent, d);
                    }
                }
                emptycellList = emptycellList.filter(c => c.qsub !== CQSUB.dot);
                if (emptycellList.length === 1) {
                    add_light(emptycellList[0]);
                }
            }
            let fn = function (c) {
                if (!c.isnull && c.qnum === CQNUM.none && c.qsub !== CQSUB.dot && c.qans !== CQANS.light) { emptynum++; }
                lightnum += (c.qans === CQANS.light);
            };
            fourside(fn, adjcell);
            if (cell.qnum >= 0) {
                //finished clue
                if (cell.qnum === lightnum) {
                    fourside(add_dot, adjcell);
                }
                //finish clue
                if (cell.qnum === emptynum + lightnum) {
                    fourside(add_light, adjcell);
                }
                //dot at corner
                if (cell.qnum - lightnum + 1 === emptynum) {
                    for (let d = 0; d < 4; d++) {
                        if (isEmpty(offset(cell, 0, 1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, 1, d))) {
                            add_dot(offset(cell, 1, 1, d));
                        }
                    }
                }
            }
            //3 & 1
            if (cell.qnum === 3) {
                for (let d = 0; d < 4; d++) {
                    if (!offset(cell, 1, 1, d).isnull && offset(cell, 1, 1, d).qnum === 1) {
                        add_light(offset(cell, -1, 0, d));
                        add_light(offset(cell, 0, -1, d));
                        add_dot(offset(cell, 1, 2, d));
                        add_dot(offset(cell, 2, 1, d));
                    }
                }
            }
            //2 & 1
            if (cell.qnum === 2) {
                for (let d = 0; d < 4; d++) {
                    if (!offset(cell, 1, 1, d).isnull && offset(cell, 1, 1, d).qnum === 1) {
                        add_dot(offset(cell, -1, -1, d));
                    }
                }
            }
            //1 & 1
            if (cell.qnum === 1) {
                for (let d = 0; d < 4; d++) {
                    if (!offset(cell, 1, 1, d).isnull && offset(cell, 1, 1, d).qnum === 1 &&
                        isNotLight(offset(cell, 1, 2, d)) && isNotLight(offset(cell, 2, 1, d))) {
                        add_dot(offset(cell, -1, 0, d));
                        add_dot(offset(cell, 0, -1, d));
                    }
                }
            }
        }
    }

    function MasyuAssist() {
        SingleLoopInCell();
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let adjcell = cell.adjacent;
            let adjline = cell.adjborder;
            {
                for (let d = 0; d < 4; d++) {
                    if (dir(adjcell, d + 1).qnum === CQNUM.bcir && dir(adjcell, d + 3).qnum === CQNUM.bcir &&
                        (dir(adjline, d + 2).isnull || dir(adjline, d + 2).qsub === BQSUB.cross)) {
                        add_cross(dir(adjline, d));
                    }
                }
            }
            if (cell.qnum === CQNUM.wcir) {//white
                for (let d = 0; d < 4; d++) {
                    //go straight
                    if (dir(adjline, d).line || dir(adjline, d + 1).qsub === BQSUB.cross || dir(adjline, d + 1).isnull) {
                        add_line(dir(adjline, d));
                        add_line(dir(adjline, d + 2));
                        add_cross(dir(adjline, d + 1));
                        add_cross(dir(adjline, d + 3));
                    }
                    //turn at one side
                    if (dir(adjline, d).line && dir(dir(adjcell, d).adjborder, d).line) {
                        add_cross(dir(dir(adjcell, d + 2).adjborder, d + 2));
                    }
                    //no turn on both side
                    if ((!dir(adjcell, d).isnull && (dir(dir(adjcell, d).adjborder, d).line ||
                        dir(dir(adjcell, d).adjborder, d + 1).qsub === BQSUB.cross && dir(dir(adjcell, d).adjborder, d + 3).qsub === BQSUB.cross
                    ) || dir(adjcell, d).qnum === CQNUM.wcir) &&
                        (!dir(adjcell, d + 2).isnull && (dir(dir(adjcell, d + 2).adjborder, d + 2).line ||
                            dir(dir(adjcell, d + 2).adjborder, d + 1).qsub === BQSUB.cross && dir(dir(adjcell, d + 2).adjborder, d + 3).qsub === BQSUB.cross
                        ) || dir(adjcell, d + 2).qnum === CQNUM.wcir)) {
                        add_line(dir(adjline, d + 1));
                        add_line(dir(adjline, d + 3));
                        add_cross(dir(adjline, d));
                        add_cross(dir(adjline, d + 2));
                    }
                }
            }
            if (cell.qnum === CQNUM.bcir) {//black
                for (let d = 0; d < 4; d++) {
                    //can't go straight this way
                    if (dir(adjcell, d).isnull || dir(adjline, d).qsub === BQSUB.cross ||
                        dir(dir(adjcell, d).adjacent, d).isnull || dir(dir(adjcell, d).adjborder, d).qsub === BQSUB.cross ||
                        dir(adjcell, d).qnum === CQNUM.bcir || dir(adjline, d + 2).line) {
                        add_cross(dir(adjline, d));
                        add_line(dir(adjline, d + 2));
                    }
                    //going straight this way will branch
                    if (dir(adjcell, d).isnull || dir(dir(adjcell, d).adjborder, d + 1).line ||
                        dir(dir(adjcell, d).adjborder, d + 3).line) {
                        add_cross(dir(adjline, d));
                        add_line(dir(adjline, d + 2));
                    }
                    //go straight
                    if (dir(adjline, d).line) {
                        add_line(dir(dir(adjcell, d).adjborder, d));
                    }
                }
            }
        }
    }

    function SimpleloopAssist() {
        SingleLoopInCell(
            function (c) { return c.qnum !== CQNUM.block; },
            function (c) { return c.qnum !== CQNUM.block; },
        );
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let adjline = cell.adjborder;
            if (cell.ques === CQUES.bwall) {
                fourside(add_cross, adjline);
            }
        }
    }

    function YajilinAssist() {
        SingleLoopInCell(
            function (c) { return c.qnum === CQNUM.none; },
        );
        let isPathable = function (c) { return !c.isnull && c.qnum === CQNUM.none && c.qans !== CQANS.block; };
        let isEmpty = function (c) { return !c.isnull && c.qnum === CQNUM.none && c.qans !== CQANS.block && c.qsub !== CQSUB.dot && c.lcnt === 0; };
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let emptynum = 0;
            let linenum = 0;
            let adjcell = cell.adjacent;
            let adjline = cell.adjborder;
            //check clue
            if (cell.qnum >= 0 && cell.qdir !== QDIR.none) {
                let d = qdirremap(cell.qdir);
                let emptynum = 0;
                let blocknum = 0;
                let lastcell = cell;
                let pcell = dir(cell.adjacent, d);
                let emptycellList = [];
                let addcellList = [];
                while (!pcell.isnull && (pcell.qdir !== cell.qdir || pcell.qnum < 0)) {
                    if (isEmpty(pcell)) {
                        emptynum++;
                        emptycellList.push(pcell);
                    }
                    blocknum += pcell.qans === CQANS.block;
                    if (isEmpty(lastcell) && isEmpty(pcell)) {
                        lastcell = cell;
                        emptynum--;
                    } else {
                        if (isEmpty(lastcell) && !isEmpty(pcell)) {
                            addcellList.push(lastcell);
                        }
                        lastcell = pcell;
                    }
                    pcell = dir(pcell.adjacent, d);
                }
                if (isEmpty(lastcell)) {
                    addcellList.push(lastcell);
                }
                if (!pcell.isnull) {
                    blocknum += pcell.qnum;
                }
                //finish clue
                if (emptynum + blocknum === cell.qnum) {
                    addcellList.forEach(cell => add_block(cell, 1));
                }
                //finished clue
                if (blocknum === cell.qnum) {
                    emptycellList.forEach(cell => add_dot(cell));
                }
            }
            //add cross
            if (cell.qnum !== CQNUM.none) {
                fourside(add_cross, adjline);
                continue;
            }
            //add dot around block
            if (cell.qans === CQANS.block) {
                fourside(add_cross, adjline);
                fourside(add_dot, adjcell);
                continue;
            }
            let fn = function (c, b) {
                if (isPathable(c) && b.qsub !== BQSUB.cross) { emptynum++; }
                linenum += b.line;
            };
            fourside2(fn, adjcell, adjline);
            //no branch
            if (linenum === 2) {
                fourside(add_cross, adjline);
            }
            //no deadend
            if (emptynum <= 1) {
                add_block(cell);
                fourside(add_cross, adjline);
                fourside(add_dot, adjcell);
            }
            //2 degree cell no deadend
            if (emptynum === 2) {
                let fn = function (c, b) {
                    if (!isPathable(c) || b.qsub === BQSUB.cross) { return; }
                    add_dot(c);
                };
                fourside2(fn, adjcell, adjline);
            }
        }
    }

    function SlitherlinkAssist() {
        let add_bg_color = function (c, color) {
            if (c === undefined || c.isnull || c.qsub !== CQSUB.none || c.qsub === color) { return; }
            if (step && flg) { return; }
            flg = true;
            c.setQsub(color);
            c.draw();
        }
        let add_bg_inner_color = function (c) {
            add_bg_color(c, CQSUB.green);
        }
        let add_bg_outer_color = function (c) {
            add_bg_color(c, CQSUB.yellow);
        }
        CellConnected(
            function (c) { return c.qsub === CQSUB.green; },
            function (c) { return c.qsub === CQSUB.yellow || c.qsub === CQSUB.none && c.qnum === 3; },
            add_bg_inner_color,
            add_bg_outer_color,
            function (c, list) { list.push(c); return; },
            function (c, nb, nc) { return nb.line; },
        );
        CellConnected(
            function (c) { return c.qsub === CQSUB.yellow; },
            function (c) { return c.qsub === CQSUB.green || c.qsub === CQSUB.none && c.qnum === 3; },
            add_bg_outer_color,
            add_bg_inner_color,
            function (c, list) { list.push(c); return; },
            function (c, nb, nc) { return nb.line; },
            true,
        );
        let twonum = 0;
        let threenum = 0;
        for (let i = 0; i < board.cell.length; i++) {
            twonum += board.cell[i].qnum === 2;
            threenum += board.cell[i].qnum === 3;
        }
        // deduce cell
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let adjcell = cell.adjacent;
            let adjline = cell.adjborder;
            let emptynum = 0;
            let linenum = 0;
            //add cross for 0
            if (cell.qnum === 0) {
                fourside(add_cross, adjline);
            }
            let fn = function (b) {
                if (b.qsub !== BQSUB.cross) { emptynum++; }
                linenum += (b.line);
            };
            fourside(fn, adjline);
            //finish number
            if (emptynum === cell.qnum) {
                fourside(add_line, adjline);
            }
            //add cross for finished number
            if (linenum === cell.qnum) {
                fourside(add_cross, adjline);
            }
            // vertical 3s
            if (cell.qnum === 3 && !adjcell.bottom.isnull && adjcell.bottom.qnum === 3 && (threenum > 2 || twonum > 0)) {
                add_line(cell.adjborder.top);
                add_line(cell.adjborder.bottom);
                add_line(adjcell.bottom.adjborder.bottom);
                if (!adjcell.left.isnull) { add_cross(adjcell.left.adjborder.bottom); }
                if (!adjcell.right.isnull) { add_cross(adjcell.right.adjborder.bottom); }
            }
            // horizontal 3s
            if (cell.qnum === 3 && !adjcell.right.isnull && adjcell.right.qnum === 3 && (threenum > 2 || twonum > 0)) {
                add_line(cell.adjborder.left);
                add_line(cell.adjborder.right);
                add_line(adjcell.right.adjborder.right);
                if (!adjcell.top.isnull) { add_cross(adjcell.top.adjborder.right); }
                if (!adjcell.bottom.isnull) { add_cross(adjcell.bottom.adjborder.right); }
            }
            //sub diagonal 3s
            if (cell.qnum === 3 && !adjcell.bottom.isnull && !adjcell.bottom.adjacent.left.isnull && adjcell.bottom.adjacent.left.qnum === 3) {
                let cell2 = adjcell.bottom.adjacent.left;
                add_line(cell.adjborder.top);
                add_line(cell.adjborder.right);
                add_line(cell2.adjborder.bottom);
                add_line(cell2.adjborder.left);
            }
            //main diagonal 3s
            if (cell.qnum === 3 && !adjcell.bottom.isnull && !adjcell.bottom.adjacent.right.isnull && adjcell.bottom.adjacent.right.qnum === 3) {
                let cell2 = adjcell.bottom.adjacent.right;
                add_line(cell.adjborder.top);
                add_line(cell.adjborder.left);
                add_line(cell2.adjborder.bottom);
                add_line(cell2.adjborder.right);
            }
        }
        //deduce cross
        for (let i = 0; i < board.cross.length; i++) {
            let cross = board.cross[i];
            let crsline = cross.adjborder;
            let emptynum = 0;
            let linenum = 0;
            let fn = function (b) {
                if (b !== undefined && !b.isnull && b.qsub !== BQSUB.cross) { emptynum++; }
                linenum += (b.line);
            };
            fourside(fn, crsline);
            //no deadend or branch
            if (emptynum === 1 || linenum === 2) {
                fourside(add_cross, crsline);
            }
            //extend deadend
            if (emptynum === 2 && linenum === 1) {
                fourside(add_line, crsline);
            }
            //empty turn with 1 or 3
            if (emptynum === 2 && linenum === 0) {
                let fn = function (c) { return c !== undefined && !c.isnull && c.qsub === 0; }
                if (fn(crsline.top) && fn(crsline.left) && crsline.top.sidecell[0].qnum === 3) { fourside(add_line, crsline); }
                if (fn(crsline.top) && fn(crsline.right) && crsline.top.sidecell[1].qnum === 3) { fourside(add_line, crsline); }
                if (fn(crsline.bottom) && fn(crsline.left) && crsline.bottom.sidecell[0].qnum === 3) { fourside(add_line, crsline); }
                if (fn(crsline.bottom) && fn(crsline.right) && crsline.bottom.sidecell[1].qnum === 3) { fourside(add_line, crsline); }

                if (fn(crsline.top) && fn(crsline.left) && crsline.top.sidecell[0].qnum === 1) { fourside(add_cross, crsline); }
                if (fn(crsline.top) && fn(crsline.right) && crsline.top.sidecell[1].qnum === 1) { fourside(add_cross, crsline); }
                if (fn(crsline.bottom) && fn(crsline.left) && crsline.bottom.sidecell[0].qnum === 1) { fourside(add_cross, crsline); }
                if (fn(crsline.bottom) && fn(crsline.right) && crsline.bottom.sidecell[1].qnum === 1) { fourside(add_cross, crsline); }
            }
            //2 degree turn or line enter
            {
                //+-+-+
                //|.1.|
                //+2+3+
                //|.4c5
                //+-+6+
                let fn = function (b1, b2, b3, b4, b5, b6, c34) {
                    if (c34.isnull) { return; }
                    //avoid 1*1 loop with 2 degree turn
                    if ((b1.isnull || b1.qsub === BQSUB.cross) && (b2.isnull || b2.qsub === BQSUB.cross) && b5.line && b6.line) {
                        add_cross(b3);
                        add_cross(b4);
                    }
                    //line enters 1
                    if (!b1.isnull && b1.line && (b2.isnull || b2.qsub === BQSUB.cross) && c34.qnum === 1) {
                        add_cross(b5);
                        add_cross(b6);
                    }
                    //2 degree turn with 2
                    if ((b1.isnull || b1.qsub === BQSUB.cross) && (b2.isnull || b2.qsub === BQSUB.cross) && (b5.qsub === BQSUB.cross || b6.qsub === BQSUB.cross) && c34.qnum === 2) {
                        add_line(b3);
                        add_line(b4);
                        add_cross(b5);
                        add_cross(b6);
                    }
                    if ((b1.isnull || b1.qsub === BQSUB.cross) && (b2.isnull || b2.qsub === BQSUB.cross) && (b5.line || b6.line) && c34.qnum === 2) {
                        add_cross(b3);
                        add_cross(b4);
                        add_line(b5);
                        add_line(b6);
                    }
                    //line enters 3
                    if (!b1.isnull && b1.line && c34.qnum === 3) {
                        add_cross(b2);
                        add_line(b5);
                        add_line(b6);
                    }
                    //line exit 1
                    if ((b2.isnull || b2.qsub === BQSUB.cross) && c34.qnum === 1 && b5.qsub === BQSUB.cross && b6.qsub === BQSUB.cross) {
                        add_line(b1);
                    }
                    //line exit 2
                    if ((b2.isnull || b2.qsub === BQSUB.cross) && c34.qnum === 2 && (b5.qsub === BQSUB.cross && b6.line || b6.qsub === BQSUB.cross && b5.line)) {
                        add_line(b1);
                    }
                    //line exit 3
                    if ((b2.isnull || b2.qsub === BQSUB.cross) && c34.qnum === 3 && b5.line && b6.line) {
                        add_line(b1);
                    }
                    //line should enter 1
                    if (b1.line && c34.qnum === 1 && b5.qsub === BQSUB.cross && b6.qsub === BQSUB.cross) {
                        add_cross(b2);
                    }
                    //line should enter 2
                    if (b1.line && c34.qnum === 2 && (b5.qsub === BQSUB.cross || b6.qsub === BQSUB.cross)) {
                        add_cross(b2);
                        add_line(b5);
                        add_line(b6);
                    }
                };
                if (!crsline.bottom.isnull) {
                    fn(crsline.top, crsline.left, crsline.bottom, crsline.right, crsline.bottom.sidecell[1].adjborder.bottom, crsline.bottom.sidecell[1].adjborder.right, crsline.bottom.sidecell[1]);
                    fn(crsline.left, crsline.top, crsline.bottom, crsline.right, crsline.bottom.sidecell[1].adjborder.bottom, crsline.bottom.sidecell[1].adjborder.right, crsline.bottom.sidecell[1]);
                    fn(crsline.top, crsline.right, crsline.bottom, crsline.left, crsline.bottom.sidecell[0].adjborder.bottom, crsline.bottom.sidecell[0].adjborder.left, crsline.bottom.sidecell[0]);
                    fn(crsline.right, crsline.top, crsline.bottom, crsline.left, crsline.bottom.sidecell[0].adjborder.bottom, crsline.bottom.sidecell[0].adjborder.left, crsline.bottom.sidecell[0]);
                }
                if (!crsline.top.isnull) {
                    fn(crsline.bottom, crsline.left, crsline.top, crsline.right, crsline.top.sidecell[1].adjborder.top, crsline.top.sidecell[1].adjborder.right, crsline.top.sidecell[1]);
                    fn(crsline.left, crsline.bottom, crsline.top, crsline.right, crsline.top.sidecell[1].adjborder.top, crsline.top.sidecell[1].adjborder.right, crsline.top.sidecell[1]);
                    fn(crsline.bottom, crsline.right, crsline.top, crsline.left, crsline.top.sidecell[0].adjborder.top, crsline.top.sidecell[0].adjborder.left, crsline.top.sidecell[0]);
                    fn(crsline.right, crsline.bottom, crsline.top, crsline.left, crsline.top.sidecell[0].adjborder.top, crsline.top.sidecell[0].adjborder.left, crsline.top.sidecell[0]);
                }
            }
        }
        //avoid forming multiple loop
        for (let i = 0; i < board.border.length; i++) {
            let border = board.border[i];
            if (border.qsub === BQSUB.cross) { continue; }
            if (border.line) { continue; }
            let cr1 = border.sidecross[0];
            let cr2 = border.sidecross[1];
            if (cr1.path !== null && cr1.path === cr2.path && board.linegraph.components.length > 1) {
                add_cross(border);
            }
        }
        //deduce color
        for (let i = 0; i < board.cell.length; i++) {
            let cell = board.cell[i];
            let adjline = cell.adjborder;
            let adjcell = cell.adjacent;
            //neighbor color
            {
                let fn = function (c, d) {
                    if (!c.isnull && cell.qsub !== CQSUB.none && cell.qsub === c.qsub) {
                        add_cross(d);
                    }
                    if (cell.qsub === CQSUB.yellow && c.isnull) {
                        add_cross(d);
                    }
                    if (!c.isnull && cell.qsub !== CQSUB.none && c.qsub !== CQSUB.none && cell.qsub !== c.qsub) {
                        add_line(d);
                    }
                    if (cell.qsub === CQSUB.green && c.isnull) {
                        add_line(d);
                    }
                };
                fourside2(fn, adjcell, adjline);
            }
            //deduce neighbor color
            if (cell.qsub === CQSUB.none) {
                let fn = function (c, b) {
                    if (b.line && c.isnull) {
                        add_bg_inner_color(cell);
                    }
                    if (b.qsub === BQSUB.cross && c.isnull) {
                        add_bg_outer_color(cell);
                    }
                    if (b.line && !c.isnull && c.qsub !== CQSUB.none) {
                        add_bg_color(cell, CQSUB.green + CQSUB.yellow - c.qsub);
                    }
                    if (b.qsub === BQSUB.cross && !c.isnull && c.qsub !== CQSUB.none) {
                        add_bg_color(cell, c.qsub);
                    }
                };
                fourside2(fn, adjcell, adjline);
            }
            //checker pattern
            if (cell.qsub === CQSUB.none) {
                let fn = function (c, c1, c2, c12) {
                    if (c1.isnull || c2.isnull || c12.isnull) { return; }
                    if (c1.qsub === CQSUB.none || c2.qsub === CQSUB.none || c12.qsub === CQSUB.none) { return; }
                    if (c1.qsub === c2.qsub && c1.qsub !== c12.qsub) {
                        add_bg_color(c, c1.qsub);
                    }
                };
                for (let d = 0; d < 4; d++) {
                    fn(cell, offset(cell, 1, 0, d), offset(cell, 0, 1, d), offset(cell, 1, 1, d));
                }
            }
            {
                let innernum = 0;
                let outernum = 0;
                let fn = function (c) {
                    if (!c.isnull && c.qsub === CQSUB.green) { innernum++; }
                    if (c.isnull || c.qsub === CQSUB.yellow) { outernum++; }
                };
                fourside(fn, adjcell);
                //surrounded by green
                if (innernum === 4) {
                    add_bg_inner_color(cell);
                }
                //number and color deduce
                if (cell.qnum >= 0) {
                    if (cell.qnum < innernum || 4 - cell.qnum < outernum) {
                        add_bg_inner_color(cell);
                    }
                    if (cell.qnum < outernum || 4 - cell.qnum < innernum) {
                        add_bg_outer_color(cell);
                    }
                    if (cell.qsub === CQSUB.green && cell.qnum === outernum) {
                        fourside(add_bg_inner_color, adjcell);
                    }
                    if (cell.qsub === CQSUB.yellow && cell.qnum === innernum) {
                        fourside(add_bg_outer_color, adjcell);
                    }
                    if (cell.qsub === CQSUB.yellow && cell.qnum === 4 - outernum) {
                        fourside(add_bg_inner_color, adjcell);
                    }
                    if (cell.qsub === CQSUB.green && cell.qnum === 4 - innernum) {
                        fourside(add_bg_outer_color, adjcell);
                    }
                    if (cell.qnum === CQSUB.yellow && outernum === 2) {
                        fourside(add_bg_inner_color, adjcell);
                    }
                    if (cell.qnum === CQSUB.yellow && innernum === 2) {
                        fourside(add_bg_outer_color, adjcell);
                    }
                    //2 different color around 1 or 3
                    {
                        let fn = function (c, d) {
                            if (!c.isnull && c.qsub === CQSUB.none) {
                                if (cell.qnum === 1) { add_cross(d); }
                                if (cell.qnum === 3) { add_line(d); }
                            }
                        };
                        if ((cell.qnum === 1 || cell.qnum === 3) && innernum === 1 && outernum === 1) {
                            fourside2(fn, adjcell, adjline);
                        }
                    }
                    //same diagonal color as 3
                    if (cell.qnum === 3 && cell.qsub !== CQSUB.none) {
                        for (let d = 0; d < 4; d++) {
                            if (!dir(adjcell, d).isnull && !dir(adjcell, d + 1).isnull && dir(dir(adjcell, d).adjacent, d + 1).qsub === cell.qsub) {
                                add_line(dir(adjline, d + 2));
                                add_line(dir(adjline, d + 3));
                            }
                        }
                    }

                    if (cell.qnum === 2) {
                        for (let d = 0; d < 4; d++) {
                            let c1 = dir(adjcell, d);
                            let c2 = dir(adjcell, d+1);
                            let l1 = (c1 === undefined || c1.isnull) ? undefined : dir(c1.adjborder, d+1);
                            let l2 = (c2 === undefined || c2.isnull) ? undefined : dir(c2.adjborder, d);
                            if (!(l1 === undefined || l1.qsub === BQSUB.cross)) continue;
                            if (!(l2 === undefined || l2.qsub === BQSUB.cross)) continue;
                            // l1 and l2 are both cross

                            let c3 = dir(adjcell, d+2);
                            let c4 = dir(adjcell, d+3);
                            // color logic
                            {
                                let fn = function(ca, cb) {
                                    if (cb === undefined || cb.isnull) return;
                                    if (ca === undefined || ca.isnull || ca.qsub === CQSUB.yellow) {
                                        add_bg_outer_color(cb);
                                    }
                                    if (ca !== undefined && !ca.isnull && ca.qsub === CQSUB.green) {
                                        add_bg_inner_color(cb);
                                    }
                                };
                                fn(c3, c4);
                                fn(c4, c3);
                            }
                            // line logic
                            let l3 = (c3 === undefined || c3.isnull) ? undefined : dir(c3.adjborder, d+3);
                            let l4 = (c4 === undefined || c4.isnull) ? undefined : dir(c4.adjborder, d+2);
                            if ((l3 !== undefined && l3.line) || (l4 !== undefined && l4.line)) {
                                add_line(dir(adjline, d));
                                add_line(dir(adjline, d+1));
                                add_cross(dir(adjline, d+2));
                                add_cross(dir(adjline, d+3));
                            }
                        }
                    }
                }
            }
        }
    }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址