// ==UserScript==
// @name Puzz.link Assistance
// @version 23.11.10.4
// @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=puzz.link
// @grant none
// @namespace https://gf.qytechs.cn/users/1192854
// @license GPL
// ==/UserScript==
'use strict';
const MAXLOOP = 30;
const MAXDFSCELLNUM = 200;
let flg = true;
let step = false;
let board;
let GENRENAME;
//const list
const CQNUM = {
quesmark: -2,
circle: -2, // no number
black: -2,
none: -1,
wcir: 1,
bcir: 2,
};
const CANUM = {
none: -1,
// Masyu
wcir: 1,
bcir: 2,
};
const CQANS = {
none: 0,
black: 1,
// Light and Shadow
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,
link: 1,
cross: 2,
// Icebarn
arrow_up: 11,
arrow_dn: 12,
arrow_lt: 13,
arrow_rt: 14,
};
//TODO: Moon or Sun, Pencils, Fillomino, Country Road
const GENRELIST = [
["Akari", AkariAssist],
["All or Nothing", AllorNothingAssist],
["Aqre", AqreAssist],
["Aquapelago", AquapelagoAssist],
["Castle Wall", CastleWallAssist],
["Cave", CaveAssist],
["Choco Banana", ChocoBananaAssist],
["ekawayeh", EkawayehAssist],
["Guide Arrow", GuideArrowAssist],
["Heyawake", HeyawakeAssist],
["Hitori", HitoriAssist],
["Icebarn", IcebarnAssist],
["Kurodoko", KurodokoAssist],
["Light and Shadow", LightandShadowAssist],
["LITS", LitsAssist],
["Masyu", MasyuAssist],
["Norinori", NorinoriAssist],
["No Three", NothreeAssist],
["Nurikabe", NurikabeAssist],
["Nuri-Maze", NuriMazeAssist],
["Nurimisaki", NurimisakiAssist],
["Shakashaka", ShakashakaAssist],
["Shikaku", ShikakuAssist],
["Simple Loop", SimpleloopAssist],
["Slalom", SlalomAssist],
["Slant", SlantAssist],
["Slitherlink", SlitherlinkAssist],
["Square Jam", SquareJamAssist],
["Star Battle", StarbattleAssist],
["Tapa", TapaAssist],
["Tasquare", TasquareAssist],
["Tentaisho", TentaishoAssist],
["Yajilin", YajilinAssist],
["Yin-Yang", YinyangAssist],
];
// main entrance
ui.puzzle.on('ready', function () {
console.log("Assistance running...");
GENRENAME = ui.puzzle.info.en;
if (GENRELIST.some(g => g[0] === GENRENAME)) {
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(); }
});
}
}, false);
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.find(g => g[0] === GENRENAME)[1]();
}
ui.puzzle.redraw();
console.log('Assisted.');
}
// set val
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 adjlist = function (a) {
return [a.top, a.left, a.bottom, a.right];
}
let fourside = function (f, a, b = undefined) {
if (b === undefined) {
f(a.top);
f(a.left);
f(a.bottom);
f(a.right);
} else {
f(a.top, b.top);
f(a.left, b.left);
f(a.bottom, b.bottom);
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];
}
let add_link = function (b) {
if (b === undefined || b.isnull || b.line || b.qans || b.qsub !== BQSUB.none) { return; }
if (step && flg) { return; }
b.setQsub(BQSUB.link);
b.draw();
flg |= b.qsub === BQSUB.link;
};
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_side = function (b) {
if (b === undefined || b.isnull || b.qans || b.qsub === BQSUB.link) { return; }
if (step && flg) { return; }
b.setQans(1);
b.draw();
flg |= b.qans;
};
let add_arrow = function (b, dir) {
if (b === undefined || b.isnull || b.qsub !== BQSUB.none) { return; }
if (step && flg) { return; }
flg = true;
b.setQsub(dir);
b.draw();
};
let add_black = 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.black);
c.draw();
};
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 deduction
function No2x2Cell({ isShaded, add_unshaded } = {}) {
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 => !isShaded(c));
if (templist.length === 1) {
add_unshaded(templist[0]);
}
}
}
function No2x2Black() {
No2x2Cell({
isShaded: c => c.qans === CQANS.black,
add_unshaded: add_green,
});
}
function No2x2Green() {
No2x2Cell({
isShaded: c => c.qsub === CQSUB.green,
add_unshaded: add_black,
});
}
function CellConnected({ isShaded, isUnshaded, add_shaded, add_unshaded,
isLinked = (c, nb, nc) => isShaded(c) && isShaded(nc),
isNotPassable = (c, nb, nc) => false,
OutsideAsShaded = false } = {}) {
// use tarjan to find cut vertex
// Maximum call stack size exceeded sometimes for big puzzle
let n = 0;
let ord = new Map();
let low = new Map();
let shdn = new Map();
let shadelist = [];
let dfs = function (c, f = null) {
if (!c.isnull && isUnshaded(c) || ord.has(c)) { return; }
if (c.isnull && !OutsideAsShaded) { return; }
ord.set(c, n);
low.set(n, n);
shdn.set(n, 0);
n++;
const cellset = new Set();
if (!c.isnull) {
let linkdfs = function (c) {
if (c.isnull || cellset.has(c)) { return; }
cellset.add(c);
fourside((nb, nc) => {
if (isLinked(c, nb, nc)) {
linkdfs(nc);
}
}, c.adjborder, c.adjacent);
}
linkdfs(c);
cellset.forEach(cl => {
ord.set(cl, ord.get(c));
shdn.set(ord.get(cl), shdn.get(ord.get(cl)) + isShaded(cl));
});
}
let fn = function (nc, nb) {
if (isNotPassable(c, nb, nc)) { return; }
if (nc === f || isUnshaded(nc)) { return; }
if (nc.isnull && !OutsideAsShaded) { 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)));
shdn.set(ordc, shdn.get(ordc) + shdn.get(ordnc));
if (ordc <= low.get(ordnc) && shdn.get(ordnc) > 0) {
cellset.forEach(c => shadelist.push(c));
}
};
if (!c.isnull) {
for (let c of cellset) {
// expand Fourside to avoid Maximum call stack size exceeded
fn(c.adjacent.top, c.adjborder.top);
fn(c.adjacent.bottom, c.adjborder.bottom);
fn(c.adjacent.left, c.adjborder.left);
fn(c.adjacent.right, c.adjborder.right);
};
} 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 (OutsideAsShaded) {
dfs(board.getc(0, 0));
} else {
for (let i = 0; i < board.cell.length; i++) {
if (!isShaded(board.cell[i]) || ord.has(board.cell[i])) { continue; }
dfs(board.cell[i]);
}
}
shadelist.forEach(c => add_shaded(c));
if (ord.size > 0) {
for (let i = 0; i < board.cell.length; i++) {
if (ord.has(board.cell[i]) || isShaded(board.cell[i]) || isUnshaded(board.cell[i])) { continue; }
add_unshaded(board.cell[i]);
}
}
}
function GreenConnected() {
CellConnected({
isShaded: c => c.qsub === CQSUB.green,
isUnshaded: c => c.qans === CQANS.black,
add_shaded: add_green,
add_unshaded: add_black,
});
}
function BlackConnected() {
CellConnected({
isShaded: c => c.qans === CQANS.black,
isUnshaded: c => c.qsub === CQSUB.green,
add_shaded: add_black,
add_unshaded: add_green,
});
}
function CellNoLoop({ isShaded, isUnshaded, add_unshaded } = {}) {
let ord = new Map();
let n = 0;
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (!isShaded(cell) || ord.has(cell)) { continue; }
let dfs = function (c) {
if (c.isnull || !isShaded(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 (isShaded(cell) || isUnshaded(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 && isShaded(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]) {
add_unshaded(cell);
}
}
}
}
}
function GreenNoLoopInCell() {
CellNoLoop({
isShaded: c => c.qsub === CQSUB.green,
isUnshaded: c => c.qans === CQANS.black,
add_unshaded: add_black,
});
}
function BlackNotAdjacent() {
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (cell.qans !== CQANS.black) { continue; }
fourside(add_green, cell.adjacent);
}
}
function SingleLoopInCell({ isPassable = c => true, isPathable = b => b.qsub !== BQSUB.cross,
isPass = c => c.qsub === CQSUB.dot, isPath = b => b.line,
add_notpass = c => { }, add_pass = c => { }, add_notpath = add_cross, add_path = add_line } = {}) {
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (!isPassable(cell)) {
add_notpass(cell);
fourside(add_notpath, cell.adjborder);
}
let emptycnt = 0;
let linecnt = 0;
let adjcell = cell.adjacent;
let adjline = cell.adjborder;
fourside((c, b) => {
if (!isPassable(c) || !isPathable(b)) {
add_notpath(b);
}
if (!c.isnull && isPassable(c) && isPathable(b)) { emptycnt++; }
linecnt += isPath(b);
}, adjcell, adjline);
if (linecnt > 0) {
add_pass(cell);
}
// no branch
if (linecnt === 2 && cell.ques !== CQUES.ice) {
fourside(add_notpath, adjline);
}
// no deadend
if (emptycnt <= 1) {
fourside(add_notpath, adjline);
add_notpass(cell);
}
// 2 degree path
if (emptycnt === 2 && (linecnt === 1 || isPass(cell))) {
fourside(add_path, adjline);
}
// avoid forming multiple loop
if (cell.path !== null && cell.ques !== CQUES.ice) {
for (let d = 0; d < 4; d++) {
let ncell = dir(adjcell, d);
if (cell.path === ncell.path && ncell.ques !== CQUES.ice && board.linegraph.components.length > 1) {
add_notpath(dir(adjline, d));
}
}
}
// ┌─ ┌─
// │. -> │.─
// └─ └─
if (cell.lcnt === 0 && isPass(cell)) {
let list = [];
fourside((c, b) => {
if (isPathable(b)) { list.push([c, b]); }
}, adjcell, adjline);
if (list.length === 3) {
let fn = function (a, b, c) {
if (a[0].path !== null && a[0].path === b[0].path) {
add_path(c[1]);
}
}
fn(list[0], list[1], list[2]);
fn(list[1], list[2], list[0]);
fn(list[2], list[0], list[1]);
}
}
}
}
function NoCheckerCell({ isShaded, isUnshaded, add_shaded, add_unshaded } = {}) {
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (isShaded(cell) || isUnshaded(cell)) { continue; }
let fn = function (c, c1, c2, c12) {
if (isShaded(c1) && isShaded(c2) && isUnshaded(c12)) {
add_shaded(c);
}
if (isUnshaded(c1) && isUnshaded(c2) && isShaded(c12)) {
add_unshaded(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 SightNumber({ isShaded, isUnshaded, add_shaded, add_unshaded } = {}) {
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
let qnum = cell.qnum;
if (cell.qnum !== CQNUM.none) {
add_shaded(cell);
}
if (cell.qnum !== CQNUM.none && cell.qnum !== CQNUM.quesmark) {
let seencnt = (isShaded(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 && isShaded(pcell)) {
farthest[d]++;
seencnt++;
pcell = dir(pcell.adjacent, d);
}
while (!pcell.isnull && !isUnshaded(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 && isShaded(pcell)) {
pcell = dir(pcell.adjacent, d);
}
if (pcell.isnull || isUnshaded(pcell)) { continue; }
let tcell = pcell;
pcell = dir(pcell.adjacent, d);
let n = 0;
while (!pcell.isnull && isShaded(pcell)) {
n++;
pcell = dir(pcell.adjacent, d);
}
if (n + seencnt + 1 > qnum) {
add_unshaded(tcell);
}
}
// must extend this way
let maxn = farthest.reduce((a, b) => a + b) + (isShaded(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));
}
}
}
}
}
function SizeRegion_Cell({ isShaded, isUnshaded, add_shaded, add_unshaded, OneNumPerRegion = true, NoUnshadedNum = true } = {}) {
// maybe rewrite this someday
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 (!isShaded(cell) && !isUnshaded(cell) && templist.filter(c => isUnshaded(c) || c.isnull).length >= 2) {
for (let d = 0; d < 4; d++) {
let ncell = dir(cell.adjacent, d);
if (isUnshaded(ncell)) { continue; }
let cellList = [];
let dfs = function (c) {
if (cellList.length > MAXDFSCELLNUM) { return; }
if (c.isnull || isUnshaded(c) || c === cell || cellList.includes(c)) { return; }
cellList.push(c);
fourside(dfs, c.adjacent);
}
dfs(ncell);
if (cellList.length > MAXDFSCELLNUM) { continue; }
let templist = cellList.filter(c => c.qnum !== CQNUM.none && (NoUnshadedNum || isShaded(c)));
// extend region without num
if (templist.length === 0 && cellList.some(c => isShaded(c)) && OneNumPerRegion) {
add_shaded(cell);
}
// extend region with less cells
if (templist.length >= 1 && templist[0].qnum !== CQNUM.quesmark && templist[0].qnum > cellList.length) {
add_shaded(cell);
}
}
}
// finished region
if (cell.qnum > 0 && isShaded(cell)) {
let cellList = [];
let dfs = function (c) {
if (cellList.length > cell.qnum) { return; }
if (c.isnull || !isShaded(c) || cellList.includes(c)) { return; }
cellList.push(c);
fourside(dfs, c.adjacent);
}
dfs(cell);
if (cellList.length === cell.qnum) {
cellList.forEach(c => fourside(add_unshaded, c.adjacent));
}
}
// finished surrounded region
if (cell.qnum > 0 && (NoUnshadedNum || isShaded(cell))) {
let cellList = [];
let dfs = function (c) {
if (cellList.length > cell.qnum) { return; }
if (c.isnull || isUnshaded(c) || cellList.includes(c)) { return; }
cellList.push(c);
fourside(dfs, c.adjacent);
}
dfs(cell);
if (cell.qnum !== CQNUM.quesmark && cell.qnum === cellList.length) {
cellList.forEach(c => add_shaded(c));
}
}
// not connect two region
for (let d1 = 0; d1 < 4; d1++) {
for (let d2 = d1 + 1; d2 < 4; d2++) {
if (isShaded(cell) || isUnshaded(cell)) { continue; }
let cell1 = dir(cell.adjacent, d1);
let cell2 = dir(cell.adjacent, d2);
if (cell1.isnull || cell2.isnull || !isShaded(cell1) || !isShaded(cell2)) { continue; }
let cellList1 = [];
let cellList2 = [];
let dfs = function (c, list) {
if (c.isnull || !isShaded(c) || list.includes(c)) { 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.includes(cell2)) { 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) {
add_unshaded(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) {
add_unshaded(cell);
}
}
if (cell.qnum >= 0 && cellList1.length + cellList2.length + 1 > cell.qnum) {
add_unshaded(cell);
}
}
}
// cell and region
for (let d = 0; d < 4; d++) {
if (isShaded(cell) || isUnshaded(cell) || cell.qnum === CQNUM.none) { continue; }
let ncell = dir(cell.adjacent, d);
if (ncell.isnull || !isShaded(ncell)) { continue; }
let cellList = [];
let dfs = function (c) {
if (c.isnull || !isShaded(c) || cellList.includes(c)) { 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)) {
add_unshaded(cell);
}
if (cell.qnum !== CQNUM.quesmark && cellList.length + 1 > cell.qnum) {
add_unshaded(cell);
}
}
}
}
function RectRegion_Cell({ isShaded, isUnshaded, add_shaded, add_unshaded, isSquare = false }) {
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (isShaded(cell) || isUnshaded(cell)) { continue; }
// can't be 3 shades in each 2*2
let fn = function (list) {
if (list.some(c => c.isnull)) { return; }
if (list.filter(c => isShaded(c)).length === 2 && list.filter(c => isUnshaded(c)).length === 1) {
add_unshaded(cell);
}
if (list.filter(c => isShaded(c)).length === 3 && list.filter(c => isUnshaded(c)).length === 0) {
add_shaded(cell);
}
};
for (let d = 0; d < 4; d++) {
fn([offset(cell, 1, 0, d), offset(cell, 0, 1, d), offset(cell, 1, 1, d)]);
}
}
if (!isSquare) { return; }
let shadelist = [];
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (!isShaded(cell)) { continue; }
// B -> B
// . . ...
for (let d = 0; d < 4; d++) {
if ([offset(cell, -1, 1, d), offset(cell, 1, 1, d)].every(c => c.isnull || isUnshaded(c))) {
add_unshaded(offset(cell, 0, 1, d));
}
}
if ((c => !c.isnull && isShaded(c))(offset(cell, 0, -1))) { continue; }
if ((c => !c.isnull && isShaded(c))(offset(cell, -1, 0))) { continue; }
let height = 1, width = 1;
while ((c => !c.isnull && isShaded(c))(offset(cell, 0, height - 1))) { height++; }
while ((c => !c.isnull && isShaded(c))(offset(cell, width - 1, 0))) { width++; }
height--;
width--;
// finished square
if (width === height) {
let templist = [offset(cell, -1, 0), offset(cell, 0, -1), offset(cell, width, 0), offset(cell, 0, height)];
if ([templist[0], templist[2]].every(c => c.isnull || isUnshaded(c)) ||
[templist[1], templist[3]].every(c => c.isnull || isUnshaded(c))) {
templist.forEach(c => add_unshaded(c));
}
}
// extend square
if (height > width) {
for (let j = 0; j < height; j++) {
let c = offset(cell, 0, j);
let l = 0, r = 0;
while ((c => !c.isnull && !isUnshaded(c))(offset(c, l, 0)) && l > width - height - 1) { l--; }
while ((c => !c.isnull && !isUnshaded(c))(offset(c, r, 0)) && r < height) { r++; }
for (let k = r - height; k <= l + height; k++) {
shadelist.push(offset(c, k, 0));
}
}
}
if (height < width) {
for (let j = 0; j < width; j++) {
let c = offset(cell, j, 0);
let l = 0, r = 0;
while ((c => !c.isnull && !isUnshaded(c))(offset(c, 0, l)) && l > height - width - 1) { l--; }
while ((c => !c.isnull && !isUnshaded(c))(offset(c, 0, r)) && r < width) { r++; }
for (let k = r - width; k <= l + width; k++) {
shadelist.push(offset(c, 0, k));
}
}
}
}
shadelist.forEach(c => add_shaded(c));
}
function RectRegion_Border({ isSquare = false } = {}) {
let isLink = b => !b.isnull && b.qsub === BQSUB.link;
let isSide = b => b.isnull || b.qans;
for (let i = 0; i < board.cross.length; i++) {
let cross = board.cross[i];
for (let d = 0; d < 4; d++) {
// x x
// x+ -> x+x
// x
let b1 = dir(cross.adjborder, d);
let b2 = dir(cross.adjborder, d + 1);
if (isLink(b1) && isLink(b2)) {
add_link(dir(cross.adjborder, d + 2));
add_link(dir(cross.adjborder, d + 3));
}
// | |
// x+ -> x+
// |
if (isSide(b1) && isLink(b2)) {
add_side(dir(cross.adjborder, d + 2));
}
// x x
// -+ -> -+-
//
if (isLink(b1) && isSide(b2)) {
add_side(dir(cross.adjborder, d + 3));
}
// | |
// + -> -+-
// x x
b2 = dir(cross.adjborder, d + 2);
if (isSide(b1) && isLink(b2)) {
add_side(dir(cross.adjborder, d + 1));
add_side(dir(cross.adjborder, d + 3));
}
}
}
if (!isSquare) { return; }
let drawlist = [];
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (isLink(cell.adjborder.top)) { continue; }
if (isLink(cell.adjborder.left)) { continue; }
let height = 1, width = 1;
while (isLink(offset(cell, 0, height - .5))) { height++; }
while (isLink(offset(cell, width - .5, 0))) { width++; }
// finished square
if (width === height) {
let templist = [offset(cell, -.5, 0), offset(cell, 0, -.5), offset(cell, width - .5, 0), offset(cell, 0, height - .5)];
if ([templist[0], templist[2]].every(c => isSide(c)) ||
[templist[1], templist[3]].every(c => isSide(c))) {
templist.forEach(c => add_side(c));
}
}
// extend square
if (height > width) {
for (let j = 0; j < height; j++) {
let c = offset(cell, 0, j);
let l = 0, r = 0;
while (!isSide(offset(c, l - .5, 0)) && l > width - height) { l--; }
while (!isSide(offset(c, r + .5, 0)) && r < height - 1) { r++; }
for (let k = r - height + 1.5; k <= l + height - 1.5; k++) {
drawlist.push(offset(c, k, 0));
}
}
}
if (height < width) {
for (let j = 0; j < width; j++) {
let c = offset(cell, j, 0);
let l = 0, r = 0;
while (!isSide(offset(c, 0, l - .5)) && l > height - width) { l--; }
while (!isSide(offset(c, 0, r + .5)) && r < width - 1) { r++; }
for (let k = r - width + 1.5; k <= l + width - 1.5; k++) {
drawlist.push(offset(c, 0, k));
}
}
}
}
drawlist.forEach(b => add_link(b));
}
// assist for certain genre
function ShikakuAssist() {
RectRegion_Border();
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (cell.qnum < 0) { continue; }
for (let d = 0; d < 4; d++) {
if (dir(cell.adjacent, d).qnum !== CQNUM.none) {
add_side(dir(cell.adjborder, d));
}
}
let h1 = -.5, h2 = .5;
let w1 = -.5, w2 = .5;
while ((b => !b.isnull && b.qsub === BQSUB.link)(offset(cell, 0, h1))) { h1--; }
while ((b => !b.isnull && b.qsub === BQSUB.link)(offset(cell, 0, h2))) { h2++; }
while ((b => !b.isnull && b.qsub === BQSUB.link)(offset(cell, w1, 0))) { w1--; }
while ((b => !b.isnull && b.qsub === BQSUB.link)(offset(cell, w2, 0))) { w2++; }
if ((h2 - h1) * (w2 - w1) === cell.qnum) {
add_side(offset(cell, 0, h1));
add_side(offset(cell, 0, h2));
add_side(offset(cell, w1, 0));
add_side(offset(cell, w2, 0));
continue;
}
while ((b => !b.isnull && !b.qans)(offset(cell, 0, h1)) &&
(c => !c.isnull && c.qnum === CQNUM.none)(offset(cell, 0, h1 - .5))) { h1--; }
while ((b => !b.isnull && !b.qans)(offset(cell, 0, h2)) &&
(c => !c.isnull && c.qnum === CQNUM.none)(offset(cell, 0, h2 + .5))) { h2++; }
while ((b => !b.isnull && !b.qans)(offset(cell, w1, 0)) &&
(c => !c.isnull && c.qnum === CQNUM.none)(offset(cell, w1 - .5, 0))) { w1--; }
while ((b => !b.isnull && !b.qans)(offset(cell, w2, 0)) &&
(c => !c.isnull && c.qnum === CQNUM.none)(offset(cell, w2 + .5, 0))) { w2++; }
h1 = Math.max(h1, -cell.qnum + .5);
h2 = Math.min(h2, cell.qnum - .5);
w1 = Math.max(w1, -cell.qnum + .5);
w2 = Math.min(w2, cell.qnum - .5);
let minh = h2 - h1, minw = w2 - w1;
for (let k = Math.ceil(cell.qnum / (w2 - w1)); k <= h2 - h1; k++) {
if (cell.qnum % k !== 0) { continue; }
minh = Math.min(minh, k);
minw = Math.min(minw, cell.qnum / k);
}
for (let j = h2 - minh + 1; j <= h1 + minh - 1; j++) {
add_link(offset(cell, 0, j));
}
for (let j = w2 - minw + 1; j <= w1 + minw - 1; j++) {
add_link(offset(cell, j, 0));
}
}
}
function SquareJamAssist() {
RectRegion_Border({ isSquare: true });
for (let i = 0; i < board.cross.length; i++) {
let cross = board.cross[i];
let list = adjlist(cross.adjborder);
if (list.filter(b => !b.isnull && b.qans).length === 3) {
list.forEach(b => add_link(b));
}
}
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (cell.qnum < 0) { continue; }
let h1 = -.5, h2 = .5;
while ((b => !b.isnull && b.qsub === BQSUB.link)(offset(cell, 0, h1))) { h1--; }
while ((b => !b.isnull && b.qsub === BQSUB.link)(offset(cell, 0, h2))) { h2++; }
if (cell.qnum === h2 - h1) {
add_side(offset(cell, 0, h1));
add_side(offset(cell, 0, h2));
}
let w1 = -.5, w2 = .5;
while ((b => !b.isnull && b.qsub === BQSUB.link)(offset(cell, w1, 0))) { w1--; }
while ((b => !b.isnull && b.qsub === BQSUB.link)(offset(cell, w2, 0))) { w2++; }
if (cell.qnum === w2 - w1) {
add_side(offset(cell, w1, 0));
add_side(offset(cell, w2, 0));
}
}
}
function TasquareAssist() {
RectRegion_Cell({
isShaded: c => c.qans === CQANS.black,
isUnshaded: c => c.qsub === CQSUB.green || c.qnum !== CQNUM.none,
add_shaded: add_black,
add_unshaded: add_dot,
isSquare: true,
});
CellConnected({
isShaded: c => c.qsub === CQSUB.dot || c.qnum !== CQNUM.none,
isUnshaded: c => c.qans === CQANS.black,
add_shaded: add_dot,
add_unshaded: add_black,
});
let is2x2able = function (c) {
for (let d = 0; d < 4; d++) {
let list = [c, offset(c, 0, 1, d), offset(c, 1, 0, d), offset(c, 1, 1, d)];
if (list.every(c => !c.isnull && c.qsub !== CQSUB.dot && c.qnum === CQNUM.none)) {
return true;
}
}
return false;
}
let isNotBlack = c => c.isnull || c.qsub === CQSUB.dot || c.qnum !== CQNUM.none;
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (cell.qnum === CQNUM.none) { continue; }
add_dot(cell);
let templist = adjlist(cell.adjacent);
if (templist.filter(c => !isNotBlack(c)).length === 1) {
templist.forEach(c => add_black(c, true));
}
if (cell.qnum === CQNUM.quesmark) { continue; }
// black cells: n around 0~3, n-3 around 4~7, 2 around 8
if (cell.qnum <= 8) {
let list = adjlist(cell.adjacent).filter(c => !isNotBlack(c));
if ([0, 1, 2, 3].includes(cell.qnum) && list.length === cell.qnum ||
[4, 5, 6].includes(cell.qnum) && list.length === cell.qnum - 3 ||
cell.qnum === 8 && list.length === 2) {
list.forEach(c => add_black(c));
}
}
// 1*2^2 around 4~6, 2*2^2 around 8
if (cell.qnum >= 4 && cell.qnum <= 8) {
let list = [];
for (let d = 0; d < 4; d++) {
list.push([offset(cell, 2, 0, d), offset(cell, 1, 0, d)]);
}
list = list.filter(l => is2x2able(l[1]));
if (list.length === Math.floor(cell.qnum / 4)) {
list.forEach(l => { add_black(l[0]), add_black(l[1]) });
}
}
// . .
// 3 -> 3
// . .
if (cell.qnum === 3) {
add_dot(offset(cell, -1, -1));
add_dot(offset(cell, -1, 1));
add_dot(offset(cell, 1, -1));
add_dot(offset(cell, 1, 1));
}
for (let d = 0; d < 4; d++) {
// ? # -> ?.# (?<=3)
if (cell.qnum <= 3 && offset(cell, 2, 0, d).qans === CQANS.black) {
add_dot(offset(cell, 1, 0, d));
}
// 4 . -> 4..
if (cell.qnum === 4 && isNotBlack(offset(cell, 2, 0, d))) {
add_dot(offset(cell, 1, 0, d));
}
// . .
// .1 -> .1
// .
if (cell.qnum === 1 && isNotBlack(offset(cell, 0, -1, d)) && isNotBlack(offset(cell, -1, 0, d))) {
add_dot(offset(cell, 1, 1, d));
}
// . .
// 2 -> 2
// . .
if (cell.qnum === 2 && isNotBlack(offset(cell, 0, -1, d))) {
add_dot(offset(cell, -1, 1, d));
add_dot(offset(cell, 1, 1, d));
}
}
let blist = [];
let dfs = function (c) {
if (blist.includes(c) || c.isnull || c.qans !== CQANS.black) { return; }
blist.push(c);
fourside(dfs, c.adjacent);
}
adjlist(cell.adjacent).forEach(c => dfs(c));
if (blist.length === cell.qnum) {
fourside(add_dot, cell.adjacent);
blist.forEach(c => fourside(add_dot, c.adjacent));
}
}
}
function TentaishoAssist() {
let isDot = obj => obj.qnum > 0;
let isEmpty = c => !c.isnull && c.ques !== CQUES.bwall;
for (let i = 0; i < board.cross.length; i++) {
let cross = board.cross[i];
let list = adjlist(cross.adjborder);
if (list.filter(b => !b.isnull && b.qsub === BQSUB.link).length === 2 && cross.lcnt === 1) {
list.forEach(b => add_side(b));
}
if (list.filter(b => !b.isnull && b.qsub === BQSUB.link).length === 3) {
list.forEach(b => add_link(b));
}
}
let n = 0;
let id = new Map(); // map every cell to unique dot id
let dotmap = new Map();
let bfs_c = function (clist, n) {
let c = clist.pop();
let x = dotmap.get(n).bx;
let y = dotmap.get(n).by;
if (!isEmpty(c) || id.has(c)) { return; }
id.set(c, n);
let fn = function (bbx, bby, cbx, cby) {
let nb = board.getb(bbx, bby);
let nc = board.getc(cbx, cby);
if (!isEmpty(nc) || nb.qans || id.has(nc) && id.get(nc) !== id.get(c)) {
add_side(nb);
add_side(board.getb(2 * x - bbx, 2 * y - bby));
}
if (id.has(nc) && id.get(nc) === id.get(c)) {
add_link(nb);
}
if (isEmpty(nc) && nb.qsub === BQSUB.link) {
add_link(board.getb(2 * x - bbx, 2 * y - bby));
clist.push(nc);
clist.push(board.getc(2 * x - cbx, 2 * y - cby));
}
};
fn(c.bx - 1, c.by, c.bx - 2, c.by);
fn(c.bx + 1, c.by, c.bx + 2, c.by);
fn(c.bx, c.by - 1, c.bx, c.by - 2);
fn(c.bx, c.by + 1, c.bx, c.by + 2);
};
let bfs = function (clist, n) {
while (clist.length > 0) { bfs_c(clist, n); }
}
// assign cells to dots and deduce
for (let x = board.minbx + 1; x <= board.maxbx - 1; x++) {
for (let y = board.minby + 1; y <= board.maxby - 1; y++) {
if (isDot(board.getobj(x, y))) {
n++;
dotmap.set(n, board.getobj(x, y));
let clist = [];
if (x % 2 === 1 && y % 2 === 1) {
clist.push(board.getc(x, y));
}
if (x % 2 === 1 && y % 2 === 0) {
clist.push(board.getc(x, y - 1));
clist.push(board.getc(x, y + 1));
}
if (x % 2 === 0 && y % 2 === 1) {
clist.push(board.getc(x - 1, y));
clist.push(board.getc(x + 1, y));
}
if (x % 2 === 0 && y % 2 === 0) {
clist.push(board.getc(x - 1, y - 1));
clist.push(board.getc(x - 1, y + 1));
clist.push(board.getc(x + 1, y - 1));
clist.push(board.getc(x + 1, y + 1));
}
bfs(clist, n);
}
}
}
// check not assigned cells
let templist = [];
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (!isDot(cell) && adjlist(cell.adjborder).filter(b => !b.isnull && !b.qans).length === 1) {
add_link(adjlist(cell.adjborder).filter(b => !b.isnull && !b.qans)[0]);
}
if (!isEmpty(cell) || id.has(cell) || templist.includes(cell)) { continue; }
let clist = [];
let nid = [];
let dfs = function (c) {
if (!isEmpty(c) || clist.includes(c)) { return; }
clist.push(c);
templist.push(c);
fourside((nb, nc) => {
if (nb.qans || !isEmpty(nc) || clist.includes(nc)) { return; }
if (id.has(nc)) {
if (!nid.includes(id.get(nc))) {
nid.push(id.get(nc));
}
return;
}
if (isEmpty(nc)) {
dfs(nc);
}
}, c.adjborder, c.adjacent)
};
dfs(cell);
for (let c of clist) {
let anid = nid.filter(n => {
let dot = dotmap.get(n);
let dc = board.getc(dot.bx * 2 - c.bx, dot.by * 2 - c.by);
return isEmpty(dc) && (!id.has(dc) || id.get(dc) === n);
});
if (anid.length === 1) {
bfs([c], anid[0]);
}
}
}
document.querySelector('#btncolor').click();
}
function NorinoriAssist() {
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
let list = adjlist(cell.adjacent);
// surrounded by dot
if (!list.some(c => !c.isnull && c.qsub !== CQSUB.dot)) {
add_dot(cell);
}
// extend domino
if (cell.qans === CQANS.black && list.filter(c => !c.isnull && c.qsub !== CQSUB.dot).length === 1) {
let ncell = list.find(c => !c.isnull && c.qsub !== CQSUB.dot);
add_black(ncell);
}
// finished domino
if (cell.qans === CQANS.black && list.some(c => !c.isnull && c.qans === CQANS.black)) {
let ncell = list.find(c => !c.isnull && c.qans === CQANS.black);
fourside(add_dot, cell.adjacent);
fourside(add_dot, ncell.adjacent);
}
// not making triomino
if (list.filter(c => !c.isnull && c.qans === CQANS.black).length >= 2) {
add_dot(cell);
}
// . .
// .X -> .X
// .
for (let d = 0; d < 4; d++) {
if (cell.qans === CQANS.black &&
(offset(cell, -1, 0, d).isnull || offset(cell, -1, 0, d).qsub === CQSUB.green) &&
(offset(cell, 0, -1, d).isnull || offset(cell, 0, -1, d).qsub === CQSUB.green)) {
add_dot(offset(cell, 1, 1, d));
}
}
}
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]);
}
// finish region
if (list.filter(c => c.qans === CQANS.black).length === 2) {
list.forEach(c => add_dot(c));
}
if (list.filter(c => c.qsub !== CQSUB.dot).length === 2) {
list.forEach(c => add_black(c));
}
if (list.filter(c => c.qans === CQANS.black).length === 1) {
list.forEach(c => {
if (c.qans === CQANS.black || c.qsub === CQSUB.dot) { return; }
if (!adjlist(c.adjacent).some(nc => !nc.isnull &&
(c.room !== nc.room && nc.qsub !== CQSUB.dot || c.room === nc.room && nc.qans === CQANS.black))) {
add_dot(c);
}
});
}
}
}
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({
isPassable: c => c.qsub !== CQSUB.gray,
isPass: c => c.qsub === CQSUB.yellow,
add_notpass: add_gray,
add_pass: add_yellow,
});
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 nbcnt = function (c) {
let templist = adjlist(c.adjacent);
return templist.filter(nc => !nc.isnull && c.room === nc.room).length;
};
let list2 = list.filter(c => nbcnt(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 => nbcnt(c) === 1);
templist.forEach(c => fourside((nb, nc) => {
if (!nc.isnull && room === nc.room) {
add_line(nb);
}
}, 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() {
BlackConnected();
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.black && c2.qans === CQANS.black && c3.qans === CQANS.black) {
add_green(cell);
}
if (c1.qsub === CQSUB.green && c2.qsub === CQSUB.green && c3.qsub === CQSUB.green) {
add_black(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.black).length === qnum) {
list.forEach(c => add_green(c));
}
if (qnum - list.filter(c => c.qans === CQANS.black).length ===
list.filter(c => c.qans === CQANS.none && c.qsub === CQSUB.none).length) {
list.forEach(c => add_black(c));
}
}
}
function KurodokoAssist() {
GreenConnected();
BlackNotAdjacent();
SightNumber({
isShaded: c => c.qsub === CQSUB.green,
isUnshaded: c => c.qans === CQANS.black,
add_shaded: add_green,
add_unshaded: add_black,
});
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (cell.qnum !== CQNUM.none) {
add_green(cell);
}
}
}
function HitoriAssist() {
GreenConnected();
BlackNotAdjacent();
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_black(cell);
}
let cnt = new Map();
for (let cell of a) {
if (cell.qnum === CQNUM.none || cell.qans === CQANS.black) 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.black) 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_black(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() {
GreenConnected();
CellConnected({
isShaded: c => c.qans === CQANS.black,
isUnshaded: c => c.qsub === CQSUB.green,
add_shaded: add_black,
add_unshaded: add_green,
OutsideAsShaded: true,
});
SightNumber({
isShaded: c => c.qsub === CQSUB.green,
isUnshaded: c => c.qans === CQANS.black,
add_shaded: add_green,
add_unshaded: add_black,
});
NoCheckerCell({
isShaded: c => c.qans === CQANS.black,
isUnshaded: c => c.qans === CQANS.green,
add_shaded: add_black,
add_unshaded: add_green,
});
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (cell.qnum !== CQNUM.none) {
add_green(cell);
}
}
}
function TapaAssist() {
No2x2Cell({
isShaded: c => c.qans === CQANS.black,
add_unshaded: add_dot,
});
CellConnected({
isShaded: c => c.qans === CQANS.black,
isUnshaded: c => c.qsub === CQSUB.dot || c.qnums.length > 0,
add_shaded: add_black,
add_unshaded: 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.includes(qnums[i])) { 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.black ? "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_black(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_black(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();
};
SizeRegion_Cell({
isShaded: c => c.qans === CQANS.black,
isUnshaded: c => c.qans === CQANS.white,
add_shaded: add_black,
add_unshaded: add_white,
NoUnshadedNum: false,
});
SizeRegion_Cell({
isShaded: c => c.qans === CQANS.white,
isUnshaded: c => c.qans === CQANS.black,
add_shaded: add_white,
add_unshaded: add_black,
NoUnshadedNum: 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({
isPassable: c => c.ques !== 1,
isPass: c => 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_black;
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({
isPassable: c => 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 = c => !c.isnull && c.qnum === CQNUM.none && c.qsub === CQSUB.none && c.qans === CQANS.none;
let isDot = c => !c.isnull && c.qsub === CQSUB.dot && c.qnum === CQNUM.none;
let isDotEmpty = c => isEmpty(c) || isDot(c);
let isBlack = c => !c.isnull && c.qans === CQANS.black;
let isBorderBlack = c => c.isnull || c.qans === CQANS.black;
let isConnectBlack = c => isBorderBlack(c) || c.qnum !== CQNUM.none;
let isCircle = c => !c.isnull && c.qnum !== CQNUM.none;
let isDotCircle = c => isDot(c) || isCircle(c);
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
let blackcnt = 0;
let dotcnt = 0;
fourside(c => {
if (isBorderBlack(c)) { blackcnt++; }
if (isDot(c)) { dotcnt++; }
}, cell.adjacent);
// no clue pattern
// add dot
if (isEmpty(cell)) {
for (let d = 0; d < 4; d++) {
// cannot place black with 2x2 black rule
if (isEmpty(offset(cell, 0, -1, d)) && isBlack(offset(cell, 1, 0, d)) && isBlack(offset(cell, 1, -1, d)) &&
(isBorderBlack(offset(cell, 0, -2, d)) || isBorderBlack(offset(cell, -1, -1, d)))) {
add_dot(cell);
}
else if (isEmpty(offset(cell, 0, -1, d)) && isBlack(offset(cell, -1, 0, d)) && isBlack(offset(cell, -1, -1, d)) &&
(isBorderBlack(offset(cell, 0, -2, d)) || isBorderBlack(offset(cell, 1, -1, d)))) {
add_dot(cell);
}
else if (isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 0, -1, d)) && isBlack(offset(cell, 1, -1, d)) &&
(isBorderBlack(offset(cell, 2, 0, d)) || isBorderBlack(offset(cell, 1, 1, d))) &&
(isBorderBlack(offset(cell, 0, -2, d)) || isBorderBlack(offset(cell, -1, -1, d)))) {
add_dot(cell);
}
else if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, -1, d)) && isBlack(offset(cell, 1, 0, d)) &&
isBorderBlack(offset(cell, -1, -1, d)) && isBorderBlack(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)) && isBlack(offset(cell, -1, 0, d)) &&
isBorderBlack(offset(cell, 1, -1, d)) && isBorderBlack(offset(cell, 0, -2, d)) && offset(cell, -1, -2, d).isnull) {
add_dot(cell);
}
// cannot place black with 2x2 dot rule
else if (isBlack(offset(cell, 1, 0, d)) && isBlack(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 (isBlack(offset(cell, -1, 0, d)) && isBlack(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 black with 2x2 black rule and 2x2 dot rule
else if (isDotEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 0, -2, d)) &&
isBlack(offset(cell, 1, -1, d)) && isBlack(offset(cell, 1, -2, d)) && isBorderBlack(offset(cell, 0, -3, d))) {
add_dot(cell);
}
else if (isDotEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 0, -2, d)) &&
isBlack(offset(cell, -1, -1, d)) && isBlack(offset(cell, -1, -2, d)) && isBorderBlack(offset(cell, 0, -3, d))) {
add_dot(cell);
}
else if (isDotEmpty(offset(cell, -1, 0, d)) && isEmpty(offset(cell, -1, -1, d)) &&
isBlack(offset(cell, 0, -1, d)) && isBorderBlack(offset(cell, -1, 1, d)) && isBorderBlack(offset(cell, -1, -2, d))) {
add_dot(cell);
}
else if (isDotEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
isBlack(offset(cell, 0, -1, d)) && isBorderBlack(offset(cell, 1, 1, d)) && isBorderBlack(offset(cell, 1, -2, d))) {
add_dot(cell);
}
// cannot place black 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)) &&
isBorderBlack(offset(cell, -1, -1, d)) && isBorderBlack(offset(cell, 3, -1, d)) &&
isBorderBlack(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)) &&
isBorderBlack(offset(cell, 1, -1, d)) && isBorderBlack(offset(cell, -3, -1, d)) &&
isBorderBlack(offset(cell, -3, 0, d)) && offset(cell, 0, -2, d).isnull) {
add_dot(cell);
}
}
}
if (isDot(cell)) {
// dot cannot be deadend
if (blackcnt === 2) {
fourside(add_dot, cell.adjacent);
}
for (let d = 0; d < 4; d++) {
// avoid 2x2 dot
if (isBorderBlack(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 (isBorderBlack(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 (isBorderBlack(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isBorderBlack(offset(cell, 2, 0, d)) &&
isBorderBlack(offset(cell, 1, -1, d)) && isEmpty(offset(cell, -1, 0, d))) {
add_dot(offset(cell, -1, 0, d));
}
else if (isBorderBlack(offset(cell, 0, -1, d)) && isEmpty(offset(cell, -1, 0, d)) && isBorderBlack(offset(cell, -2, 0, d)) &&
isBorderBlack(offset(cell, -1, -1, d)) && isEmpty(offset(cell, 1, 0, d))) {
add_dot(offset(cell, 1, 0, d));
}
}
}
// add black
if (isEmpty(cell)) {
// black deadend
if (blackcnt >= 3) {
add_black(cell);
}
for (let d = 0; d < 4; d++) {
// cannot dot with 2x2 dot rule
if (isBorderBlack(offset(cell, -1, 0, d)) && isBorderBlack(offset(cell, 2, 0, d)) && isEmpty(offset(cell, 1, 0, d)) &&
offset(cell, 0, -1, d).isnull && offset(cell, 1, -1, d).isnull) {
add_black(cell);
}
else if (isBorderBlack(offset(cell, 1, 0, d)) && isBorderBlack(offset(cell, 0, -1, d)) && isDot(offset(cell, -1, 1, d)) &&
isDotEmpty(offset(cell, -1, 0, d)) && isDotEmpty(offset(cell, 0, 1, d))) {
add_black(cell);
}
}
}
// clue pattern
// any circle clue
if (cell.qnum !== CQNUM.none) {
// clue deadend check
if (blackcnt === 3) {
fourside(add_dot, cell.adjacent);
}
else if (dotcnt === 1) {
fourside(add_black, cell.adjacent);
}
for (let d = 0; d < 4; d++) {
// avoid 2x2 black pattern
if (isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) &&
isBlack(offset(cell, 0, -2, d)) && isBlack(offset(cell, 1, -2, d))) {
add_black(offset(cell, 0, 1, d));
add_black(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)) &&
isBlack(offset(cell, 2, 0, d)) && isBlack(offset(cell, 2, -1, d))) {
add_black(offset(cell, 0, 1, d));
add_black(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)) && isBlack(offset(cell, 1, -2, d)) &&
(isBorderBlack(offset(cell, 0, -3, d)) || isBorderBlack(offset(cell, -1, -2, d)))) {
add_black(offset(cell, 0, 1, d));
add_black(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)) && isBlack(offset(cell, 2, -1, d)) &&
(isBorderBlack(offset(cell, 3, 0, d)) || isBorderBlack(offset(cell, 2, 1, d)))) {
add_black(offset(cell, 0, 1, d));
add_black(offset(cell, -1, 0, d));
}
// avoid 2x2 black 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)) && isBlack(offset(cell, 0, -2, d)) && isBorderBlack(offset(cell, 1, -3, d))) {
add_black(offset(cell, 0, 1, d));
add_black(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)) && isBlack(offset(cell, 2, 0, d)) && isBorderBlack(offset(cell, 3, -1, d))) {
add_black(offset(cell, 0, 1, d));
add_black(offset(cell, -1, 0, d));
}
// avoid border 2x2 black
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)) && isBorderBlack(offset(cell, 3, 0, d)) &&
isBorderBlack(offset(cell, 3, -1, d)) && offset(cell, 0, -2, d).isnull) {
add_black(offset(cell, 0, 1, d));
add_black(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)) && isBorderBlack(offset(cell, 0, -3, d)) &&
isBorderBlack(offset(cell, 1, -3, d)) && offset(cell, 2, 0, d).isnull) {
add_black(offset(cell, 0, 1, d));
add_black(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)) && isBorderBlack(offset(cell, 4, 0, d)) &&
isBorderBlack(offset(cell, 4, -1, d)) && offset(cell, 0, -2, d).isnull) {
add_black(offset(cell, 0, 1, d));
add_black(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)) && isBorderBlack(offset(cell, 0, -4, d)) &&
isBorderBlack(offset(cell, 1, -4, d)) && offset(cell, 2, 0, d).isnull) {
add_black(offset(cell, 0, 1, d));
add_black(offset(cell, -1, 0, d));
}
}
}
if (isEmpty(cell)) {
for (let d = 0; d < 4; d++) {
// cannot place black with 2x2 white
if (isBlack(offset(cell, 1, 0, d)) && isBlack(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 (isBlack(offset(cell, -1, 0, d)) && isBlack(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 (isBorderBlack(offset(cell, 1, 0, d)) && isBorderBlack(offset(cell, 0, -1, d)) && isCircle(offset(cell, -1, 1, d)) &&
isEmpty(offset(cell, -1, 0, d)) && isEmpty(offset(cell, 0, 1, d))) {
add_black(cell);
}
}
}
if (isDot(cell)) {
for (let d = 0; d < 4; d++) {
// avoid 2x2 white
if (isBorderBlack(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_black(offset(cell, 2, 1, d));
add_black(offset(cell, 1, 2, d));
}
else if (isBorderBlack(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_black(offset(cell, -2, 1, d));
add_black(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_black(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_black(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 && isConnectBlack(offset(cell, 0, -j, d))) {
add_black(offset(cell, 0, -1, d));
break;
}
if (isBorderBlack(offset(cell, 0, -j, d))) {
add_black(offset(cell, 0, -1, d));
break;
}
// avoid 2x2 dot
if (isDot(offset(cell, 1, -j + 1, d)) && isDot(offset(cell, 1, -j, d))) {
add_black(offset(cell, 0, -1, d));
break;
}
if (isDot(offset(cell, -1, -j + 1, d)) && isDot(offset(cell, -1, -j, d))) {
add_black(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_black(offset(cell, 0, -cell.qnum, d));
}
}
}
}
// 2x2 rules
No2x2Cell({
isShaded: c => c.qans === CQANS.black,
add_unshaded: add_dot,
});
No2x2Cell({
isShaded: c => c.qsub === CQSUB.dot,
add_unshaded: add_black,
});
CellConnected({
isShaded: isDotCircle,
isUnshaded: isBlack,
add_shaded: add_dot,
add_unshaded: add_black,
});
CellConnected({
isShaded: isDot,
isUnshaded: isConnectBlack,
add_shaded: add_dot,
add_unshaded: add_black,
});
}
function ChocoBananaAssist() {
SizeRegion_Cell({
isShaded: c => c.qans === CQANS.black,
isUnshaded: c => c.qsub === CQSUB.green,
add_shaded: add_black,
add_unshaded: add_green,
OneNumPerRegion: false,
NoUnshadedNum: false,
});
SizeRegion_Cell({
isShaded: c => c.qsub === CQSUB.green,
isUnshaded: c => c.qans === CQANS.black,
add_shaded: add_green,
add_unshaded: add_black,
OneNumPerRegion: false,
NoUnshadedNum: false,
});
RectRegion_Cell({
isShaded: c => c.qans === CQANS.black,
isUnshaded: c => c.qsub === CQSUB.green,
add_shaded: add_black,
add_unshaded: add_green,
});
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (cell.qnum === 1 || cell.qnum === 2) {
add_black(cell);
}
// non-rect
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.black);
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.black && 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++) {
let cell = board.cell[i];
// 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.black).length === 4) {
add_black(cell);
}
}
flg = 0;
CellConnected({
isShaded: c => c.qans === CQANS.black,
isUnshaded: c => c.qsub === CQSUB.dot || c.qnum !== CQNUM.none,
add_shaded: c => add_black(c, true),
add_unshaded: add_dot,
});
No2x2Cell({
isShaded: c => c.qans === CQANS.black,
add_unshaded: add_dot,
});
SizeRegion_Cell({
isShaded: c => c.qsub === CQSUB.dot || c.qnum !== CQNUM.none,
isUnshaded: c => c.qans === CQANS.black,
add_shaded: add_dot,
add_unshaded: c => add_black(c, true),
});
// 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.includes(c)) { 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.includes(cell)) {
add_black(cell);
}
}
}
}
}
function GuideArrowAssist() {
BlackNotAdjacent();
GreenConnected();
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;
fourside(c => {
if (!c.isnull && c.qsub === CQSUB.green && vis.has(c)) { cnt++; }
}, adjcell);
if (cnt >= 2) add_black(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.black) 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.black) 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({
isShaded: c => c.anum === CANUM.wcir,
isUnshaded: c => c.anum === CANUM.bcir,
add_shaded: add_white,
add_unshaded: add_black,
});
CellConnected({
isShaded: c => c.anum === CANUM.bcir,
isUnshaded: c => c.anum === CANUM.wcir,
add_shaded: add_black,
add_unshaded: add_white,
});
No2x2Cell({
isShaded: c => c.anum === CANUM.wcir,
add_unshaded: add_black,
});
No2x2Cell({
isShaded: c => c.anum === CANUM.bcir,
add_unshaded: add_white,
});
NoCheckerCell({
isShaded: c => c.anum === CANUM.wcir,
isUnshaded: c => c.anum === CANUM.bcir,
add_shaded: add_white,
add_unshaded: add_black,
});
// 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);
}
}
}
}
// 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() {
No2x2Black();
No2x2Green();
CellConnected({
isShaded: c => c.qsub === CQSUB.green,
isUnshaded: c => c.qans === CQANS.black,
add_shaded: add_green,
add_unshaded: add_black,
isLinked: (c, nb, nc) => c.room === nc.room,
});
CellConnected({
isShaded: 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;
},
isUnshaded: c => c.qans === CQANS.black || c.ques === CQUES.tri,
add_shaded: add_green,
add_unshaded: () => { },
isLinked: (c, nb, nc) => c.room === nc.room,
});
let startcell = board.getc(board.startpos.bx, board.startpos.by);
let goalcell = board.getc(board.goalpos.bx, board.goalpos.by);
let circnt = 0;
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
circnt += 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.black)) {
cellList.forEach(c => add_black(c));
continue;
}
let circnt1 = circnt, circnt2 = circnt;
let templist = [];
cellList.forEach(c => {
let list = [offset(c, -1, 0), offset(c, 0, -1), offset(c, 0, 1), offset(c, 1, 0)];
list.forEach(c => {
if (c.isnull || templist.includes(c)) { return; }
templist.push(c);
});
});
templist = templist.filter(c => !c.isnull && c.qsub === CQSUB.green);
if (templist.length < 2) { continue; }
// no loop
let templist2 = templist.map(c => {
let dfslist = [];
let dfs = function (c) {
if (c.isnull || c.qsub !== CQSUB.green || dfslist.includes(c)) { return; }
dfslist.push(c);
fourside(dfs, c.adjacent);
};
dfs(c);
if (dfslist.some(c => c === startcell)) {
circnt1 = dfslist.filter(c => c.ques === CQUES.cir).length;
}
if (dfslist.some(c => c === goalcell)) {
circnt2 = dfslist.filter(c => c.ques === CQUES.cir).length;
}
return dfslist.filter(c => templist.includes(c)).length;
});
if (templist2.some(n => n > 1)) {
cellList.forEach(c => add_black(c));
continue;
}
// not enough cir
if (circnt1 + circnt2 < circnt) {
cellList.forEach(c => add_black(c));
continue;
}
// no branch for line
templist2 = templist.map(c => {
let res = 0;
let dfslist = [];
let dfs = function (c) {
if (c.isnull || c.qsub !== CQSUB.green || dfslist.includes(c)) { 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);
});
if (templist2.reduce((a, b) => a + b) > 2) {
cellList.forEach(c => add_black(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.black);
if (templist.length === 2) {
templist.forEach(c => add_green(c));
}
}
// surrounded by black
{
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.black).length === 4) {
add_black(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_black(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.black || cell.ques === CQUES.tri) {
fourside(add_cross, adjline);
}
if (cell.qans !== CQANS.black) {
let emptycnt = 0;
let linecnt = 0;
fourside((c, b) => {
if (!c.isnull && b.qsub !== BQSUB.cross) { emptycnt++; }
linecnt += b.line;
}, adjcell, adjline);
if (linecnt > 0) {
add_green(cell);
}
// no branch
if (linecnt === 2 || linecnt === 1 && (cell === startcell || cell === goalcell)) {
fourside(add_cross, adjline);
}
// no deadend
if (emptycnt === 1) {
if (cell !== startcell && cell !== goalcell) {
fourside(add_cross, adjline);
} else {
fourside((c, b) => {
if (!c.isnull && b.qsub !== BQSUB.cross) {
add_line(b);
}
}, adjcell, adjline);
}
}
// 2 degree path
if (emptycnt === 2 && cell !== startcell && cell !== goalcell && (linecnt === 1 || cell.ques === CQUES.cir)) {
fourside((c, b) => {
add_line(b);
if (!b.isnull && b.line) {
add_green(c);
}
}, adjcell, adjline);
}
// extend line
if (linecnt === 1 && cell !== startcell && cell !== goalcell ||
linecnt === 0 && (cell === startcell || cell === goalcell || cell.ques === CQUES.cir)) {
let fn = function (c, b, list) {
if (c.isnull || c.qsub !== CQSUB.green || list.includes(c)) { 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();
BlackNotAdjacent();
GreenConnected();
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
if (cell.qnum !== CQNUM.none) {
add_black(cell);
}
if (cell.qnum > 0) {
let templist = [];
let fn = function (c) {
if (c.qans !== CQANS.black) { return; }
if (templist.includes(c)) { 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() {
BlackConnected();
No2x2Black();
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_black(c));
}
if (templist.filter(c => c.qans === CQANS.black).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.includes(c)) { 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.black) { continue; }
templist2 = [];
let fn2 = function (c, step = 3) {
if (step < 0 || c.room !== room) { return; }
templist2.push(c);
fn2(c.adjacent.top, step - 1);
fn2(c.adjacent.bottom, step - 1);
fn2(c.adjacent.left, step - 1);
fn2(c.adjacent.right, step - 1);
}
fn2(room.clist[j]);
templist.forEach(c => {
if (!templist2.includes(c)) {
add_dot(c);
}
});
}
}
}
function NothreeAssist() {
BlackNotAdjacent();
GreenConnected();
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 blackcnt = 0;
let emptycnt = 0;
cellList.forEach(c => {
blackcnt += c.qans === CQANS.black;
emptycnt += c.qans !== CQANS.black && c.qsub !== CQSUB.dot;
});
if (blackcnt === 0 && emptycnt === 1) {
cellList.forEach(c => add_black(c));
}
if (blackcnt === 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.black).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.black);
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.black)) { continue; }
templist2 = templist2.filter(c => c.qsub !== CQSUB.dot);
if (templist1.length === 0 && templist2.length === 1) {
add_black(templist2[0]);
}
if (templist1.length === 1 && templist2.length === 0) {
add_green(templist1[0]);
}
}
}
}
}
function EkawayehAssist() {
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) {
let c1 = board.getc(tx / 2 - 1, ty / 2);
let c2 = board.getc(tx / 2 + 1, ty / 2);
if (c1.room === room) { add_green(c1); }
if (c2.room === room) { add_green(c2); }
}
if (rows % 2 === 0 && cols % 2 === 1) {
let c1 = board.getc(tx / 2, ty / 2 - 1);
let c2 = board.getc(tx / 2, ty / 2 + 1);
if (c1.room === room) { add_green(c1); }
if (c2.room === room) { add_green(c2); }
}
if (rows % 2 === 1 && cols % 2 === 1) {
let c = board.getc(tx / 2, ty / 2);
if (qnum >= 0 && qnum % 2 === 0 && c.room === room) {
add_green(c);
}
if (qnum >= 0 && qnum % 2 === 1 && c.room === room) {
add_black(c);
}
}
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.black) {
add_black(board.getc(tx - cell.bx, ty - cell.by));
}
}
}
HeyawakeAssist();
}
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 blacking 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.includes(c)) { 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 blacking edge including dot
let isEdgeEx = function (c, ndir) { // 0 = left, 1 = bottom, 2 = right, 3 = top
return isEdge(c, ndir) || isDotEdge(c);
};
// check blacking 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 blacking 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 tricnt = 0;
let emptycnt = 0;
fourside(c => {
if (!c.isnull && c.qans >= 2) { tricnt++; }
if (isEmpty(c)) { emptycnt++; }
}, 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 (tricnt === 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 (emptycnt === cell.qnum - tricnt) {
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() {
GreenConnected();
BlackNotAdjacent();
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
let adjcell = cell.adjacent;
let blackcnt = 0;
fourside(c => {
blackcnt += c.isnull || c.qans === CQANS.black;
}, 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 bordercnt = 0;
let emptycellList = [cell];
while (!pcell.isnull && pcell.qans !== CQANS.black && bordercnt < 2) {
if (dir(pcell.adjborder, d + 2).ques) {
bordercnt++;
}
emptycellList.push(pcell);
pcell = dir(pcell.adjacent, d);
}
emptycellList = emptycellList.filter(c => c.qsub !== CQSUB.green);
if (bordercnt === 2 && emptycellList.length === 1) {
add_black(emptycellList[0]);
}
}
}
const MAXSIT = 10000;
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 sitcnt = 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.black ? "BLK" : (cell.qsub === CQSUB.green ? "GRN" : "UNK")));
apl.set(cell, (cell.qans === CQANS.black ? "BLK" : (cell.qsub === CQSUB.green ? "GRN" : "UNK")));
}
if (qnum === list.filter(c => c.qans === CQANS.black).length) {
list.forEach(c => add_green(c));
continue;
}
// randomly chosen approximate formula
if (list.length > MAXAREA &&
(qnum - list.filter(c => c.qans === CQANS.black).length + 1) < list.filter(c => c.qans === CQANS.none && c.qsub === CQSUB.none).length) { continue; }
if ((qnum - list.filter(c => c.qans === CQANS.black).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.includes(c)) { return; }
if (c.qsub === CQSUB.green || c.qans === CQANS.black) { return; }
surlist.push(c);
apl.set(c, "GRN");
});
});
let dfs = function (i, blkcnt) {
if (sitcnt > MAXSIT) { return; }
if (i === list.length) {
if (blkcnt !== qnum) { return; }
let templist = [];
let templist2 = [];
if (list.some(c => {
if (cst.get(c) === "BLK") { return false; }
if (templist.includes(c)) { return false; }
let n = 0;
let olist = [];
let dfs = function (c) {
if (c.isnull || templist.includes(c)) { return false; }
if (c.room !== room) {
if (c.qans === CQANS.black) { 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.includes(c)) { 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");
});
return;
}
if (cst.get(list[i]) !== "UNK") { dfs(i + 1, blkcnt); return; }
sitcnt++;
let templist = [offset(list[i], -1, 0), offset(list[i], 0, -1), offset(list[i], 1, 0), offset(list[i], 0, 1)];
if (blkcnt < qnum && !templist.some(c => !c.isnull && c.qans === CQANS.black || cst.has(c) && cst.get(c) === "BLK")) {
cst.set(list[i], "BLK");
dfs(i + 1, blkcnt + 1);
cst.set(list[i], "UNK");
}
cst.set(list[i], "GRN");
dfs(i + 1, blkcnt);
cst.set(list[i], "UNK");
};
dfs(0, list.filter(c => c.qans === CQANS.black).length);
if (sitcnt > MAXSIT) { continue; }
list.forEach(c => {
if (apl.get(c) === "BLK") {
add_black(c);
}
if (apl.get(c) === "GRN") {
add_green(c);
}
});
surlist.forEach(c => {
if (apl.get(c) === "GRN") {
add_green(c);
}
});
}
}
function AkariAssist() {
let isEmpty = c => !c.isnull && c.qnum === CQNUM.none && c.qans !== CQANS.light && c.qsub !== CQSUB.dot;
let isNotLight = c => c.isnull || c.qnum !== CQNUM.none || c.qsub === CQSUB.dot
let add_light = function (c) { add_black(c, true); };
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
let adjcell = cell.adjacent;
let emptycnt = 0;
let lightcnt = 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]);
}
}
fourside(c => {
if (!c.isnull && c.qnum === CQNUM.none && c.qsub !== CQSUB.dot && c.qans !== CQANS.light) { emptycnt++; }
lightcnt += (c.qans === CQANS.light);
}, adjcell);
if (cell.qnum >= 0) {
// finished clue
if (cell.qnum === lightcnt) {
fourside(add_dot, adjcell);
}
// finish clue
if (cell.qnum === emptycnt + lightcnt) {
fourside(add_light, adjcell);
}
// dot at corner
if (cell.qnum - lightcnt + 1 === emptycnt) {
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({
isPass: c => c.qnum !== CQNUM.none,
});
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({
isPassable: c => c.ques !== CQUES.bwall,
isPass: c => c.ques !== CQUES.bwall,
});
}
function YajilinAssist() {
SingleLoopInCell({
isPassable: c => c.qnum === CQNUM.none,
isPass: c => c.qsub === CQSUB.dot,
add_notpass: c => add_black(c, true),
add_pass: add_dot,
});
let isPathable = c => !c.isnull && c.qnum === CQNUM.none && c.qans !== CQANS.black;
let isEmpty = c => !c.isnull && c.qnum === CQNUM.none && c.qans !== CQANS.black && c.qsub !== CQSUB.dot && c.lcnt === 0;
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
let adjcell = cell.adjacent;
let adjline = cell.adjborder;
// TODO: rewrite this
// check clue
if (cell.qnum >= 0 && cell.qdir !== QDIR.none) {
let d = qdirremap(cell.qdir);
let emptycnt = 0;
let blackcnt = 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)) {
emptycnt++;
emptycellList.push(pcell);
}
blackcnt += pcell.qans === CQANS.black;
if (isEmpty(lastcell) && isEmpty(pcell)) {
lastcell = cell;
emptycnt--;
} 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) {
blackcnt += pcell.qnum;
}
// finish clue
if (emptycnt + blackcnt === cell.qnum) {
addcellList.forEach(cell => add_black(cell, true));
}
// finished clue
if (blackcnt === cell.qnum) {
emptycellList.forEach(cell => add_dot(cell));
}
}
// add cross
if (cell.qnum !== CQNUM.none) {
fourside(add_cross, adjline);
continue;
}
// add dot around black
if (cell.qans === CQANS.black) {
fourside(add_cross, adjline);
fourside(add_dot, adjcell);
continue;
}
let emptycnt = 0;
let linecnt = 0;
fourside((b, c) => {
if (isPathable(c) && b.qsub !== BQSUB.cross) { emptycnt++; }
linecnt += b.line;
}, adjline, adjcell);
// no branch
if (linecnt === 2) {
fourside(add_cross, adjline);
}
// no deadend
if (emptycnt <= 1) {
add_black(cell);
fourside(add_cross, adjline);
fourside(add_dot, adjcell);
}
// 2 degree cell no deadend
if (emptycnt === 2) {
fourside((b, c) => {
if (!isPathable(c) || b.qsub === BQSUB.cross) { return; }
add_dot(c);
}, adjline, adjcell);
}
}
}
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); }
let isCross = b => b.isnull || b.qsub === BQSUB.cross
let isLine = b => b.line
let add_oneline = function (b1, b2) {
if (b1.qsub === BQSUB.cross || b1.isnull || b2.line) {
add_cross(b1);
add_line(b2);
}
if (b2.qsub === BQSUB.cross || b2.isnull || b1.line) {
add_cross(b2);
add_line(b1);
}
}
CellConnected({
isShaded: c => c.qsub === CQSUB.green,
isUnshaded: c => c.qsub === CQSUB.yellow || c.qsub === CQSUB.none && c.qnum === 3,
add_shaded: add_bg_inner_color,
add_unshaded: add_bg_outer_color,
isLinked: (c, nb, nc) => nb.qsub === BQSUB.cross,
isNotPassable: (c, nb, nc) => nb.line,
});
CellConnected({
isShaded: c => c.qsub === CQSUB.yellow,
isUnshaded: c => c.qsub === CQSUB.green || c.qsub === CQSUB.none && c.qnum === 3,
add_shaded: add_bg_outer_color,
add_unshaded: add_bg_inner_color,
isLinked: (c, nb, nc) => nb.qsub === BQSUB.cross,
isNotPassable: (c, nb, nc) => nb.line,
OutsideAsShaded: true,
});
NoCheckerCell({
isShaded: c => !c.isnull && c.qsub === CQSUB.green,
isUnshaded: c => c.isnull || c.qsub === CQSUB.yellow,
add_shaded: add_bg_inner_color,
add_unshaded: add_bg_outer_color,
});
// counting this due to some small loop joke
let twocnt = 0;
let threecnt = 0;
for (let i = 0; i < board.cell.length; i++) {
twocnt += board.cell[i].qnum === 2;
threecnt += board.cell[i].qnum === 3;
}
for (let i = 0; i < board.cell.length; i++) {
let cell = board.cell[i];
let blist = adjlist(cell.adjborder);
if (blist.filter(b => b.line).length === cell.qnum) {
blist.forEach(b => add_cross(b));
}
if (blist.filter(b => b.qsub !== BQSUB.cross).length === cell.qnum) {
blist.forEach(b => add_line(b));
}
// deduce single clue
// 1
// 2+3+
// 4c5
// +6+
let fn = function (c, b1, b2, b3, b4, b5, b6) {
// x x
// x+ + -> x+x+
// 1 x1
// + + + +
if (c.qnum === 1 && isCross(b1) && isCross(b2)) {
add_cross(b3);
add_cross(b4);
}
// x x
// x+ + -> x+-+
// 3 |3
// + + + +
if (c.qnum === 3 && isCross(b1) && isCross(b2)) {
add_line(b3);
add_line(b4);
}
// x x
// -+ + -+ +
// 1 -> 1x
// + + +x+
if (c.qnum === 1 && (isCross(b1) && isLine(b2) || isLine(b1) && isCross(b2))) {
add_cross(b5);
add_cross(b6);
}
// x |
// + + -+ + x+ +
// 1x -> 1x or 1x
// +x+ +x+ +x+
if (c.qnum === 1 && isCross(b5) && isCross(b6)) {
add_oneline(b1, b2);
}
// x
// -+ + -+ +
// 3 -> 3|
// + + +-+
if (c.qnum === 3 && (isLine(b1) || isLine(b2))) {
add_cross(b1);
add_cross(b2);
add_line(b5);
add_line(b6);
}
// x |
// + + -+ + x+ +
// 3| -> 3| or 3|
// +-+ +-+ +-+
if (c.qnum === 3 && isLine(b5) && isLine(b6)) {
add_oneline(b1, b2);
}
// x x
// x+ + x+x+
// 2| -> x2|
// + + +-+
if (c.qnum === 2 && isCross(b1) && isCross(b2) && (isLine(b5) || isLine(b6))) {
add_cross(b3);
add_cross(b4);
add_line(b5);
add_line(b6);
}
// x x
// x+ + x+-+
// 2x -> |2x
// + + +x+
if (c.qnum === 2 && isCross(b1) && isCross(b2) && (isCross(b5) || isCross(b6))) {
add_line(b3);
add_line(b4);
add_cross(b5);
add_cross(b6);
}
// x
// -+ + -+ +
// 2x -> 2x
// + + +-+
if (c.qnum === 2 && (isLine(b1) || isLine(b2)) && (isCross(b5) || isCross(b6))) {
add_cross(b1);
add_cross(b2);
add_line(b5);
add_line(b6);
}
// x |
// + + -+ + x+ +
// 2x -> 2x or 2x
// +-+ +-+ +-+
if (c.qnum === 2 && (isLine(b5) && isCross(b6) || isCross(b5) && isLine(b6))) {
add_oneline(b1, b2);
}
};
for (let d = 0; d < 4; d++) {
fn(cell, offset(cell, -1, -.5, d), offset(cell, -.5, -1, d),
offset(cell, 0, -.5, d), offset(cell, -.5, 0, d),
offset(cell, .5, 0, d), offset(cell, 0, .5, d),);
// + + + +-+ +
// 3 |3
// + + + -> + + +
// 3 3|
// + + + + +-+
if (cell.qnum === 3 && offset(cell, 1, 1, d).qnum === 3) {
add_line(offset(cell, 0, -.5, d));
add_line(offset(cell, -.5, 0, d));
add_line(offset(cell, 1.5, 1, d));
add_line(offset(cell, 1, 1.5, d));
}
// x x x x
// x+ + x+ +-
// 2 -> 2
// -+ + -+ +
// x
if (cell.qnum === 2 &&
isCross(offset(cell, -.5, -1, d)) && isCross(offset(cell, -1, -.5, d))) {
add_oneline(offset(cell, .5, -1, d), offset(cell, 1, -.5, d));
add_oneline(offset(cell, -1, .5, d), offset(cell, -.5, 1, d));
}
// x x
// x+ + x+ +
// 2 -> 2
// + +x + +x
// x
if (cell.qnum === 2 &&
isCross(offset(cell, -.5, -1, d)) && isCross(offset(cell, -1, -.5, d)) &&
(isCross(offset(cell, 1, .5, d)) || isCross(offset(cell, .5, 1, d)))) {
add_cross(offset(cell, 1, .5, d));
add_cross(offset(cell, .5, 1, d));
}
// x x
// x+ + x+-+
// 2 -> |2x
// + +- +x+-
// |
if (cell.qnum === 2 &&
isCross(offset(cell, -.5, -1, d)) && isCross(offset(cell, -1, -.5, d)) &&
(isLine(offset(cell, 1, .5, d)) || isLine(offset(cell, .5, 1, d)))) {
add_line(offset(cell, 0, -.5, d));
add_line(offset(cell, -.5, 0, d));
add_cross(offset(cell, .5, 0, d));
add_cross(offset(cell, 0, .5, d));
add_line(offset(cell, 1, .5, d));
add_line(offset(cell, .5, 1, d));
}
// x
// + + + + + +
// 3 3 -> |3|3|
// + + + + + +
// x
if (cell.qnum === 3 && (threecnt > 2 || twocnt > 0) &&
offset(cell, 1, 0, d).qnum === 3) {
add_line(offset(cell, -.5, 0, d));
add_line(offset(cell, .5, 0, d));
add_line(offset(cell, 1.5, 0, d));
add_cross(offset(cell, .5, -1, d));
add_cross(offset(cell, .5, 1, d));
}
// x
// + + + + + +
// x2 3 -> x2 3|
// + + + + + +
// x
if (cell.qnum === 2 && offset(cell, 1, 0, d).qnum === 3 && isCross(offset(cell, -.5, 0, d))) {
add_line(offset(cell, 1.5, 0, d));
add_cross(offset(cell, .5, -1, d));
add_cross(offset(cell, .5, 1, d));
}
// +x+ + +x+ +
// x1 x1
// + + + -> + + +
// 1 1x
// + + + + +x+
if (cell.qnum === 1 && offset(cell, 1, 1, d).qnum === 1 &&
isCross(offset(cell, 0, -.5, d)) && isCross(offset(cell, -.5, 0, d))) {
add_cross(offset(cell, 1.5, 1, d));
add_cross(offset(cell, 1, 1.5, d));
}
}
}
// connectivity at cross
for (let i = 0; i < board.cross.length; i++) {
let cross = board.cross[i];
let blist = adjlist(cross.adjborder);
let linecnt = blist.filter(b => b.line).length;
let crosscnt = blist.filter(b => b.qsub === BQSUB.cross).length;
if (linecnt === 2 || crosscnt === 3) {
blist.forEach(b => add_cross(b));
}
if (linecnt === 1 && crosscnt === 2) {
blist.forEach(b => add_line(b));
}
}
// 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
{
fourside((b, c) => {
if (!c.isnull && cell.qsub !== CQSUB.none && cell.qsub === c.qsub) {
add_cross(b);
}
if (cell.qsub === CQSUB.yellow && c.isnull) {
add_cross(b);
}
if (!c.isnull && cell.qsub !== CQSUB.none && c.qsub !== CQSUB.none && cell.qsub !== c.qsub) {
add_line(b);
}
if (cell.qsub === CQSUB.green && c.isnull) {
add_line(b);
}
}, adjline, adjcell);
}
// deduce neighbor color
if (cell.qsub === CQSUB.none) {
fourside((b, c) => {
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);
}
}, adjline, adjcell);
}
{
let innercnt = 0;
let outercnt = 0;
fourside(c => {
if (!c.isnull && c.qsub === CQSUB.green) { innercnt++; }
if (c.isnull || c.qsub === CQSUB.yellow) { outercnt++; }
}, adjcell);
// surrounded by green
if (innercnt === 4) {
add_bg_inner_color(cell);
}
// number and color deduce
if (cell.qnum >= 0) {
if (cell.qnum < innercnt || 4 - cell.qnum < outercnt) {
add_bg_inner_color(cell);
}
if (cell.qnum < outercnt || 4 - cell.qnum < innercnt) {
add_bg_outer_color(cell);
}
if (cell.qsub === CQSUB.green && cell.qnum === outercnt) {
fourside(add_bg_inner_color, adjcell);
}
if (cell.qsub === CQSUB.yellow && cell.qnum === innercnt) {
fourside(add_bg_outer_color, adjcell);
}
if (cell.qsub === CQSUB.yellow && cell.qnum === 4 - outercnt) {
fourside(add_bg_inner_color, adjcell);
}
if (cell.qsub === CQSUB.green && cell.qnum === 4 - innercnt) {
fourside(add_bg_outer_color, adjcell);
}
if (cell.qnum === 2 && outercnt === 2) {
fourside(add_bg_inner_color, adjcell);
}
if (cell.qnum === 2 && innercnt === 2) {
fourside(add_bg_outer_color, adjcell);
}
// 2 different color around 1 or 3
if ((cell.qnum === 1 || cell.qnum === 3) && innercnt === 1 && outercnt === 1) {
fourside((c, d) => {
if (!c.isnull && c.qsub === CQSUB.none) {
if (cell.qnum === 1) { add_cross(d); }
if (cell.qnum === 3) { add_line(d); }
}
}, 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) {
// x
// x+ +
// 2 A
// + +a
// Bb
for (let d = 0; d < 4; d++) {
let b1 = offset(cell, -.5, -1, d);
let b2 = offset(cell, -1, -.5, d);
if (!(b1.isnull || b1.qsub === BQSUB.cross)) { continue; }
if (!(b2.isnull || b2.qsub === BQSUB.cross)) { continue; }
let c1 = dir(adjcell, d + 2);
let c2 = dir(adjcell, d + 3);
// A=B
add_bg_color(c1, (c2.isnull ? CQSUB.yellow : c2.qsub));
add_bg_color(c2, (c1.isnull ? CQSUB.yellow : c1.qsub));
}
}
}
}
}
}