您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Do trivial deduction.
当前为
// ==UserScript== // @name Puzz.link Assistance // @version 24.12.1.0 // @description Do trivial deduction. // @author Leaving Leaves // @match https://puzz.link/p*/* // @match https://pzplus.tck.mn/p*/* // @match https://pzprxs.vercel.app/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 = 50; const MAXDFSCELLNUM = 200; let flg = true, flg2 = true; let step = false; let board; let GENRENAME; // used for showing pattern // ×·█━┃┓┛┗┏╺╹╸╻●○ // const list const CQNUM = { quesmark: -2, circle: -2, // no number black: -2, none: -1, wcir: 1, bcir: 2, // Moon or Sun sun: 1, moon: 2, }; const CANUM = { none: -1, // Yinyang wcir: 1, bcir: 2, }; const CQANS = { none: 0, black: 1, // Light and Shadow white: 2, // Starbattle star: 1, // Akari light: 1, // Shakashaka triangle triBL: 2, triBR: 3, triTR: 4, triTL: 5, // Slant rslash: 31, lslash: 32, }; const CQUES = { none: 0, // Castle Wall gray: 0, white: 1, black: 2, // Icebarn ice: 6, fire: 6, // Simpleloop wall: 7, // Slalom vgate: 21, hgate: 22, // Nurimaze cir: 41, tri: 42, }; const CQSUB = { none: 0, dot: 1, green: 1, circle: 1, // Slitherlink yellow: 2, // All or Nothing gray: 1, // Moon or Sun cross: 2, }; 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, }; const BQNUM = { none: -1, wcir: 1, bcir: 2, }; const CRQSUB = { none: 0, in: 1, out: 2, } const GENRELIST = [ ["Aho-ni-Narikire", AhoniNarikireAssist], ["Akari", AkariAssist], ["Akichiwake", AkichiwakeAssist], ["All or Nothing", AllorNothingAssist], // ["Angle Loop", AngleLoopAssist], ["Ant Mill", AntMillAssist], ["Aqre", AqreAssist], ["Aquapelago", AquapelagoAssist], ["Aquarium", AquariumAssist], ["Araf", ArafAssist], ["Arukone", ArukoneAssist], ["Ayeheya", AyeheyaAssist], ["Barns", BarnsAssist], ["Border Block", BorderBlockAssist], ["Bosanowa", BosanowaAssist], ["Bosnian Road", BosnianRoadAssist], ["Box", BoxAssist], ["Brownies", BrowniesAssist], ["Canal View", CanalViewAssist], ["Castle Wall", CastleWallAssist], ["Cave", CaveAssist], ["Chained Block", ChainedBlockAssist], ["Choco Banana", ChocoBananaAssist], ["Chocona", ChoconaAssist], ["Circles and Squares", CirclesAndSquaresAssist], ["Cocktail Lamp", CocktailLampAssist], ["Coffee Milk", CoffeeMilkAssist], ["Combi Block", CombiBlockAssist], ["Context", ContextAssist], ["Country Road", CountryRoadAssist], ["Creek", CreekAssist], ["Dominion", DominionAssist], ["Dosun-Fuwari", DosunFuwariAssist], ["Double Back", DoubleBackAssist], ["Double Choco", DoubleChocoAssist], ["Fillomino", FillominoAssist], ["Fire Walk", FireWalkAssist], ["FiveCells", FiveCellsAssist], ["FourCells", FourCellsAssist], ["Goats and Wolves", GoatsAndWolvesAssist], ["Guide Arrow", GuideArrowAssist], ["Hashiwokakero", HashiwokakeroAssist], ["Hebi-Ichigo", HebiIchigoAssist], ["Herugolf", HerugolfAssist], ["Heteromino", HeterominoAssist], ["Heyablock", HeyablockAssist], ["Heyawake", HeyawakeAssist], ["Hitori", HitoriAssist], ["Icebarn", IcebarnAssist], ["Icelom", IcelomAssist], ["Ice Walk", IceWalkAssist], ["Inverse LITSO", InverseLitsoAssist], ["Islands", IslandsAssist], ["Juosan", JuosanAssist], ["Kakuro", KakuroAssist], ["Koburin", KoburinAssist], ["Kropki", KropkiAssist], ["Kurodoko", KurodokoAssist], ["Light and Shadow", LightandShadowAssist], ["Litherslink", LitherslinkAssist], ["LITS", LitsAssist], ["Look-Air", LookAirAssist], ["Martini", MartiniAssist], ["Masyu", MasyuAssist], ["Maxi Loop", MaxiLoopAssist], ["Mejilink", MejilinkAssist], ["Mid-loop", MidloopAssist], ["Minarism", MinarismAssist], ["Minesweeper", MinesweeperAssist], ["Mirroring Tile", MirroringTileAssist], ["Mochikoro", MochikoroAssist], ["Moon or Sun", MoonOrSunAssist], ["Myopia", MyopiaAssist], ["Nanameguri", NanameguriAssist], ["Nawabari", NawabariAssist], ["NIKOJI", NikojiAssist], ["Nondango", NondangoAssist], ["Nonogram", NonogramAssist], ["Norinori", NorinoriAssist], ["Norinuri", NorinuriAssist], ["No Three", NothreeAssist], ["Numberlink", NumberlinkAssist], ["Nuribou", NuribouAssist], ["Nurikabe", NurikabeAssist], ["Nuri-Maze", NuriMazeAssist], ["Nurimisaki", NurimisakiAssist], ["One Room One Door", OneRoomOneDoorAssist], ["Paintarea", PaintareaAssist], ["Parquet", ParquetAssist], ["Pencils", PencilsAssist], ["Pentominous", PentominousAssist], ["Pipelink", PipelinkAssist], ["Putteria", PutteriaAssist], ["Rail Pool", RailPoolAssist], ["Rassi Silai", RassiSilaiAssist], ["Reflect Link", ReflectLinkAssist], ["Regional Yajilin", RegionalYajilinAssist], ["Ring-ring", RingringAssist], ["Ripple Effect", RippleEffectAssist], ["Sashigane", SashiganeAssist], ["School Trip", SchoolTripAssist], ["Shakashaka", ShakashakaAssist], ["Shikaku", ShikakuAssist], ["Simple Loop", SimpleloopAssist], ["Skyscrapers", SkyscrapersAssist], ["Slalom", SlalomAssist], ["Slant", SlantAssist], ["Slash Pack", SlashPackAssist], ["Slitherlink", SlitherlinkAssist], ["Snake", SnakeAssist], ["Square Jam", SquareJamAssist], ["Star Battle", StarbattleAssist], ["Stostone", StostoneAssist], ["Sudoku", SudokuAssist], ["Sukoro", SukoroAssist], ["Tapa", TapaAssist], ["Tapa-Like Loop", TapaLikeLoopAssist], ["Tasquare", TasquareAssist], ["Tatamibari", TatamibariAssist], ["Tawamurenga", TawamurengaAssist], ["Tentaisho", TentaishoAssist], ["Tents", TentsAssist], ["Tetrominous", TetrominousAssist], ["Tilepaint", TilepaintAssist], ["Toichika", ToichikaAssist], ["Tren", TrenAssist], ["Uso-one", UsoOneAssist], ["Uso-tatami", UsoTatamiAssist], ["Vertex Slitherlink", VertexSlitherlinkAssist], ["Voxas", VoxasAssist], ["Wall Logic", WallLogicAssist], ["Water Walk", WaterWalkAssist], ["Yajilin", YajilinAssist], ["Yajirushi 2", Yajirushi2Assist], ["Yajisan-Kazusan", YajisanKazusanAssist], ["Yin-Yang", YinyangAssist], ]; // main entrance let initDone = false; let main = function () { GENRENAME = ui.puzzle.info.en; if (initDone || GENRENAME === undefined) { return; } initDone = true; if (document.querySelector("#assist") !== null) { return; } console.log(`Puzzle Genre Name: ${GENRENAME}`); console.log(`Puzzle Link: ${window.location.href}`); console.log("Assistance running..."); let btnName = "Assist"; let btn2Name = "Assist Step"; if (!GENRELIST.some(g => g[0] === GENRENAME)) { console.log("Automatically generated assistant."); btnName += "(AG)"; btn2Name += "(AG)"; } let btn = `<button type="button" class="btn" id="assist" style="display: inline;">${btnName}</button>`; let btn2 = `<button type="button" class="btn" id="assiststep" style="display: inline;">${btn2Name}</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' || event.key === '`') { assist(); } if (event.key === 'w' || event.key === 'W') { assiststep(); } }); window.parent.postMessage("Ready to Assist", "*"); }; ui.puzzle.on('ready', main, false); let initTimer = setInterval(() => { if (initDone) { clearInterval(initTimer); return; } console.log("Puzz.link Assistance didn't launch. Relaunching..."); main(); }, 1000); // for postMessage window.addEventListener( "message", (event) => { if (event.data === "assist") { assist(); } if (event.data === "assiststep") { assiststep(); } if (event.data === "undo") { ui.puzzle.undo(); } if (event.data === "redo") { ui.puzzle.redo(); } }, false, ); function assiststep() { step = true; assist(); step = false; } function assist() { console.time("Assisted. Elapsed Time"); flg = true; board = ui.puzzle.board; for (let loop = 0; loop < MAXLOOP; loop++) { if (!flg && !flg2) { break; } flg = flg2 = false; if (GENRELIST.some(g => g[0] === GENRENAME)) { GENRELIST.find(g => g[0] === GENRENAME)[1](); } else { GeneralAssist(); } if (flg && step) { break; } } ui.puzzle.redraw(); console.timeEnd("Assisted. Elapsed Time"); window.parent.postMessage(ui.puzzle.check().complete ? "Solved" : "Not Solved", "*"); if (ui.puzzle.check().complete) { printBoard(); } } function printBoard() { // only some genres are able (i.e. looks good) to show in text. let res = ""; let hasSide = false; forEachCell(cell => fourside(b => hasSide ||= (!b.isnull && b.qans), cell.adjborder)); if (["Slitherlink", "Litherslink", "Myopia"].includes(GENRENAME)) { for (let i = 0; i < board.cross.length; i++) { let cross = board.cross[i]; let t; t |= cross.adjborder.top.line << 0; t |= cross.adjborder.left.line << 1; t |= cross.adjborder.bottom.line << 2; t |= cross.adjborder.right.line << 3; res += "·╹╸┛╻┃┓┫╺┗━┻┏┣┳╋"[t]; if (cross.bx === board.maxbx) { res += '\n'; } } } else if (GENRENAME === "Hashiwokakero") { forEachCell(cell => { res += (() => { if (cell.qnum === CQNUM.none && cell.lcnt > 0) { if (cell.adjborder.top.line === 1) return "│"; if (cell.adjborder.top.line === 2) return "║"; if (cell.adjborder.left.line === 1) return "─"; if (cell.adjborder.left.line === 2) return "═"; } if (cell.qnum !== CQNUM.none) { let num = cell.qnum; if (num === CQNUM.quesmark) return "?"; if (num >= 0 && num < 10) return "0123456789".split('')[num]; if (num >= 10 && num < 100) return num.toString(); return "#"; } return "·"; })(); if (cell.bx === board.cols * 2 - 1) { res += '\n'; } }); } else if (hasSide) { for (let i = 0; i < board.cross.length; i++) { let cross = board.cross[i]; let t = 0; let fn = (c => c.isnull || c.ques === CQUES.wall); t |= (fn(offset(cross, -.5, -.5)) ^ fn(offset(cross, +.5, -.5))) << 0; t |= (fn(offset(cross, -.5, -.5)) ^ fn(offset(cross, -.5, +.5))) << 1; t |= (fn(offset(cross, +.5, +.5)) ^ fn(offset(cross, -.5, +.5))) << 2; t |= (fn(offset(cross, +.5, +.5)) ^ fn(offset(cross, +.5, -.5))) << 3; t |= (cross.adjborder.top.qans) << 0; t |= (cross.adjborder.left.qans) << 1; t |= (cross.adjborder.bottom.qans) << 2; t |= (cross.adjborder.right.qans) << 3; res += "·╹╸┛╻┃┓┫╺┗━┻┏┣┳╋"[t]; if (cross.bx === board.maxbx) { res += '\n'; } } } else { let color = undefined; if (board.roommgr.components !== undefined) { color = Array(board.roommgr.components.length).fill(undefined); } // const palette = ["\x1b[31m\x1b[101m", "\x1b[32m\x1b[102m", "\x1b[33m\x1b[103m", "\x1b[36m\x1b[106m"]; const palette = ["\x1b[101m", "\x1b[102m", "\x1b[103m", "\x1b[106m"]; if (board.roommgr.components !== undefined) { let temp = board.roommgr.getSideAreaInfo().map(([a, b]) => a.id > b.id ? [a.id, b.id] : [b.id, a.id]); let edge = Array.from(Array(board.roommgr.components.length), i => []); temp.forEach(([a, b]) => edge[a].push(b)); for (let i = 0; i < color.length;) { if (color[i] === undefined) { color[i] = 0; } else { color[i]++; } while (edge[i].some(j => color[i] === color[j])) { color[i]++; } if (color[i] >= 4) { color[i] = undefined; i--; continue; } i++; } } let hasNum = false; forEachCell(cell => hasNum ||= cell.qnum !== 0); forEachCell(cell => { if (color !== undefined && cell.room !== null && color[cell.room.id] !== undefined) { res += palette[color[cell.room.id]]; } if (isIce(cell)) { res += "\x1b[106m"; } res += (() => { if (GENRENAME === "Akari") { if (isBlack(cell)) { return "○"; } if (cell.qnum !== CQNUM.none) { return "█"; } return "·"; } if (isBlack(cell) && GENRENAME === "Star Battle") { return "★" }; if (isBlack(cell) || [CQUES.wall, CQUES.white, CQUES.black].includes(cell.ques)) { return "█"; } if (cell.lcnt > 0) { let t; t |= cell.adjborder.top.line << 0; t |= cell.adjborder.left.line << 1; t |= cell.adjborder.bottom.line << 2; t |= cell.adjborder.right.line << 3; return "·╹╸┛╻┃┓┫╺┗━┻┏┣┳╋"[t]; } if (GENRENAME === "No Three") { return "·"; } if (GENRENAME === "Shakashaka" && cell.qnum !== CQNUM.none) { return "█"; } if (GENRENAME === "Shakashaka" && cell.qans !== CQANS.none) { return "··◣◢◥◤"[cell.qans]; } if (cell.anum !== -1) { let num = cell.anum; if (num === CQNUM.quesmark) return "?"; if (num >= 0 && num < 10) return "0123456789".split('')[num]; if (num >= 10 && num < 100) return num.toString(); return "#"; } if (cell.qnum !== CQNUM.none && cell.qdir !== 0) { let num = cell.qnum; let dir = ".^v<>"[cell.qdir]; if (num >= 0 && num < 10) return num.toString() + dir; if (num >= 10 && num < 36) return String.fromCharCode(num - 10 + 65) + dir; if (num >= 36) return "#" + dir; } if (hasNum && cell.qnum !== CQNUM.none && GENRENAME !== "Heyawake") { let num = cell.qnum; if (num === CQNUM.quesmark) return "?"; if (num >= 0 && num < 10) return "0123456789".split('')[num]; if (num >= 10 && num < 100) return num.toString(); return "#"; } if (GENRENAME === "Masyu" || GENRENAME === "Yinyang") { if (cell.qnum === CQNUM.bcir || cell.anum === CANUM.bcir) { return "●"; } if (cell.qnum === CQNUM.wcir || cell.anum === CANUM.wcir) { return "○"; } } return "·"; })(); if (isIce(cell)) { res += "\x1b[49m"; } if (cell.bx === board.cols * 2 - 1) { res += '\n'; } }); } console.log("Solution:\n" + res); } let isBlack = c => !c.isnull && c.qans === CQANS.black; let isntBlack = c => c.isnull || c.qsub === CQSUB.green; let isGreen = c => !c.isnull && c.qsub === CQSUB.green; let isYellow = c => !c.isnull && c.qsub === CQSUB.yellow; let isDot = isGreen; let isIce = c => !c.isnull && c.ques === CQUES.ice && (GENRENAME === "Barns" || GENRENAME.includes("Ice")); let isFire = c => !c.isnull && c.ques === CQUES.fire && (GENRENAME.includes("Fire")); let isNum = c => c.qnum !== CQNUM.none && c.qnum !== CQNUM.quesmark; let isClue = c => c.qnum !== CQNUM.none; let isSide = b => !b.isnull && (b.qans === 1 || !b.inside || b.ques === 1 || b.sidecell.some(c => c.ques === CQUES.wall)); let isLink = b => !b.isnull && b.qsub === BQSUB.link; let isntLink = b => b.isnull || isSide(b); let isLine = b => !b.isnull && b.line === 1; let isntLine = b => b.isnull || b.qsub === BQSUB.cross; let isCross = b => !b.isnull && b.qsub === BQSUB.cross; let isBound = b => b.ques === 1; let isInside = c => c.bx >= 0 && c.by >= 0 && c.bx <= board.cols * 2 && c.by <= board.rows * 2; let isEdge = o => o.bx === 0 || o.bx === board.cols * 2 || o.by === 0 || o.by === board.rows * 2; let isOcell = c => !c.isnull && c.qsub === CQSUB.circle; let isXcell = c => !c.isnull && c.qsub === CQSUB.cross; let offset = function (c, dx, dy, dir = 0) { // CCW as in coordinate 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, b = undefined) { if (b === undefined) { return [a.right, a.top, a.left, a.bottom]; } return [[a.right, b.right], [a.top, b.top], [a.left, b.left], [a.bottom, b.bottom]]; } let adjdiaglist = function (c) { return [[-1, -1], [1, -1], [1, 1], [-1, 1]].map(([x, y]) => offset(c, x, y)); } let adj8list = function (c) { return [[-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0]].map(([x, y]) => offset(c, x, y)); } 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 qdirRemap = function (qdir) { return [null, 1, 3, 2, 0][qdir]; } let getShape = function (clist, rot = true) { let minx = board.maxbx, miny = board.maxby, maxx = board.minbx, maxy = board.minby; for (let i = 0; i < clist.length; i++) { minx = Math.min(minx, clist[i].bx); miny = Math.min(miny, clist[i].by); maxx = Math.max(maxx, clist[i].bx); maxy = Math.max(maxy, clist[i].by); } let s = [[], [], [], [], [], [], [], []]; for (let y = 0; y <= maxy - miny; y += 2) { for (let x = 0; x <= maxx - minx; x += 2) { s[0].push(clist.includes(board.getc(minx + x, miny + y)) ? 1 : 0); s[1].push(clist.includes(board.getc(minx + x, maxy - y)) ? 1 : 0); } } if (!rot) { return s[0]; } for (let x = 0; x <= maxx - minx; x += 2) { for (let y = 0; y <= maxy - miny; y += 2) { s[4].push(clist.includes(board.getc(minx + x, miny + y)) ? 1 : 0); s[5].push(clist.includes(board.getc(minx + x, maxy - y)) ? 1 : 0); } } s[2] = s[1].concat().reverse(); s[3] = s[0].concat().reverse(); s[6] = s[5].concat().reverse(); s[7] = s[4].concat().reverse(); for (var h = 0; h < 8; h++) { s[h] = (h < 4 ? (maxx - minx) / 2 + 1 : (maxy - miny) / 2 + 1) + ":" + s[h].join(""); } s = s.sort(); return s[0]; } let cellTrail = function (c, sd, PassLine = false) { if (c.isnull) { return []; } let nc = offset(c, 1, 0, sd); if (nc.isnull) { return [c] }; let clist = [nc], lstd = sd; while (isIce(nc)) { nc = offset(nc, 1, 0, sd); clist.push(nc); } while (!nc.isnull && isInside(nc) && nc !== c && (PassLine || nc.lcnt === 0)) { let nd; let nnc = [0, 1, 2, 3].flatMap(d => { if ((lstd + 2) % 4 === d) { return []; } let nnb = offset(nc, .5, 0, d); let nnc = offset(nc, 1, 0, d); let nl = [nnc]; if (clist.length === 1 && nnc === c) { return []; } if (PassLine && nc.lcnt === 1 && !isLine(nnb)) { return []; } while (isIce(nnc)) { nnc = offset(nnc, 1, 0, d); nl.push(nnc); } if (nnc.isnull || isCross(nnb) || clist.includes(nnc)) { return []; } nd = d; return [nl]; }); if (nnc.length === 1) { nc = nnc[0][nnc[0].length - 1]; lstd = nd; clist.push(...nnc[0]); } else { break; } } return [c, ...clist]; } let getComb = l => l.reduce((a, b) => a.flatMap(i => b.flatMap(j => [[...i, j]])), [[]]); let roomId = room => board.roommgr.components.indexOf(room); let unique = l => Array.from(new Set(l)); function forEachCell(f = cell => { }) { for (let i = 0; i < board.cell.length; i++) { f(board.cell[i]); } } function forEachBorder(f = border => { }) { for (let i = 0; i < board.border.length; i++) { f(board.border[i]); } } function forEachCross(f = cross => { }) { for (let i = 0; i < board.cross.length; i++) { f(board.cross[i]); } } function forEachRoom(f = room => { }) { if (board.roommgr.components === undefined) { return; } for (let i = 0; i < board.roommgr.components.length; i++) { f(board.roommgr.components[i]); } } // set val 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 ||= isCross(b); }; let add_line = function (b) { if (b === undefined || b.isnull || b.line || isCross(b)) { 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_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, notOnNum = true) { if (notOnNum && (c.qnum !== CQNUM.none || c.qnums.length > 0)) { return; } if (c === undefined || c.isnull || c.qans !== CQANS.none || c.qsub !== CQSUB.none || c.anum !== CANUM.none) { return; } if (step && flg) { return; } flg ||= c.lcnt === 0; c.setQsub(CQSUB.dot); c.draw(); }; let add_green = function (c) { if (c === undefined || c.isnull || c.qans !== CQANS.none || c.qsub !== CQSUB.none) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.green); c.draw(); }; let add_yellow = function (c) { if (c === undefined || c.isnull || c.qans !== CQANS.none || c.qsub !== CQSUB.none) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.yellow); c.draw(); }; let add_inout = function (cr, qsub) { if (qsub === 3) { qsub = 1; } if (qsub === 0) { qsub = 2; } if (cr.isnull || cr.qsub !== CRQSUB.none || qsub === undefined) { return; } flg2 = true; cr.setQsub(qsub); cr.draw(); } let add_number = function (c, n) { if (c === undefined || c.isnull || c.anum !== CANUM.none) { return; } if (step && flg) { return; } flg = true; c.setAnum(n); c.draw(); }; let add_sudokunum = function (c, n) { if (c.isnull || c.anum !== -1) { return; } if (step && flg) { return; } if (c.qnum !== CQNUM.none) { flg2 = true; } else { flg = true; } c.snum.forEach((_, i) => c.setSnum(i, -1)); c.setAnum(n); c.draw(); } let add_candidate = function (c, l) { if (c.isnull || c.anum !== CANUM.none) { return; } l = Array.from(new Set(l.filter(n => n !== -1))).sort((a, b) => a - b); if (c.snum.some(n => n !== -1)) { l = l.filter(n => c.snum.includes(n)); } // i tried to add more number in snum but something weird keeps happening if (l.length === 0 || l.length > 4) { return; } if (l.length === 1) { add_sudokunum(c, l[0]); return; } while (l.length < 4) { l.push(-1); } if (c.snum.join(',') === l.join(',')) { return; } // if (l.length > 4) { l = [-1, -1, -1, -1, ...l]; } if (step && flg) { return; } flg = true; for (let i = l.length; i < c.snum.length; i++) { c.setSnum(i, -1); } l.forEach((n, i) => c.setSnum(i, n)); c.draw(); } let add_Ocell = function (c) { if (c === undefined || c.isnull || c.anum !== CANUM.none || c.qsub !== CQSUB.none || c.qnum !== CQNUM.none || c.qnums.length > 0) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.circle); c.draw(); }; let add_Xcell = function (c) { if (c === undefined || c.isnull || c.lcnt > 0 || c.anum !== CANUM.none || c.qsub !== CQSUB.none || c.qnum !== CQNUM.none || c.qnums.length > 0) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.cross); c.draw(); }; // single rule deduction function NShadeInClist({ isShaded = isBlack, isUnshaded = isGreen, add_shaded = add_black, add_unshaded = add_green, n, clist, AtLeastOne = false } = {}) { clist = clist.filter(c => c !== undefined && !c.isnull); if (AtLeastOne && clist.filter(c => !isUnshaded(c)).length === 1) { clist.filter(c => !isUnshaded(c)).forEach(c => add_shaded(c)); } if (n < 0) { return; } if (clist.filter(c => isShaded(c)).length === n) { clist.filter(c => !isShaded(c)).forEach(c => add_unshaded(c)); } if (clist.filter(c => !isUnshaded(c)).length === n) { clist.filter(c => !isUnshaded(c)).forEach(c => add_shaded(c)); } } function NShadeConnectedInRoom({ isShaded = isBlack, isUnshaded = isGreen, add_shaded = add_black, add_unshaded = add_green, room, qnum } = {}) { if (qnum < 0) { return; } let clist = Array.from(room.clist); if (qnum === 0) { clist.forEach(c => add_unshaded(c)); return; } if (clist.filter(c => !isUnshaded(c)).length === qnum) { clist.filter(c => !isUnshaded(c)).forEach(c => add_shaded(c)); } // unshade blank parts smaller than clue let cset = new Set(); let blkcnt = 0; clist.forEach(c => { if (isUnshaded(c) || cset.has(c)) { return; } let list = []; let dfs = function (c) { if (c.isnull || isUnshaded(c) || !clist.includes(c) || cset.has(c)) { return; } cset.add(c); list.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (isBound(nb)) { return; } dfs(nc); }) }; dfs(c); if (list.length < qnum) { list.forEach(c => add_unshaded(c)); } else { blkcnt++; } }); // unshade cells out of reach if (clist.some(c => isShaded(c))) { let cset = new Set(); let t = qnum - clist.filter(c => isShaded(c)).length; let queue = clist.flatMap(c => isShaded(c) ? [[c, t]] : []); while (queue.length > 0) { let [c, t] = queue.shift(); if (c.room !== room || t < 0 || cset.has(c)) { continue; } cset.add(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (isBound(nb)) { return; } queue.push([nc, t - 1]); }); } clist.forEach(c => !cset.has(c) ? add_unshaded(c) : null); } // find shaded cell without given shaded cell if (blkcnt === 1) { clist.forEach(c => { if (isUnshaded(c) || isShaded(c)) { return; } let fn = c => c.isnull || c.room !== room || isUnshaded(c); if (!([0, 1, 2, 3].some(d => fn(offset(c, -1, 0, d)) && [-1, 0, 1].some(y => fn(offset(c, 1, y, d))) || fn(offset(c, -1, -1, d)) && [[1, -1], [1, 0], [1, 1], [0, 1], [-1, 1]].some(([x, y]) => fn(offset(c, x, y, d)))))) { return; } let cset; let dfs = function (cc) { if (fn(cc) || cset.has(cc) || cc === c) { return; } cset.add(cc); fourside(dfs, cc.adjacent); }; if (adjlist(c.adjacent).every(nc => { cset = new Set(); dfs(nc); return cset.size < qnum; })) { add_shaded(c); } }); } } function NoDeadendBorder() { forEachCross(cross => { let list = adjlist(cross.adjborder); if (list.some(b => b.isnull)) { return; } if (list.filter(b => b.qans).length === 1 && list.filter(b => b.qsub !== BQSUB.link).length === 2) { list.forEach(b => add_side(b)); } if (list.filter(b => b.qsub !== BQSUB.link).length === 1) { list.forEach(b => add_link(b)); } }); } function NoCrossingBorder() { 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)); } } } function RoomPassOnce({ LengthClue = false, MaybeNotPassed = false } = {}) { CellConnected_InRegion({ isShaded: c => c.lcnt > 0, isUnshaded: c => adjlist(c.adjborder).every(b => isntLine(b)), add_shaded: () => { }, add_unshaded: () => { }, ByLine: true, }); forEachRoom(room => { let clist = Array.from(room.clist); let qnum = (room.top === undefined ? -1 : room.top.qnum); // cross out parts that can't reach in room let sc = clist.find(c => c.lcnt > 0); if (sc === undefined && MaybeNotPassed) { return; } if (sc !== undefined) { let cset = new Set(); let dfs = function (c) { if (c.isnull || c.room !== room || cset.has(c)) { return; } cset.add(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (nb.isnull || nb.ques || isCross(nb)) { return; } dfs(nc); }); }; dfs(sc); clist.filter(c => !cset.has(c)).forEach(c => { fourside(add_cross, c.adjborder) }); } let oblist = [], iblist = []; clist.forEach(cell => { fourside((nb, nc) => { if (!nc.isnull && nc.room !== room && nb.ques) { oblist.push(nb); } if (!nc.isnull && nc.room === room && !nb.ques && !iblist.includes(nb)) { iblist.push(nb); } if (!nc.isnull && nc.room === room && nb.ques && board.roommgr.components.length > 1) { add_cross(nb); } }, cell.adjborder, cell.adjacent); }); // 2 lines on whole boundry if (oblist.filter(b => isLine(b)).length === 2) { oblist.forEach(b => add_cross(b)); } if (oblist.filter(b => !isCross(b)).length === 2) { oblist.forEach(b => add_line(b)); } // 1 line between two rooms when there are over 2 rooms if (oblist.filter(b => isLine(b)).length === 1 && board.roommgr.components.length > 2) { let nroom = oblist.find(b => isLine(b)).sidecell.find(c => c.room !== room).room; oblist.forEach(b => b.sidecell.some(c => c.room === nroom) ? add_cross(b) : null); } let crlist = []; let e = new Map(); let v = new Set(); oblist.forEach(b => { let [s, t] = b.sidecross; e.set(s, [...(e.get(s) ?? []), t]); e.set(t, [...(e.get(t) ?? []), s]); v.add(s); v.add(t); }); // cell connected let ringflg = true; for (let i = 0; i < board.cols; i++) { ringflg &&= (board.getc(i * 2 + 1, 1).room === room); ringflg &&= (board.getc(i * 2 + 1, board.rows * 2 - 1).room === room); } for (let j = 0; j < board.rows; j++) { ringflg &&= (board.getc(1, j * 2 + 1).room === room); ringflg &&= (board.getc(board.cols * 2 - 1, j * 2 + 1).room === room); } if (!ringflg) { iblist.forEach(b => { if (!b.sidecell.every(c => c.qsub === CQSUB.circle)) { return; } if (!b.sidecross.every(cr => v.has(cr) || cr.bx === 0 || cr.by === 0 || cr.bx === board.cols * 2 || cr.by === board.rows * 2)) { return; } add_line(b); }); } // inside and outside are splited two parts on boundry // this part is like the outside in yinyang v = Array.from(v); let sv = (v.find(cr => e.get(cr).length === 1) ?? v[0]); while (sv !== undefined && crlist.length < v.length) { crlist.push(sv); sv = e.get(sv).find(cr => !crlist.includes(cr)); } let len = crlist.length; if (crlist.some(cr => cr.qsub === CRQSUB.in) && crlist.some(cr => cr.qsub === CRQSUB.out)) { for (let i = 0; i < len; i++) { if (crlist[i].qsub === CRQSUB.none || crlist[(i + 1) % len].qsub !== CRQSUB.none) { continue; } for (let j = (i + 1) % len; j != i; j = (j + 1) % len) { if (crlist[j].qsub === CRQSUB.in + CRQSUB.out - crlist[i].qsub) { break; } if (crlist[j].qsub === CRQSUB.none) { continue; } if (crlist[j].qsub === crlist[i].qsub) { for (let k = i; k != j; k = (k + 1) % len) { add_inout(crlist[k], crlist[i].qsub); } } } } } // cells that will cross the boundry let ocl = clist.filter(c => { if (adjlist(c.adjborder).some(b => isLine(b) && isBound(b))) { return true; } if (c.lcnt === 1 && adjlist(c.adjborder).every(b => isLine(b) || isntLine(b) || isBound(b))) { return true; } return false; }); if (ocl.length === 2) { oblist.forEach(b => b.sidecell.every(c => !ocl.includes(c)) ? add_cross(b) : null); } // parity if (ocl.length === 1 && LengthClue && qnum > 0 && oblist.filter(b => b.line).length < 2 && board.roommgr.components.length > 1) { let p = (ocl[0].bx + ocl[0].by + (qnum % 2 === 1 ? 0 : 2)) % 4; clist.forEach(c => { if (c === ocl[0]) { return; } if ((c.bx + c.by) % 4 === p) { return; } adjlist(c.adjborder).forEach(nb => isBound(nb) ? add_cross(nb) : null); }); } // no exiting without going through every circle if (ocl.length === 1 && (clist.some(c => c.qsub === CQSUB.circle && c.lcnt === 0) || qnum > clist.filter(c => c.lcnt > 0).length)) { let c = clist.find(c => c.lcnt === 1 && c !== ocl[0] && c.path === ocl[0].path); if (!c === undefined) { adjlist(c.adjborder).forEach(nb => isBound(nb) ? add_cross(nb) : null); } c = ocl[0]; if (adjlist(c.adjborder).some(b => isLine(b) && isBound(b))) { adjlist(c.adjborder).forEach(b => isBound(b) ? add_cross(b) : null); } if (adjlist(c.adjborder).filter(b => !isBound(b) && !isntLine(b)).length === 1) { add_line(adjlist(c.adjborder).find(b => !isBound(b) && !isntLine(b))) } } }); // Hamiltonian cycle if (board.roommgr.components.length > 2 && !MaybeNotPassed) { let g = new Map(); let blist = [], lset = new Set(); let DSU = new Map(); // Disjoint Set Union let DSUfind = function (n) { if (DSU.get(n) !== n) { DSU.set(n, DSUfind(DSU.get(n))); } return DSU.get(n); }; forEachBorder(b => { if (!isBound(b) || isCross(b) || b.sidecell[0].room === b.sidecell[1].room) { return; } let [s, t] = b.sidecell.map(c => roomId(c.room)); if (!g.has(s)) { g.set(s, new Set()); DSU.set(s, s); } if (!g.has(t)) { g.set(t, new Set()); DSU.set(t, t); } g.get(s).add(t); g.get(t).add(s); blist.push(b); if (isLine(b)) { lset.add(JSON.stringify([s, t])); lset.add(JSON.stringify([t, s])); DSU.set(DSUfind(s), DSUfind(t)); } }); let lflg = true; while (lflg) { lflg = false; g.forEach((tset, s) => { if (tset.size !== 2) { return; } Array.from(tset).forEach(t => { if (!lset.has(JSON.stringify([s, t]))) { lflg = true; lset.add(JSON.stringify([s, t])); lset.add(JSON.stringify([t, s])); DSU.set(DSUfind(s), DSUfind(t)); } }); }); g.forEach((tset, s) => { let tl = Array.from(tset).filter(t => !lset.has(JSON.stringify([s, t]))); tl.forEach(t => { if (tl.length === tset.size - 2 || DSUfind(s) === DSUfind(t) && Array.from(DSU.values()).some(n => DSUfind(n) !== DSUfind(0))) { lflg = true; g.get(s).delete(t); g.get(t).delete(s); } }); }); } blist.forEach(b => { let [s, t] = b.sidecell.map(c => roomId(c.room)); if (!g.get(s).has(t)) { add_cross(b); } }); lset.forEach(e => { let l = blist.filter(b => JSON.stringify(b.sidecell.map(c => roomId(c.room)).sort()) === e); if (l.length === 1) { add_line(l[0]); } }); } // TODO: double bridge / size-2 cut } function NoFacingDoor() { forEachCell(cell => { for (let d = 0; d < 4; d++) { if (!isGreen(cell)) { break; } let pcell = offset(cell, 1, 0, d); let bordercnt = 0; let emptycellList = [cell]; while (!pcell.isnull && !isBlack(pcell) && bordercnt < 2) { if (offset(pcell, -.5, 0, d).ques) { bordercnt++; } emptycellList.push(pcell); pcell = offset(pcell, 1, 0, d); } emptycellList = emptycellList.filter(c => !isGreen(c)); if (bordercnt === 2 && emptycellList.length === 1) { add_black(emptycellList[0]); } } }); } function No2x2Cell({ isShaded, add_unshaded } = {}) { forEachCell(cell => { let templist = [cell, offset(cell, 1, 0), offset(cell, 0, 1), offset(cell, 1, 1)]; if (templist.some(c => c.isnull)) { return; } templist = templist.filter(c => !isShaded(c)); if (templist.length === 1) { add_unshaded(templist[0]); } }); } function No2x2Black() { No2x2Cell({ isShaded: isBlack, add_unshaded: add_green, }); } function No2x2Green() { No2x2Cell({ isShaded: isGreen, add_unshaded: add_black, }); } function CellConnected({ isShaded, isUnshaded, add_shaded, add_unshaded, isLinked = (c, nb, nc) => isLine(nb) || isLink(nb), isNotPassable = (c, nb, nc) => false, cantDivideShade = (s, o) => s > 0, isOthers = () => false, OutsideAsShaded = false, OnlyOneConnected = true, ConnectedInRegion = false, SizeInRegion = false, ForceSearch = false, UnshadeEmpty = true, DiagDir = false, Obj = "cell", BridgeType = "none" } = {}) { let forEachObj = (Obj === "cell" ? forEachCell : forEachCross); // use tarjan to find cut vertex let n = 0; let ord = new Map(); let low = new Map(); let shdn = new Map(); let otcn = new Map(); let cnt = new Map(); let fth = new Map(); let vst = new Set(); let shadelist = []; let bridgelist = []; // to avoid Maximum call stack size exceeded, manually use a stack to track the cells let dfs = function (sc) { let stack = [{ cell: sc, father: null, visited: false }]; while (stack.length > 0) { let cur = stack[stack.length - 1]; let c = cur.cell; let f = cur.father; let v = cur.visited; if (!v) { if (!c.isnull && isUnshaded(c) || ord.has(c)) { stack.pop(); continue; } if (c.isnull && !OutsideAsShaded) { stack.pop(); continue; } ord.set(c, n); low.set(n, n); shdn.set(n, 0); otcn.set(n, 0); cnt.set(n, 0); fth.set(c, f); n++; stack[stack.length - 1] = { cell: c, father: f, visited: true }; } else { stack.pop(); } if (!c.isnull) { const cellset = new Set(); let linkdfs = function (c) { if (c.isnull || !isInside(c) || cellset.has(c) || isUnshaded(c)) { return; } cellset.add(c); for (let d = 0; d < 4; d++) { let nb = offset(c, .5, 0, d); let nc = offset(c, 1, 0, d); while (isIce(nc)) { nc = offset(nc, 1, 0, d); } if (isNotPassable(c, nb, nc)) { continue; } if (nb.isnull || nc.isnull) { continue; } if (isLinked(c, nb, nc)) { linkdfs(nc); } } } linkdfs(c); if (!v) { cellset.forEach(cl => { ord.set(cl, ord.get(c)); shdn.set(ord.get(c), shdn.get(ord.get(c)) + isShaded(cl)); otcn.set(ord.get(c), otcn.get(ord.get(c)) + isOthers(cl)); fth.set(cl, f); }); cnt.set(ord.get(c), cellset.size); } let cn = cellset.size; let fn = function (c, nb, nc) { if (isNotPassable(c, nb, nc)) { return; } if (nc.isnull && !OutsideAsShaded) { return; } if (nc === f || f !== null && ord.get(f) === ord.get(nc) || isUnshaded(nc)) { return; } if (ord.get(c) === ord.get(nc)) { return; } if (ord.has(nc) && ord.get(nc) < ord.get(c)) { low.set(ord.get(c), Math.min(low.get(ord.get(c)), ord.get(nc))); return; } if (!v) { stack.push({ cell: nc, father: c, visited: false }); } if (v && c === fth.get(nc)) { let ordc = ord.get(c); let ordnc = ord.get(nc); low.set(ordc, Math.min(low.get(ordc), low.get(ordnc))); if (!vst.has(ordnc)) { shdn.set(ordc, shdn.get(ordc) + shdn.get(ordnc)); otcn.set(ordc, otcn.get(ordc) + otcn.get(ordnc)); cnt.set(ordc, cnt.get(ordc) + cnt.get(ordnc)); vst.add(ordnc); } if (!cellset.has(sc) && ordc <= low.get(ordnc)) { cn += cnt.get(ordnc); if (cantDivideShade(shdn.get(ordnc), otcn.get(ordnc))) { cellset.forEach(c => shadelist.push(c)); } } if (cantDivideShade(shdn.get(ordnc), otcn.get(ordnc)) && ordc < low.get(ordnc)) { bridgelist.push([ordc, ordnc]); } } }; let tmp = new Set(); for (let c of cellset) { for (let d = 0; d < 4; d++) { let nb = offset(c, .5, 0, d); let nc = offset(c, 1, 0, d); while (isIce(nc)) { nc = offset(nc, 1, 0, d); } if (fth.get(nc) === sc) { tmp.add(ord.get(nc)); } fn(c, nb, nc); } if (DiagDir) { for (let d = 0; d < 4; d++) { let nc = offset(c, 1, 1, d); if (fth.get(nc) === sc) { tmp.add(ord.get(nc)); } fn(c, undefined, nc); } } } if (ConnectedInRegion && SizeInRegion && c.room.top.qnum >= 0 && Array.from(c.room.clist).filter(c => !isUnshaded(c)).length - cn < c.room.top.qnum) { cellset.forEach(c => shadelist.push(c)); } if (v && c === sc && tmp.size > 1) { cellset.forEach(c => shadelist.push(c)); } } if (!v && c.isnull) { if (Obj === "cell") { for (let i = 0; i < board.cols; i++) { stack.push({ cell: board.getobj(2 * i + 1, board.minby + 1), father: c, visited: false }); stack.push({ cell: board.getobj(2 * i + 1, board.maxby - 1), father: c, visited: false }); } for (let i = 0; i < board.rows; i++) { stack.push({ cell: board.getobj(board.minbx + 1, 2 * i + 1), father: c, visited: false }); stack.push({ cell: board.getobj(board.maxbx - 1, 2 * i + 1), father: c, visited: false }); } } } } } if (OutsideAsShaded) { dfs(board.emptycell); } else { forEachObj(cell => { if (!ForceSearch && (!isShaded(cell) || ord.has(cell))) { return; } if (n > 0 && OnlyOneConnected) { return; } dfs(cell); }); } shadelist.forEach(c => add_shaded(c)); if (ord.size > 0 && UnshadeEmpty) { forEachObj(cell => { if (ord.has(cell) || isShaded(cell) || isUnshaded(cell) || isIce(cell)) { return; } add_unshaded(cell); }); } if (ConnectedInRegion) { forEachRoom(room => { let clist = Array.from(room.clist); if (clist.some(c => isShaded(c))) { clist.filter(c => !ord.has(c)).forEach(c => add_unshaded(c)); } }); } bridgelist.forEach(([s, t]) => { // bridge let l = Array.from(board.border).filter(b => !isNotPassable(b.sidecell[0], b, b.sidecell[1]) && b.sidecell.map(c => ord.get(c)).sort().join(',') === [s, t].sort().join(',')); if (l.length === 1 && BridgeType === "line") { l.forEach(b => add_line(b)); } if (BridgeType === "link") { l.forEach(b => add_link(b)); } }); } function GreenConnected() { CellConnected({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, }); } function BlackConnected() { CellConnected({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, }); } function GreenConnectedDiagonally() { CellConnected({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, DiagDir: true, }); } function CellConnected_InRegion({ isShaded, isUnshaded, add_shaded, add_unshaded, SizeClue = false, ByLine = false } = {}) { CellConnected({ isShaded: isShaded, isUnshaded: isUnshaded, add_shaded: add_shaded, add_unshaded: add_unshaded, isNotPassable: (c, nb, nc) => isBound(nb) || ByLine && isCross(nb), BridgeType: ByLine ? "line" : "none", OnlyOneConnected: false, UnshadeEmpty: false, ConnectedInRegion: true, SizeInRegion: SizeClue, }); if (SizeClue) { forEachRoom(room => NShadeConnectedInRoom({ isShaded: isShaded, isUnshaded: isUnshaded, add_shaded: add_shaded, add_unshaded: add_unshaded, room: room, qnum: room.top.qnum, })); } } function BlackConnected_InRegion(SizeClue = false) { CellConnected_InRegion({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, SizeClue: SizeClue, }); } function CluePerRegion({ isShaded, isUnshaded, add_shaded = () => { }, add_unshaded = () => { }, isNotPassable = (c, nb, nc) => false, isOthers = () => false, cantDivideShade = (s, o) => s === 0 && o > 0, BridgeType = "none" } = {}) { CellConnected({ isShaded: isShaded, isUnshaded: isUnshaded, add_shaded: add_shaded, add_unshaded: add_unshaded, isNotPassable: isNotPassable, cantDivideShade: cantDivideShade, isOthers: isOthers, OnlyOneConnected: false, UnshadeEmpty: true, BridgeType: BridgeType, }); }; function CellNoLoop({ isShaded, isUnshaded, add_unshaded } = {}) { let ord = new Map(); let n = 0; forEachCell(cell => { if (!isShaded(cell) || ord.has(cell)) { return; } let dfs = function (c) { if (c.isnull || !isShaded(c) || ord.has(c)) { return; } ord.set(c, n); fourside(dfs, c.adjacent); } dfs(cell); n++; }); forEachCell(cell => { if (isShaded(cell) || isUnshaded(cell)) { return; } 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: isGreen, isUnshaded: isBlack, add_unshaded: add_black, }); } function BlackNotAdjacent() { forEachCell(cell => { if (!isBlack(cell)) { return; } fourside(add_green, cell.adjacent); }); } function BlackNotAdjacent_OverBorder() { forEachCell(cell => { if (!isBlack(cell)) { return; } for (let d = 0; d < 4; d++) { let nb = offset(cell, 0.5, 0, d); let nc = offset(cell, 1, 0, d) if (!nb.isnull && nb.ques) { add_green(nc); } } }); } function BlackDomino() { forEachCell(cell => { let list = adjlist(cell.adjacent); // surrounded by dot if (list.every(c => c.isnull || isGreen(c))) { add_green(cell); } // extend domino if (isBlack(cell) && list.filter(c => !isntBlack(c)).length === 1) { let ncell = list.find(c => !isntBlack(c)); add_black(ncell); } // finished domino if (isBlack(cell) && list.some(c => isBlack(c))) { fourside(add_dot, cell.adjacent); } // not making triomino if (list.filter(c => isBlack(c)).length >= 2 || list.every(c => isntBlack(c) || adjlist(c.adjacent).some(cc => isBlack(cc)))) { add_green(cell); } for (let d = 0; d < 4; d++) { // · · // ·█ -> ·█ // · if (isBlack(cell) && (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)); } } }); } function SingleLoopInCell({ isPassable = c => true, isPathable = b => !isCross(b), isPass = c => c.qsub === CQSUB.dot, isPath = b => b.line, add_notpass = c => { }, add_pass = c => { }, add_notpath = add_cross, add_path = add_line, Directed = false, hasCross = false } = {}) { 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 genlist = c => { if (c.adjborder === undefined) { return []; } return [[c.adjborder.top, BQSUB.arrow_up, BQSUB.arrow_dn], [c.adjborder.bottom, BQSUB.arrow_dn, BQSUB.arrow_up], [c.adjborder.left, BQSUB.arrow_lt, BQSUB.arrow_rt], [c.adjborder.right, BQSUB.arrow_rt, BQSUB.arrow_lt]]; }; let has_in = function (c) { let templist = genlist(c); templist = templist.filter(b => !b[0].isnull && b[0].line); return templist.some(([b, o_arr, i_arr]) => b.qsub === i_arr); } let has_out = function (c) { let templist = genlist(c); templist = templist.filter(b => !b[0].isnull && b[0].line); return templist.some(([b, o_arr, i_arr]) => b.qsub === o_arr); } let initied = false; forEachCross(cross => { initied ||= cross.qsub === CRQSUB.in || cross.qsub === CRQSUB.none; }) if (!initied) { forEachCross(cross => cross.setQsub(CRQSUB.none)); } let hasIce = false; forEachCell(cell => { hasIce ||= isIce(cell); }); if (!hasIce && !hasCross) { CellConnected({ isShaded: cr => cr.qsub === CRQSUB.in, isUnshaded: cr => cr.qsub === CRQSUB.out, add_shaded: cr => add_inout(cr, CRQSUB.in), add_unshaded: cr => add_inout(cr, CRQSUB.out), isLinked: (c, nb, nc) => isCross(nb) || c.qsub === CRQSUB.in && nc.qsub === CRQSUB.in, isNotPassable: (c, nb, nc) => nb.line, Obj: "cross", }); CellConnected({ isShaded: cr => cr.qsub === CRQSUB.out, isUnshaded: cr => cr.qsub === CRQSUB.in, add_shaded: cr => add_inout(cr, CRQSUB.out), add_unshaded: cr => add_inout(cr, CRQSUB.in), isLinked: (c, nb, nc) => isCross(nb) || c.qsub === CRQSUB.out && nc.qsub === CRQSUB.out, isNotPassable: (c, nb, nc) => nb.line, Obj: "cross", }); } // don't form a loop of crossed borders when some cells inbetween should be passed if ((hasIce || hasCross) && isPass(board.emptycell)) { let ord = new Map(); let n = 0; forEachCross(cross => { if (ord.has(cross)) { return; } n++; let DFS = function (cr) { if (cr.isnull || ord.has(cr)) { return; } ord.set(cr, n); for (let d = 0; d < 4; d++) { let ncr = offset(cr, 1, 0, d); if (!ncr.isnull && (isCross(offset(cr, .5, 0, d)) || cr.qsub !== CRQSUB.none && cr.qsub === ncr.qsub)) { DFS(ncr); } } } DFS(cross); }); let m = new Map(); forEachBorder(border => { let t = border.sidecross.map(cr => ord.get(cr)).sort(); if (t[0] === t[1]) { return; } t = t.join(','); if (!m.has(t)) { m.set(t, []); } m.get(t).push(border); }); Array.from(m).forEach(([_, l]) => { // TODO: find a better way to cover all cases if (l.length <= 1 || !l.some(c => c.sidecell.every(c => isInside(c) && !isIce(c)))) { return; } l.forEach(b => add_line(b)); }); } forEachCell(cell => { if (isIce(cell)) { let fn = (b1, b2) => { if (b1.line) { add_line(b2); } if (b1.isnull || isCross(b1)) { add_cross(b2) }; } for (let d = 0; d < 4; d++) { let ncell = cell; while (isIce(ncell)) { fn(offset(ncell, .5, 0, d), offset(ncell, -.5, 0, d)); ncell = offset(ncell, 1, 0, d); } } } }); forEachCell(cell => { // avoid forming multiple loop if (cell.path !== null && !isIce(cell) && !hasCross) { for (let d = 0; d < 4; d++) { let ncell = cellTrail(cell, d).pop(); if (cell.lcnt === 1 && ncell.lcnt === 1 && cell.path === ncell.path && board.linegraph.components.length > 1) { add_notpath(offset(cell, .5, 0, d)); } if (cell === ncell && board.linegraph.components.length > 1) { add_notpath(offset(cell, .5, 0, d)); } } } }) forEachCell(cell => { if (!isPassable(cell)) { add_notpass(cell); fourside(add_notpath, cell.adjborder); } let emptycnt = 0; let linecnt = 0; fourside((c, b) => { if (!isPassable(c) || !isPathable(b)) { add_notpath(b); } if (!c.isnull && isPassable(c) && isPathable(b)) { emptycnt++; } linecnt += isPath(b); }, cell.adjacent, cell.adjborder); if (linecnt > 0) { add_pass(cell); } // no branch and no cross if (linecnt === 2 && !isIce(cell) && !hasCross) { fourside(add_notpath, cell.adjborder); } // no deadend if (emptycnt <= 1) { fourside(add_notpath, cell.adjborder); add_notpass(cell); } // cross if (linecnt === 3 && hasCross) { fourside(add_path, cell.adjborder); } // 2 degree path if (emptycnt === 2 && (linecnt === 1 || isPass(cell))) { fourside(add_path, cell.adjborder); } if (linecnt === 0 && !isIce(cell) && !hasCross) { let list = adjlist(cell.adjborder, cell.adjacent); list = list.map(([nb, nc], d) => { while (isIce(nc)) { nc = offset(nc, 1, 0, d); } return [nb, nc]; }); list = list.filter(([nb, nc]) => !nb.isnull && !nc.isnull && isPathable(nb)); if (list.length > 0 && list[0][1].path !== null && list.every(([nb, nc]) => nc.path === list[0][1].path && board.linegraph.components.length > 1)) { fourside(add_notpath, cell.adjborder); } // ┏╸ ┏╸ // ┃· -> ┃╺━ // ┗╸ ┗╸ if (cell.lcnt === 0 && isPass(cell)) { list.forEach(([nb, nc]) => { let t = list.filter(e => e[0] !== nb); if (t[0][1].path !== null && t.every(([nb, nc]) => nc.path === t[0][1].path) && board.linegraph.components.length > 1) { add_path(nb); } }); } } // extend arrow if (Directed && cell.lcnt === 2 && !isIce(cell)) { let list = genlist(cell); list = list.filter(b => b[0].line); if (list.filter(b => b[0].qsub === BQSUB.none).length === 1) { if (list[0][0].qsub !== BQSUB.none) { list = [list[1], list[0]]; } if (list[1][0].qsub === list[1][1]) { add_arrow(list[0][0], list[0][2]); } if (list[1][0].qsub === list[1][2]) { add_arrow(list[0][0], list[0][1]); } } } // choose path if (Directed && cell.lcnt === 1 && !isIce(cell)) { for (let d = 0; d < 4; d++) { let ncell = offset(cell, 1, 0, d); while (!ncell.isnull && isIce(ncell)) { ncell = offset(ncell, 1, 0, d); } if (ncell.isnull || ncell.lcnt !== 1 || offset(ncell, -.5, 0, d).line) { continue; } if (has_in(cell) && has_in(ncell) || has_out(cell) && has_out(ncell)) { add_cross(offset(cell, .5, 0, d)); } } } }); // this is here because the following two CellConnected may have bug if line on ice isn't fully labeled with arrow forEachCell(cell => { if (!isIce(cell)) { return; } for (let d = 0; d < 4; d++) { let pcell = cell; while (isIce(pcell) && offset(pcell, .5, 0, d).qsub === BQSUB.cross && offset(pcell, -.5, 0, d).qsub === BQSUB.cross) { add_cross(offset(pcell, -.5, 0, d)); pcell = offset(pcell, -1, 0, d); } pcell = cell; while (isIce(pcell) && offset(pcell, .5, 0, d).line && (!offset(pcell, -.5, 0, d).line || offset(pcell, .5, 0, d).qsub !== BQSUB.none && offset(pcell, -.5, 0, d).qsub === BQSUB.none)) { add_line(offset(pcell, -.5, 0, d)); if (offset(pcell, .5, 0, d).qsub !== BQSUB.none) { add_arrow(offset(pcell, -.5, 0, d), offset(pcell, .5, 0, d).qsub); } pcell = offset(pcell, -1, 0, d); } } }); if (Directed) { // used to avoid all in/out arrow region CellConnected({ isShaded: c => !has_out(c) && has_in(c), isUnshaded: c => has_out(c), add_shaded: () => { }, add_unshaded: c => fourside(add_cross, c.adjborder), isNotPassable: (c, nb, nc) => isCross(nb), OnlyOneConnected: false, }); CellConnected({ isShaded: c => !has_in(c) && has_out(c), isUnshaded: c => has_in(c), add_shaded: () => { }, add_unshaded: c => fourside(add_cross, c.adjborder), isNotPassable: (c, nb, nc) => isCross(nb), OnlyOneConnected: false, }); } add_inout(board.getobj(0, 0), !Directed ? CRQSUB.out : .5); // add invisible qsub at cross if (!hasIce && !hasCross) { forEachCross(cross => { // no checker if (cross.qsub === CRQSUB.none) { let fn = function (cr, cr1, cr2, cr12) { if (cr1.isnull || cr2.isnull || cr12.isnull) { return; } if (cr1.qsub === CRQSUB.none || cr2.qsub === CRQSUB.none || cr12.qsub === CRQSUB.none) { return; } if (cr1.qsub === cr2.qsub && cr1.qsub !== cr12.qsub) { add_inout(cr, cr1.qsub); } }; for (let d = 0; d < 4; d++) { if (!isIce(offset(cross, .5, .5, d))) fn(cross, offset(cross, 1, 0, d), offset(cross, 0, 1, d), offset(cross, 1, 1, d)); } } }); } let di = -1; if (Directed && !hasIce && !hasCross) { forEachBorder(border => { let l = [[BQSUB.arrow_up, CRQSUB.out], [BQSUB.arrow_dn, CRQSUB.in], [BQSUB.arrow_lt, CRQSUB.in], [BQSUB.arrow_rt, CRQSUB.out]]; if (di !== -1) { return; } if ([11, 12, 13, 14].includes(border.qsub)) { if (border.sidecross[0].qsub !== CRQSUB.none) { di = ((border.sidecross[0].qsub) ^ (l.filter(([bq, crq]) => border.qsub === bq)[0][1])); } if (border.sidecross[1].qsub !== CRQSUB.none) { di = ((border.sidecross[1].qsub) ^ (l.filter(([bq, crq]) => border.qsub === bq)[0][1]) ^ 1); } } }); } let crossSet = new Set(); let dfs = function (cr) { if (cr.qsub === CRQSUB.none || crossSet.has(cr)) { return; } crossSet.add(cr); for (let d = 0; d < 4; d++) { let ncr = offset(cr, 1, 0, d); let b = offset(cr, .5, 0, d); if (ncr.isnull) { continue; } if (cr.qsub !== CRQSUB.none) { // add line between different i/o (() => { if (cr.isnull || ncr.isnull) { return; } if (cr.qsub === CRQSUB.none || ncr.qsub === CRQSUB.none) { return; } if (cr.qsub === ncr.qsub) { add_cross(b); } if (cr.qsub !== ncr.qsub) { add_line(b); } if (cr.qsub !== ncr.qsub && di !== -1) { let l = [[BQSUB.arrow_up, CRQSUB.out, false], [BQSUB.arrow_dn, CRQSUB.in, false], [BQSUB.arrow_lt, CRQSUB.in, true], [BQSUB.arrow_rt, CRQSUB.out, true]]; l = l.filter(([bq, crq, vert]) => (crq ^ di) === b.sidecross[0].qsub && vert === b.isvert); add_arrow(b, l[0][0]); } })(); // extend i/o through cross/line (() => { if (ncr.isnull) { return; } if (isntLine(b)) { add_inout(ncr, cr.qsub); } if (!b.isnull && isLine(b) && !Directed) { add_inout(ncr, cr.qsub ^ 1); } if (!b.isnull && isLine(b) && b.qsub !== BQSUB.none && Directed) { add_inout(ncr, cr.qsub + ([BQSUB.arrow_up, BQSUB.arrow_lt, BQSUB.arrow_dn, BQSUB.arrow_rt][d] === b.qsub ? 1 : -1)); } if ([11, 12, 13, 14].includes(b.qsub) && di !== -1 && !hasIce && !hasCross) { let l = [[BQSUB.arrow_up, CRQSUB.out], [BQSUB.arrow_dn, CRQSUB.in], [BQSUB.arrow_lt, CRQSUB.in], [BQSUB.arrow_rt, CRQSUB.out]]; l = l.filter(([bq, crq]) => bq === b.qsub); add_inout(b.sidecross[0], l[0][1] ^ di); add_inout(b.sidecross[1], l[0][1] ^ di ^ 1); } })(); dfs(ncr); } } }; forEachCross(cross => dfs(cross)); } function SingleLoopInBorder({ useCrossQsub = true } = {}) { 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_green = function (c) { add_bg_color(c, CQSUB.green); } let add_yellow = function (c) { add_bg_color(c, CQSUB.yellow); } let isYellow = c => c.isnull || c.qsub === CQSUB.yellow; CellConnected({ isShaded: isGreen, isUnshaded: isYellow, add_shaded: add_green, add_unshaded: add_yellow, isLinked: (c, nb, nc) => isCross(nb), isNotPassable: (c, nb, nc) => nb.line, }); CellConnected({ isShaded: isYellow, isUnshaded: isGreen, add_shaded: add_yellow, add_unshaded: add_green, isLinked: (c, nb, nc) => isCross(nb), isNotPassable: (c, nb, nc) => nb.line, OutsideAsShaded: true, }); NoCheckerCell({ isShaded: isGreen, isUnshaded: isYellow, add_shaded: add_green, add_unshaded: add_yellow, }); // use qsub for each cross to track what it can be if (useCrossQsub) { forEachCross(cross => { let qsub; if (cross.qsub === 0 || JSON.parse(cross.qsub).length === 0) { qsub = [[]]; let list = adjlist(cross.adjborder); for (let i = 0; i < 4; i++) { for (let j = i + 1; j < 4; j++) { if (list[i].isnull || list[j].isnull) { continue; } qsub.push([list[i].id, list[j].id]); } } } else { qsub = JSON.parse(cross.qsub); } qsub = qsub.filter(s => s.every(i => !isCross(board.border[i]))); fourside(b => { if (b.line) { qsub = qsub.filter(s => s.includes(b.id)); } if (qsub.every(s => s.includes(b.id))) { add_line(b); } if (qsub.every(s => !s.includes(b.id))) { add_cross(b); } }, cross.adjborder); cross.setQsub(JSON.stringify(qsub)); }); } // connectivity at cross forEachCross(cross => { let blist = adjlist(cross.adjborder); let linecnt = blist.filter(b => b.line).length; let crosscnt = blist.filter(b => isCross(b)).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 forEachBorder(border => { if (isCross(border) || border.line) { return; } let cr1 = border.sidecross[0], cr2 = border.sidecross[1]; if (cr1.path !== null && cr1.path === cr2.path && board.linegraph.components.length > 1) { add_cross(border); } }); // deduce color forEachCell(cell => { // neighbor color fourside((b, c) => { if (isGreen(cell) && isYellow(c) || isGreen(c) && isYellow(cell)) { add_line(b); } if (isGreen(cell) && isGreen(c) || isYellow(cell) && isYellow(c)) { add_cross(b); } }, cell.adjborder, cell.adjacent); // deduce neighbor color if (cell.qsub === CQSUB.none) { fourside((b, c) => { if (isLine(b) && isYellow(c) || isCross(b) && isGreen(c)) { add_green(cell); } if (isCross(b) && isYellow(c) || isLine(b) && isGreen(c)) { add_yellow(cell); } }, cell.adjborder, cell.adjacent); } let innercnt = adjlist(cell.adjacent).filter(c => isGreen(c)).length; let outercnt = adjlist(cell.adjacent).filter(c => isYellow(c)).length; // surrounded by green if (innercnt === 4) { add_green(cell); } }); } function NoCheckerCell({ isShaded, isUnshaded, add_shaded, add_unshaded } = {}) { forEachCell(cell => { if (isShaded(cell) || isUnshaded(cell)) { return; } 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 } = {}) { forEachCell(cell => { let qnum = cell.qnum; if (qnum === CQNUM.none || qnum === CQNUM.quesmark) { return; } let seencnt = (isShaded(cell) ? 1 : 0); let farthest = [0, 0, 0, 0]; // count seen shaded cells for (let d = 0; d < 4; d++) { let pcell = offset(cell, 1, 0, d); while (!pcell.isnull && isShaded(pcell)) { farthest[d]++; seencnt++; pcell = offset(pcell, 1, 0, d); } while (!pcell.isnull && !isUnshaded(pcell)) { farthest[d]++; pcell = offset(pcell, 1, 0, d); } } // not extend too much for (let d = 0; d < 4; d++) { let pcell = offset(cell, 1, 0, d); while (!pcell.isnull && isShaded(pcell)) { pcell = offset(pcell, 1, 0, d); } if (pcell.isnull || isUnshaded(pcell)) { continue; } let tcell = pcell; pcell = offset(pcell, 1, 0, d); let n = 0; while (!pcell.isnull && isShaded(pcell)) { n++; pcell = offset(pcell, 1, 0, d); } if (n + seencnt + 1 > qnum) { add_unshaded(tcell); } } // must extend this way let maxn = farthest.reduce((a, b) => a + b) + (isUnshaded(cell) ? 0 : 1); for (let d = 0; d < 4; d++) { for (let j = 1; j <= qnum - maxn + farthest[d]; j++) { add_shaded(offset(cell, 0, -j, d)); } } }); } function SizeRegion_Cell({ isShaded, isUnshaded, add_shaded, add_unshaded, OneNumPerRegion = true, NoUnshadedNum = true } = {}) { // TODO: maybe rewrite this someday forEachCell(cell => { if (NoUnshadedNum && cell.qnum !== CQNUM.none) { add_shaded(cell); } if (OneNumPerRegion && cell.qnum === CQNUM.none && adjlist(cell.adjacent).every(c => c.isnull || isUnshaded(c))) { add_unshaded(cell); } // 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 = offset(cell, 1, 0, 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 if (!isShaded(cell) && !isUnshaded(cell)) { let cellList = [cell]; let dfs = function (c) { if (c.isnull || !isShaded(c) || cellList.includes(c)) { return; } cellList.push(c); dfs(c.adjacent.top); dfs(c.adjacent.bottom); dfs(c.adjacent.left); dfs(c.adjacent.right); } fourside(dfs, cell.adjacent); let qnumlist = cellList.filter(c => c.qnum !== CQNUM.none); if (qnumlist.length > 1 && OneNumPerRegion) { add_unshaded(cell); } qnumlist = qnumlist.filter(c => c.qnum !== CQNUM.quesmark); if (qnumlist.length > 0 && qnumlist.some(c => c.qnum !== qnumlist[0].qnum)) { add_unshaded(cell); } if (qnumlist.length > 0 && cellList.length > qnumlist[0].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 = offset(cell, 1, 0, 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); } } }); // unreachable cell if (OneNumPerRegion) { let cset = new Set(); let cques = new Set(); forEachCell(cell => { if (cell.qnum === CQNUM.none || cques.has(cell)) { return; } let cs = new Set(); let queue = [[cell, cell.qnum]]; while (queue.length > 0) { let [c, t] = queue.shift(); if (c.isnull || isUnshaded(c) || t === 0 || cs.has(c)) { continue; } cs.add(c); cset.add(c); if (t < 0) { cques.add(c); } adjlist(c.adjacent).forEach(nc => queue.push([nc, t - 1])); } }); forEachCell(cell => !cset.has(cell) ? add_unshaded(cell) : undefined); } } function SizeRegion_Border({ isLinkable = (c, nb, nc) => !nb.isnull && !nc.isnull, isSideable = (c, nb, nc) => !nb.isnull && !nc.isnull, isGroupable = (sc, c) => true, isCon = isLink, isNCon = isSide, add_con = add_link, add_ncon = add_side, OneNumPerRegion = false, scell = null, allSize = null, byLine = false, hasAnum = false, } = {}) { if (hasAnum && !byLine) { forEachCell(cell => { if (isNum(cell)) { add_number(cell, cell.qnum); } if (cell.anum === CANUM.none) { return; } fourside((nb, nc) => { if (nb.isnull || nc.isnull) { return; } if (nc.anum !== CANUM.none && nc.anum !== cell.anum && isSideable(cell, nb, nc)) { add_side(nb); } if (nb.qsub === BQSUB.link) { add_number(nc, cell.anum); } }, cell.adjborder, cell.adjacent); }); } let getqn = c => !hasAnum ? c.qnum : c.anum; NoDeadendBorder(); if (scell === null && allSize === null) { forEachCell(cell => { if (getqn(cell) === CQNUM.none || getqn(cell) === CQNUM.quesmark) { return; } let arg = { ...arguments[0] }; arg.scell = cell; SizeRegion_Border(arg); }); } let isNear = c => !c.isnull && (scell === null || Math.abs(c.bx - scell.bx) + Math.abs(c.by - scell.by) <= 2 * (allSize ?? getqn(scell)) - 2); // use tarjan to find cut vertex let n = 0; let ord = new Map(); let low = new Map(); let cgn = new Map(); let ctn = new Map(); let csn = new Map(); let clg = new Map(); let clt = new Map(); let fth = new Map(); let vst = new Set(); let linklist = [], sidelist = []; // to avoid Maximum call stack size exceeded, manually use a stack to track the cells let dfs = function (sc) { let stack = [{ cell: sc, father: null, visited: false }]; while (stack.length > 0) { let cur = stack[stack.length - 1]; let c = cur.cell; let f = cur.father; let v = cur.visited; if (!v) { if (ord.has(c)) { stack.pop(); continue; } ord.set(c, n); low.set(n, n); cgn.set(n, 1); ctn.set(n, 1); csn.set(n, 1); clg.set(n, []); clt.set(n, []); fth.set(c, f); n++; stack[stack.length - 1] = { cell: c, father: f, visited: true }; } else { stack.pop(); } const cellset = new Set(); let linkdfs = function (c) { if (c.isnull || !isInside(c) || cellset.has(c)) { return; } cellset.add(c); for (let d = 0; d < 4; d++) { let nb = offset(c, .5, 0, d); let nc = offset(c, 1, 0, d); if (nb.isnull || nc.isnull) { continue; } if (isCon(nb) && isLinkable(c, nb, nc)) { linkdfs(nc); } } } if (byLine) { cellset.add(c); } else { linkdfs(c); } if (Array.from(cellset).some(c => { if (!isNear(c)) { return true; } if (scell !== null && getqn(c) !== CQNUM.none && getqn(c) !== CQNUM.quesmark && getqn(scell) !== getqn(c)) { return true; } if (scell !== null && getqn(c) !== CQNUM.none && getqn(c) !== CQNUM.quesmark && scell !== c && OneNumPerRegion) { return true; } if (scell !== null && !isGroupable(scell, c)) { return true; } return false; })) { Array.from(cellset).forEach(c => ord.set(c, -1)); continue; } if (!v) { cellset.forEach(cc => { ord.set(cc, ord.get(c)); if (allSize !== null) { clg.set(ord.get(cc), [...clg.get(ord.get(cc)), allSize]); clt.set(ord.get(cc), [...clt.get(ord.get(cc)), allSize]); } else if (getqn(cc) !== CQNUM.none) { clg.set(ord.get(cc), [...clg.get(ord.get(cc)), getqn(cc)]); clt.set(ord.get(cc), [...clt.get(ord.get(cc)), getqn(cc)]); } fth.set(cc, f); }); cgn.set(ord.get(c), cellset.size); ctn.set(ord.get(c), cellset.size); csn.set(ord.get(c), cellset.size); } let fn = function (c, nb, nc) { if (nc.isnull || isNCon(nb)) { return; } if (ord.has(nc) && ord.get(nc) === -1) { return; } if (!isCon(nb) && clg.get(ord.get(c)).includes(cellset.size) && isSideable(c, nb, nc)) { sidelist.push(nb); return; } if (nc === f || f !== null && ord.get(f) === ord.get(nc) || !isLinkable(c, nb, nc)) { return; } if (ord.get(c) === ord.get(nc)) { return; } if (!isCon(nb) && isSideable(c, nb, nc) && ord.has(nc)) { let cl = clg.get(ord.get(c)), cln = clg.get(ord.get(nc)); if (OneNumPerRegion && cl.length > 0 && cln.length > 0) { sidelist.push(nb); } cl = cl.filter(n => n !== CQNUM.quesmark); cln = cln.filter(n => n !== CQNUM.quesmark); if (cl.some(n1 => cln.some(n2 => n1 !== n2))) { sidelist.push(nb); } if ([...cl, ...cln].some(n => n < cgn.get(ord.get(c)) + cgn.get(ord.get(nc)))) { sidelist.push(nb); } } if (!isLinkable(c, nb, nc)) { return; } if (ord.has(nc) && ord.get(nc) < ord.get(c)) { low.set(ord.get(c), Math.min(low.get(ord.get(c)), ord.get(nc))); return; } if (!v && nc.ques !== CQUES.wall) { stack.push({ cell: nc, father: c, visited: false }); } if (v && c === fth.get(nc)) { let ordc = ord.get(c); let ordnc = ord.get(nc); low.set(ordc, Math.min(low.get(ordc), low.get(ordnc))); if (!vst.has(ordnc)) { ctn.set(ordc, ctn.get(ordc) + ctn.get(ordnc)); clt.set(ordc, [...clt.get(ordc), ...clt.get(ordnc)]); if (ordc <= low.get(ordnc)) { csn.set(ordc, csn.get(ordc) + ctn.get(ordnc)); } vst.add(ordnc); } if (ordc < low.get(ordnc)) { linklist.push([c, nb, nc]); } } }; for (let c of cellset) { for (let d = 0; d < 4; d++) { let nb = offset(c, .5, 0, d); let nc = offset(c, 1, 0, d); fn(c, nb, nc); } } } } forEachCell(cell => { if (ord.has(cell) || cell.ques === CQUES.wall) { return; } if (scell !== null && scell !== cell) { return; } sidelist = []; linklist = []; dfs(cell); Array.from(ord).forEach(([c, n]) => { if (n === -1) { return; } if (scell !== null && getqn(cell) > ctn.get(ord.get(cell)) - csn.get(n) && hasAnum) { add_number(c, getqn(cell)); } }) sidelist.forEach(b => ord.get(b.sidecell[0]) !== ord.get(b.sidecell[1]) ? add_ncon(b) : undefined); linklist.forEach(([c, nb, nc]) => { const ordc = ord.get(c); const ordnc = ord.get(nc); let siz = function (cl) { if (OneNumPerRegion) { cl = cl.map(n => n === CQNUM.quesmark ? 1 : n); } if (!OneNumPerRegion) { cl = cl.filter(n => n !== CQNUM.quesmark); cl = unique(cl); } return cl.reduce((a, b) => a + b, 0); } let cl = [...clt.get(ordnc)]; if (scell === null && siz(cl) > ctn.get(ordnc)) { add_con(nb); } if (scell === null && !cl.includes(CQNUM.quesmark) && OneNumPerRegion) { if (siz(cl) === ctn.get(ordnc) && isSideable(c, nb, nc)) { add_ncon(nb); } if (siz(cl) !== ctn.get(ordnc) && isLinkable(c, nb, nc)) { add_con(nb); } } if (allSize !== null) { if (ctn.get(ordnc) % allSize === 0 && isSideable(c, nb, nc)) { add_ncon(nb); } if (ctn.get(ordnc) % allSize !== 0 && isLinkable(c, nb, nc)) { add_con(nb); } } const acl = clt.get(ord.get(cell)); const an = ctn.get(ord.get(cell)); let ocl = []; acl.forEach(n => { if (cl.includes(n)) { cl.splice(cl.indexOf(n), 1); } else { ocl.push(n); } }); if (siz(ocl) > an - ctn.get(ordnc)) { add_con(nb); } if (scell === null && !ocl.includes(CQNUM.quesmark) && OneNumPerRegion) { if (siz(ocl) === an - ctn.get(ordnc) && isSideable(c, nb, nc)) { add_ncon(nb); } if (siz(ocl) !== an - ctn.get(ordnc) && isLinkable(c, nb, nc)) { add_con(nb); } } }); }); if (scell !== null && ord.size === getqn(scell) && !byLine) { let clist = Array.from(ord.keys()); clist.forEach(c => { adjlist(c.adjborder, c.adjacent).filter(([nb, nc]) => ord.has(nc)).forEach(([nb, nc]) => add_con(nb)); }); } } function StripRegion_cell({ isShaded, add_unshaded } = {}) { forEachCell(cell => { let templist = [cell, offset(cell, 1, 0), offset(cell, 0, 1), offset(cell, 1, 1)]; if (templist.some(c => c.isnull)) { return; } // can't be over 2 shades in each 2*2 if (templist.filter(c => isShaded(c)).length === 2) { templist.forEach(c => add_unshaded(c)); } }); } function RectRegion_Cell({ isShaded, isUnshaded, add_shaded, add_unshaded, isSizeAble = (w, h, sc, c) => true } = {}) { forEachCell(cell => { if (isShaded(cell) || isUnshaded(cell)) { return; } // can't be exactly 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)]); } }); // record the (un)shade count in (0,0) to (a,b) let scnt = Array.from(new Array(board.rows), () => new Array(board.cols).fill(0)); let ucnt = Array.from(new Array(board.rows), () => new Array(board.cols).fill(0)); for (let i = 0; i < board.rows; i++) { for (let j = 0; j < board.cols; j++) { scnt[i][j] = (b => isShaded(b) ? 1 : 0)(board.getc(2 * j + 1, 2 * i + 1)); scnt[i][j] += (i > 0 ? scnt[i - 1][j] : 0) + (j > 0 ? scnt[i][j - 1] : 0) - (i > 0 && j > 0 ? scnt[i - 1][j - 1] : 0); ucnt[i][j] = (b => isUnshaded(b) ? 1 : 0)(board.getc(2 * j + 1, 2 * i + 1)); ucnt[i][j] += (i > 0 ? ucnt[i - 1][j] : 0) + (j > 0 ? ucnt[i][j - 1] : 0) - (i > 0 && j > 0 ? ucnt[i - 1][j - 1] : 0); } } // check if there can be a rectangle exactly between c1 and c2 let isRectAble = function (c1, c2) { if (c1.isnull || c2.isnull) { return 0; } let [x1, x2] = [(c1.bx - 1) / 2, (c2.bx - 1) / 2].sort((x, y) => x - y); let [y1, y2] = [(c1.by - 1) / 2, (c2.by - 1) / 2].sort((x, y) => x - y); let f = (a, b) => a < 0 || b < 0 ? 0 : ucnt[Math.min(a, board.rows - 1)][Math.min(b, board.cols - 1)]; return f(y2, x2) - f(y1 - 1, x2) - f(y2, x1 - 1) + f(y1 - 1, x1 - 1) === 0; } let isRectAble2 = function (c1, c2, dir = ['L', 'U', 'R', 'D']) { if (c1.isnull || c2.isnull) { return 0; } let [x1, x2] = [(c1.bx - 1) / 2, (c2.bx - 1) / 2].sort((x, y) => x - y); let [y1, y2] = [(c1.by - 1) / 2, (c2.by - 1) / 2].sort((x, y) => x - y); let f = (a, b) => a < 0 || b < 0 ? 0 : scnt[Math.min(a, board.rows - 1)][Math.min(b, board.cols - 1)]; if (dir.includes('L') && f(y2, x1 - 1) - f(y2, x1 - 2) - f(y1 - 1, x1 - 1) + f(y1 - 1, x1 - 2) !== 0) { return false; } if (dir.includes('U') && f(y1 - 1, x2) - f(y1 - 2, x2) - f(y1 - 1, x1 - 1) + f(y1 - 2, x1 - 1) !== 0) { return false; } if (dir.includes('R') && f(y2, x2 + 1) - f(y2, x2) - f(y1 - 1, x2 + 1) + f(y1 - 1, x2) !== 0) { return false; } if (dir.includes('D') && f(y2 + 1, x2) - f(y2, x2) - f(y2 + 1, x1 - 1) + f(y2, x1 - 1) !== 0) { return false; } return true; } forEachCell(cell => { if (!isShaded(cell) && cell.qnum <= 0) { return; } if (isShaded(cell.adjborder.top)) { return; } if (isShaded(cell.adjborder.left)) { return; } let wid = 1, hei = 1; while (isShaded(offset(cell, wid, 0))) { wid++; } while (isShaded(offset(cell, 0, hei))) { hei++; } let sc = null; for (let dx = 0; dx < wid; dx++) { for (let dy = 0; dy < hei; dy++) { if (offset(cell, dx, dy).qnum >= 0) { sc = offset(cell, dx, dy); } } } let rectlist = []; for (let dl = 0; isRectAble(offset(cell, dl, 0), offset(cell, 0, 0)); dl--) { if (!isRectAble2(offset(cell, dl, 0), offset(cell, 0, 0), ['L'])) { continue; } for (let dr = wid - 1; isRectAble(offset(cell, dl, 0), offset(cell, dr, 0)); dr++) { if (!isRectAble2(offset(cell, dl, 0), offset(cell, dr, 0), ['L', 'R'])) { continue; } for (let du = 0; isRectAble(offset(cell, dl, du), offset(cell, dr, 0)); du--) { if (!isRectAble2(offset(cell, dl, du), offset(cell, dr, 0), ['L', 'R', 'U'])) { continue; } for (let dd = hei - 1; isRectAble(offset(cell, dl, du), offset(cell, dr, dd)); dd++) { if (!isRectAble2(offset(cell, dl, du), offset(cell, dr, dd))) { continue; } if (!isSizeAble(dr - dl + 1, dd - du + 1, sc, offset(cell, dl, du))) { continue; } rectlist.push({ dl: dl, du: du, dr: dr, dd: dd }); } } } } if (!isShaded(cell) && rectlist.length === 0) { add_unshaded(cell); } if (!isShaded(cell) || rectlist.length === 0) { return; } let ml = rectlist.reduce((m, obj) => Math.max(m, obj.dl), -board.cols); let mu = rectlist.reduce((m, obj) => Math.max(m, obj.du), -board.rows); let mr = rectlist.reduce((m, obj) => Math.min(m, obj.dr), +board.cols); let md = rectlist.reduce((m, obj) => Math.min(m, obj.dd), +board.rows); for (let j = ml; j <= mr; j++) { add_shaded(offset(cell, j, 0)); } for (let j = mu; j <= md; j++) { add_shaded(offset(cell, 0, j)); } if (rectlist.every(obj => obj.dl === ml)) { add_unshaded(offset(cell, ml - 1, 0)); } if (rectlist.every(obj => obj.dr === mr)) { add_unshaded(offset(cell, mr + 1, 0)); } if (rectlist.every(obj => obj.du === mu)) { add_unshaded(offset(cell, 0, mu - 1)); } if (rectlist.every(obj => obj.dd === md)) { add_unshaded(offset(cell, 0, md + 1)); } }); } function RectRegion_Border({ doTrial = true, isSizeAble = (w, h, sc, c) => true } = {}) { 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++) { // × × // ×· -> ×·× // × let b1 = offset(cross, .5, 0, d); let b2 = offset(cross, 0, -.5, d); if (isLink(b1) && isLink(b2)) { add_link(offset(cross, -.5, 0, d)); add_link(offset(cross, 0, +.5, d)); } // ┃ ┃ // ×╹ -> ×┃ // ┃ if (isSide(b1) && isLink(b2)) { add_side(offset(cross, -.5, 0, d)); } // × × // ━╸ -> ━━━ // if (isLink(b1) && isSide(b2)) { add_side(offset(cross, 0, +.5, d)); } // ┃ ┃ // ╹ -> ━┻━ // × × b2 = offset(cross, -.5, 0, d); if (isSide(b1) && isLink(b2)) { add_side(offset(cross, 0, -.5, d)); add_side(offset(cross, 0, +.5, d)); } } } if (!doTrial) { return; } // record the sides count in (0,0) to (a,b); s1 for horizontal side, s2 for vertical side const s1 = Array.from(new Array(board.rows), () => new Array(board.cols).fill(0)); const s2 = Array.from(new Array(board.rows), () => new Array(board.cols).fill(0)); // record the links count in a row; l1 for vertical link, l2 for horizontal link const l1 = Array.from(new Array(board.rows), () => new Array(board.cols).fill(0)); const l2 = Array.from(new Array(board.rows), () => new Array(board.cols).fill(0)); for (let i = 0; i < board.rows; i++) { for (let j = 0; j < board.cols; j++) { s1[i][j] = (b => !b.isnull && b.qans ? 1 : 0)(board.getb(2 * j + 1, 2 * i)); s2[i][j] = (b => !b.isnull && b.qans ? 1 : 0)(board.getb(2 * j, 2 * i + 1)); s1[i][j] += (i > 0 ? s1[i - 1][j] : 0) + (j > 0 ? s1[i][j - 1] : 0) - (i > 0 && j > 0 ? s1[i - 1][j - 1] : 0); s2[i][j] += (i > 0 ? s2[i - 1][j] : 0) + (j > 0 ? s2[i][j - 1] : 0) - (i > 0 && j > 0 ? s2[i - 1][j - 1] : 0); l1[i][j] = (b => !b.isnull && b.qsub === BQSUB.link ? 1 : 0)(board.getb(2 * j + 1, 2 * i)); l2[i][j] = (b => !b.isnull && b.qsub === BQSUB.link ? 1 : 0)(board.getb(2 * j, 2 * i + 1)); l1[i][j] += (j > 0 ? l1[i][j - 1] : 0); l2[i][j] += (i > 0 ? l2[i - 1][j] : 0); } } // check if there can be a rectangle exactly between c1 and c2 let isRectAble = function (c1, c2) { if (c1.isnull || c2.isnull) { return 0; } let [x1, x2] = [(c1.bx - 1) / 2, (c2.bx - 1) / 2].sort((x, y) => x - y); let [y1, y2] = [(c1.by - 1) / 2, (c2.by - 1) / 2].sort((x, y) => x - y); let f1 = (a, b) => a < 0 || b < 0 ? 0 : s1[a][b]; let f2 = (a, b) => a < 0 || b < 0 ? 0 : s2[a][b]; return f1(y2, x2) - f1(y1, x2) - f1(y2, x1 - 1) + f1(y1, x1 - 1) + f2(y2, x2) - f2(y1 - 1, x2) - f2(y2, x1) + f2(y1 - 1, x1) === 0; } let isRectAble2 = function (c1, c2, dir = ['L', 'U', 'R', 'D']) { if (c1.isnull || c2.isnull) { return 0; } let [x1, x2] = [(c1.bx - 1) / 2, (c2.bx - 1) / 2].sort((x, y) => x - y); let [y1, y2] = [(c1.by - 1) / 2, (c2.by - 1) / 2].sort((x, y) => x - y); let g1 = (a, b) => a < 0 || b < 0 || a >= board.rows || b >= board.cols ? 0 : l1[a][b]; let g2 = (a, b) => a < 0 || b < 0 || a >= board.rows || b >= board.cols ? 0 : l2[a][b]; if (dir.includes('L') && g2(y2, x1) - g2(y1 - 1, x1) !== 0) { return false; } if (dir.includes('U') && g1(y1, x2) - g1(y1, x1 - 1) !== 0) { return false; } if (dir.includes('R') && g2(y2, x2 + 1) - g2(y1 - 1, x2 + 1) !== 0) { return false; } if (dir.includes('D') && g1(y2 + 1, x2) - g1(y2 + 1, x1 - 1) !== 0) { return false; } return true; } let emptycnt = 0; forEachCell(c => { if (adjlist(c.adjborder).every(b => b.isnull || b.qsub !== BQSUB.link)) emptycnt++; }); forEachCell(cell => { if (isLink(cell.adjborder.top)) { return; } if (isLink(cell.adjborder.left)) { return; } let wid = 1, hei = 1; while (isLink(offset(cell, wid - .5, 0))) { wid++; } while (isLink(offset(cell, 0, hei - .5))) { hei++; } let sc = null; for (let dx = 0; dx < wid; dx++) { for (let dy = 0; dy < hei; dy++) { if (offset(cell, dx, dy).qnum >= 0) { sc = offset(cell, dx, dy); } } } if ([offset(cell, -.5, 0), offset(cell, 0, -.5), offset(cell, wid - .5, 0), offset(cell, 0, hei - .5)] .every(b => b.isnull || b.qans)) { return; } // ignore empty cell when there are too many if (emptycnt > 500 && sc === null) { return; } let rectlist = []; for (let dl = 0; isRectAble(offset(cell, dl, 0), offset(cell, 0, 0)); dl--) { if (!isRectAble2(offset(cell, dl, 0), offset(cell, 0, 0), ['L'])) { continue; } for (let dr = wid - 1; isRectAble(offset(cell, dl, 0), offset(cell, dr, 0)); dr++) { if (!isRectAble2(offset(cell, dl, 0), offset(cell, dr, 0), ['L', 'R'])) { continue; } for (let du = 0; isRectAble(offset(cell, dl, du), offset(cell, dr, 0)); du--) { if (!isRectAble2(offset(cell, dl, du), offset(cell, dr, 0), ['L', 'R', 'U'])) { continue; } for (let dd = hei - 1; isRectAble(offset(cell, dl, du), offset(cell, dr, dd)); dd++) { if (!isRectAble2(offset(cell, dl, du), offset(cell, dr, dd))) { continue; } if (!isSizeAble(dr - dl + 1, dd - du + 1, sc, offset(cell, dl, du))) { continue; } rectlist.push({ dl: dl, du: du, dr: dr, dd: dd }); } } } } if (rectlist.length === 0) { return; } let ml = rectlist.reduce((m, obj) => Math.max(m, obj.dl), -board.cols); let mu = rectlist.reduce((m, obj) => Math.max(m, obj.du), -board.rows); let mr = rectlist.reduce((m, obj) => Math.min(m, obj.dr), +board.cols); let md = rectlist.reduce((m, obj) => Math.min(m, obj.dd), +board.rows); for (let j = ml; j < mr; j++) { add_link(offset(cell, j + .5, 0)); } for (let j = mu; j < md; j++) { add_link(offset(cell, 0, j + .5)); } if (rectlist.every(obj => obj.dl === ml)) { add_side(offset(cell, ml - .5, 0)); } if (rectlist.every(obj => obj.dr === mr)) { add_side(offset(cell, mr + .5, 0)); } if (rectlist.every(obj => obj.du === mu)) { add_side(offset(cell, 0, mu - .5)); } if (rectlist.every(obj => obj.dd === md)) { add_side(offset(cell, 0, md + .5)); } }); } function LatinSquare({ hasBox = false, ext = (c, cand) => cand } = {}) { let size = Math.max(board.rows, board.cols); forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_sudokunum(cell, cell.qnum); } }); forEachCell(cell => { if (cell.anum !== -1) { return; } let arr = () => Array(size).fill().map((_, i) => i + 1); let cand = arr(), row = [], col = []; if (cell.snum.some(n => n !== -1)) { cand = cell.snum.filter(n => n !== -1); } for (let i = 0; i < size; i++) { row.push(board.getc(i * 2 + 1, cell.by)); col.push(board.getc(cell.bx, i * 2 + 1)); } let fn = function (clist) { let a = arr(); clist.forEach((c, i) => { if (cell === c || c.isnull) { return; } let b = c.anum === -1 && c.snum.every(n => n === -1); a = b ? [] : a.filter(n => n !== c.anum && !c.snum.includes(n)); if (c.anum !== -1) { cand = cand.filter(n => n !== c.anum); } if (c.anum === -1 && c.snum.filter(n => n !== -1).length > 0) { for (let j = i + 1; j < clist.length; j++) { let c2 = clist[j]; if (cell === c2 || c2.isnull || c2.anum !== -1 || c2.snum.every(n => n === -1)) { continue; } let l2 = new Set([...c.snum, ...c2.snum].filter(n => n !== -1)); if (l2.size === 2) { cand = cand.filter(n => !l2.has(n)); } for (let k = j + 1; k < clist.length; k++) { let c3 = clist[k]; if (cell === c3 || c3.isnull || c3.anum !== -1 || c3.snum.every(n => n === -1)) { continue; } let l3 = new Set([...c.snum, ...c2.snum, ...c3.snum].filter(n => n !== -1)); if (l3.size === 3) { cand = cand.filter(n => !l3.has(n)); } for (let l = k + 1; l < clist.length; l++) { let c4 = clist[l]; if (cell === c4 || c4.isnull || c4.anum !== -1 || c4.snum.every(n => n === -1)) { continue; } let l4 = new Set([...c.snum, ...c2.snum, ...c3.snum, ...c4.snum].filter(n => n !== -1)); if (l4.size === 4) { cand = cand.filter(n => !l4.has(n)); } } } } } }); if (clist.every(c => !c.isnull) && a.length === 1) { add_sudokunum(cell, a[0]); } } fn(row); fn(col); if (hasBox) { fn(Array.from(cell.room.clist)); } cand = ext(cell, cand); add_candidate(cell, cand); }); } // see all checks from ui.puzzle.pzpr.common.AnsCheck.prototype // see used checks from ui.puzzle.checker.checklist_normal function GeneralAssist() { const checklist = ui.puzzle.checker.checklist_normal; const numberRemainsUnshaded = ui.puzzle.board.cell[0].numberRemainsUnshaded; let isGreen = c => !c.isnull && c.qsub === CQSUB.green; if (numberRemainsUnshaded) { isGreen = c => c.qsub === CQSUB.dot || c.qnum !== CQNUM.none; add_green = add_dot; } if (checklist.some(f => f.name === "checkCountinuousUnshadeCell")) { NoFacingDoor(); } if (checklist.some(f => f.name === "checkConnectShade")) { CellConnected({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, }); } if (checklist.some(f => f.name === "checkConnectUnshadeRB" || f.name === "checkConnectUnshade")) { CellConnected({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, }); } if (checklist.some(f => f.name === "checkConnectShadeOutside")) { CellConnected({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, OutsideAsShaded: true, }); } if (checklist.some(f => f.name === "checkConnectUnshadeOutside")) { CellConnected({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, OutsideAsShaded: true, }); } if (checklist.some(f => f.name === "checkConnectShade") && checklist.some(f => f.name === "checkConnectUnshadeRB" || f.name === "checkConnectUnshade" || f.name === "checkConnectUnshadeOutside")) { NoCheckerCell({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, }); } if (checklist.some(f => f.name === "checkAdjacentShadeCell")) { BlackNotAdjacent(); } if (checklist.some(f => f.name === "check2x2ShadeCell")) { No2x2Cell({ isShaded: isBlack, add_unshaded: add_green, }) } if (checklist.some(f => f.name === "check2x2UnshadeCell")) { No2x2Cell({ isShaded: isGreen, add_unshaded: add_black, }) } if (checklist.some(f => f.name === "checkDeadendLine") && checklist.some(f => f.name === "checkBranchLine") && checklist.some(f => f.name === "checkCrossLine") && checklist.some(f => f.name === "checkOneLoop")) { if (checklist.some(f => f.name === "checkNoLine")) { SingleLoopInCell({ isPassable: c => c.ques !== CQUES.wall, isPass: c => c.ques !== CQUES.wall, }); } else { SingleLoopInCell(); } } if (checklist.some(f => f.name === "checkRoomPassOnce")) { RoomPassOnce(); } if (checklist.some(f => f.name === "checkUnshadeOnCircle")) { forEachCell(c => { if (c.qnum === CQNUM.wcir) add_green(c); }) } if (checklist.some(f => f.name === "checkShadeOnCircle")) { forEachCell(c => { if (c.qnum === CQNUM.bcir) add_black(c); }) } if (checklist.some(f => f.name === "checkUnshadeSquare")) { RectRegion_Cell({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, isSizeAble: (w, h, sc, c) => w === h, }) } if (checklist.some(f => f.name === "checkBorderCross")) { NoCrossingBorder(); } if (checklist.some(f => f.name === "checkNumberAndUnshadeSize")) { forEachCell(c => { if (c.qnum !== CQNUM.none) add_green(c); }) SizeRegion_Cell({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: c => add_black(c, true), OneNumPerRegion: checklist.some(f => f.name === "checkNoNumberInUnshade") && checklist.some(f => f.name === "checkDoubleNumberInUnshade"), }); } if (checklist.some(f => f.name === "checkNumberSize")) { SizeRegion_Cell({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, OneNumPerRegion: checklist.some(f => f.name === "checkNoNumberInUnshade") && checklist.some(f => f.name === "checkDoubleNumberInUnshade"), NoUnshadedNum: false, }); SizeRegion_Cell({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, OneNumPerRegion: checklist.some(f => f.name === "checkNoNumberInUnshade") && checklist.some(f => f.name === "checkDoubleNumberInUnshade"), NoUnshadedNum: false, }); } if (checklist.some(f => f.name === "checkSideAreaShadeCell")) { forEachCell(c => { if (!isBlack(c)) { return; } for (let d = 0; d < 4; d++) { let nb = offset(c, 0.5, 0, d); let nc = offset(c, 1, 0, d) if (!nb.isnull && nb.ques) { add_green(nc); } } }); } } // assist for certain genre function MirroringTileAssist() { SizeRegion_Cell({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, OneNumPerRegion: false, }); let cset = new Set(); forEachCell(cell => { let clist = []; if (cset.has(cell) || isGreen(cell)) { return; } let dfs = function (c) { if (!isBlack(c) || clist.includes(c)) { return; } clist.push(c); cset.add(c); fourside(dfs, c.adjacent); }; dfs(cell); if (!clist.every(c => adjlist(c.adjacent).every(nc => nc.isnull || isGreen(nc) || clist.includes(nc)))) { return; } let oclist = []; clist.forEach(c => adjdiaglist(c).forEach(nc => { if (!nc.isnull && !isGreen(nc) && !clist.includes(nc)) { oclist.push(nc); } })); let cand = clist.flatMap((cc) => { let r = [clist.map(c => [c.bx - cc.bx, c.by - cc.by])]; r = [...r, ...r.map(l => l.map(([x, y]) => [-y, x]))]; r = [...r, ...r.map(l => l.map(([x, y]) => [-x, y]))]; r = [...r, ...r.map(l => l.map(([x, y]) => [x, -y]))]; r = r.flatMap(l => oclist.map(oc => l.map(([x, y]) => board.getc(oc.bx + x, oc.by + y)))); r = r.filter(l => l.every(c => !c.isnull && !isGreen(c) && (!isNum(c) || c.qnum === clist.length) && !adjlist(c.adjacent).some(nc => !l.includes(nc) && isBlack(nc)))); return r; }); if (cand.length > 0) { let ic = cand.reduce((a, b) => a.filter(c => b.includes(c))); ic.forEach(c => add_black(c)); let uc = cand.map(l => unique(l.flatMap(c => adjlist(c.adjacent)).filter(c => !c.isnull && !l.includes(c)))); uc = uc.reduce((a, b) => a.filter(c => b.includes(c))); uc.forEach(c => add_green(c)); } }); } function _WalkAssist(isOther) { SizeRegion_Border({ isLinkable: (c, nb, nc) => !c.isnull && !nc.isnull && !isOther(c) && !isOther(nc), isCon: b => isLine(b) && !b.sidecell.some(c => isOther(c)), isNCon: b => isCross(b) || b.sidecell.some(c => isOther(c)), add_con: add_line, add_ncon: b => !b.sidecell.some(c => isOther(c)) ? add_cross(b) : undefined, byLine: true, }); forEachCell(cell => { if (cell.qnum !== 2) { return; } let t = adjlist(cell.adjborder, cell.adjacent).filter(([nb, nc]) => isOther(nc) && !isCross(nb)); if (t.length === 1) { add_line(t[0][0]); } }); let ccnt = new Map(); let cqnum = new Map(); let cecnt = new Map(); forEachCell(cell => { if (isOther(cell) || ccnt.has(cell)) { return; } let cl = []; let dfs = function (c) { if (c.isnull || isOther(c) || cl.includes(c)) { return; } cl.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => isLine(nb) ? dfs(nc) : undefined); }; dfs(cell); let ecnt = cl.filter(c => adjlist(c.adjborder, c.adjacent).some(([nb, nc]) => !isLine(nb) && !isCross(nb) && !nc.isnull && !isOther(nc))).length; if (cl.length === 1 && cl[0].lcnt === 0 && adjlist(cl[0].adjborder, cl[0].adjacent).filter(([nb, nc]) => !isLine(nb) && !nc.isnull && !isOther(nc)).length >= 2) { ecnt = 2; } cl.forEach(c => { ccnt.set(c, cl.length); cecnt.set(c, ecnt); }); if (cl.some(c => isNum(c))) { let qnum = cl.find(c => isNum(c)).qnum; cl.forEach(c => cqnum.set(c, qnum)); if (qnum === cl.length) { cl.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => !isOther(nc) && !isLine(nb) ? add_cross(nb) : undefined)); } if (qnum > cl.length && cl.flatMap(c => adjlist(c.adjborder)).filter(b => isLine(b) && b.sidecell.some(c => isOther(c))).length === 1) { cl.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => isOther(nc) ? add_cross(nb) : undefined)); } } }); forEachBorder(border => { if (isEdge(border)) { add_cross(border); } if (border.sidecell.some(c => c.isnull || isOther(c))) { return; } let [n1, n2] = border.sidecell.map(c => ccnt.get(c)); let [qn1, qn2] = border.sidecell.map(c => cqnum.get(c)); let [cen1, cen2] = border.sidecell.map(c => cecnt.get(c)); if (qn1 !== undefined && qn2 !== undefined && qn1 !== qn2) { add_cross(border); } if (qn1 === undefined && qn2 === undefined) { return; } if (n1 + n2 > (qn1 ?? qn2)) { add_cross(border); } if (n1 + n2 !== (qn1 ?? qn2) && cen1 === 1 && cen2 === 1) { add_cross(border); } }); } function FireWalkAssist() { _WalkAssist(isFire); CellConnected({ isShaded: cr => cr.qsub === CRQSUB.in, isUnshaded: cr => cr.qsub === CRQSUB.out, add_shaded: cr => add_inout(cr, CRQSUB.in), add_unshaded: cr => add_inout(cr, CRQSUB.out), isLinked: (c, nb, nc) => isCross(nb) || c.qsub === CRQSUB.in && nc.qsub === CRQSUB.in, isNotPassable: (c, nb, nc) => { if (nb !== undefined) return nb.line; let d = [0, 1, 2, 3].find(d => offset(c, 1, 1, d) === nc); return !isFire(offset(c, .5, .5, d)); }, DiagDir: true, Obj: "cross", }); CellConnected({ isShaded: cr => cr.qsub === CRQSUB.out, isUnshaded: cr => cr.qsub === CRQSUB.in, add_shaded: cr => add_inout(cr, CRQSUB.out), add_unshaded: cr => add_inout(cr, CRQSUB.in), isLinked: (c, nb, nc) => isCross(nb) || c.qsub === CRQSUB.out && nc.qsub === CRQSUB.out, isNotPassable: (c, nb, nc) => { if (nb !== undefined) return nb.line; let d = [0, 1, 2, 3].find(d => offset(c, 1, 1, d) === nc); return !isFire(offset(c, .5, .5, d)); }, DiagDir: true, Obj: "cross", }); forEachCell(cell => { let emptycnt = adjlist(cell.adjborder).filter(b => !isEdge(b) && !isCross(b)).length; let linecnt = adjlist(cell.adjborder).filter(b => isLine(b)).length; // no branch and no cross if (linecnt === 2 && !isFire(cell)) { fourside(add_cross, cell.adjborder); } // no deadend if (emptycnt <= 1) { fourside(add_cross, cell.adjborder); } // cross if (linecnt === 3) { fourside(add_line, cell.adjborder); } // 2 degree path if (emptycnt === 2 && (linecnt === 1 || isClue(cell))) { fourside(add_line, cell.adjborder); } for (let d = 0; d < 4; d++) { if (isFire(cell) && isLine(offset(cell, -.5, 0, d)) && isCross(offset(cell, 0, -.5, d))) { add_line(offset(cell, 0, +.5, d)); } if (isFire(cell) && isLine(offset(cell, -.5, 0, d)) && isCross(offset(cell, 0, +.5, d))) { add_line(offset(cell, 0, -.5, d)); } if (isFire(cell) && isLine(offset(cell, .5, 0, d)) && isLine(offset(cell, -.5, 0, d))) { fourside(add_line, cell.adjborder); } if (isFire(cell) && isCross(offset(cell, .5, 0, d)) && isCross(offset(cell, -.5, 0, d))) { fourside(add_cross, cell.adjborder); } } if (offset(cell, +.5, +.5).qsub === CRQSUB.out && offset(cell, +.5, -.5).qsub === CRQSUB.in && offset(cell, -.5, -.5).qsub === CRQSUB.out && offset(cell, -.5, +.5).qsub === CRQSUB.in) { cell.setQans(1); cell.draw(); } if (offset(cell, +.5, +.5).qsub === CRQSUB.in && offset(cell, +.5, -.5).qsub === CRQSUB.out && offset(cell, -.5, -.5).qsub === CRQSUB.in && offset(cell, -.5, +.5).qsub === CRQSUB.out) { cell.setQans(2); cell.draw(); } }); add_inout(board.getobj(0, 0), CRQSUB.out); let crossSet = new Set(); let dfs = function (cr) { if (cr.qsub === CRQSUB.none || crossSet.has(cr)) { return; } crossSet.add(cr); for (let d = 0; d < 4; d++) { let ncr = offset(cr, 1, 0, d); let b = offset(cr, .5, 0, d); if (ncr.isnull) { continue; } if (cr.qsub !== CRQSUB.none) { // add line between different i/o (() => { if (cr.isnull || ncr.isnull) { return; } if (cr.qsub === CRQSUB.none || ncr.qsub === CRQSUB.none) { return; } if (cr.qsub === ncr.qsub) { add_cross(b); } if (cr.qsub !== ncr.qsub) { add_line(b); } })(); // extend i/o through cross/line (() => { if (ncr.isnull) { return; } if (isntLine(b)) { add_inout(ncr, cr.qsub); } if (!b.isnull && isLine(b)) { add_inout(ncr, cr.qsub ^ 1); } })(); dfs(ncr); } } }; forEachCross(cross => dfs(cross)); } function WaterWalkAssist() { SingleLoopInCell({ isPass: isClue, }); let isWater = c => !c.isnull && c.ques === CQUES.ice; _WalkAssist(isWater); forEachCell(cell => { if (!isWater(cell)) { return; } if (adjlist(cell.adjborder, cell.adjacent).every(([nb, nc]) => isCross(nb) || nc.isnull || isWater(nc))) { fourside(add_cross, cell.adjborder); } for (let d = 0; d < 4; d++) { if (isWater(offset(cell, 1, 0, d)) && isLine(offset(cell, .5, 0, d))) { let f = b => !b.sidecell.some(c => !c.isnull && !isWater(c)) ? add_cross(b) : undefined; [...adjlist(cell.adjborder), ...adjlist(offset(cell, 1, 0, d).adjborder)].forEach(b => f(b)); } } }); } function IceWalkAssist() { SingleLoopInCell({ isPass: isClue, }); _WalkAssist(isIce); } function HebiIchigoAssist() { let isEmpty = c => !c.isnull && !isClue(c) && c.anum === CANUM.none; let extendHebi = l => l.flatMap(h => { let [x, y] = h[h.length - 1]; let nc = [[1, 0], [0, -1], [-1, 0], [0, 1]].map(([dx, dy]) => [x + dx, y + dy]); nc = nc.map(e => [...h, e]); nc = nc.filter(nh => new Set(nh.map(e => e.join(','))).size === nh.length); return nc; }); let extendHebiN = (l, n) => n > 0 ? extendHebi(extendHebiN(l, n - 1)) : l; const HEBI = extendHebiN([[[0, 0], [-1, 0]]], 3); forEachCell(cell => { if (isNum(cell) && cell.qnum === 0) { let d = qdirRemap(cell.qdir); let pcell = offset(cell, 1, 0, d); while (!pcell.isnull && !isClue(pcell)) { add_dot(pcell); pcell = offset(pcell, 1, 0, d); } } if (isNum(cell) && cell.qnum !== 0) { let d = qdirRemap(cell.qdir); let pcell = offset(cell, 1, 0, d); while (isDot(pcell) || isEmpty(pcell) && adjlist(pcell.adjacent).some(nc => nc.anum !== CANUM.none && (nc.anum + cell.qnum) % 2 === 0)) { add_dot(pcell); pcell = offset(pcell, 1, 0, d); } if (!pcell.isnull && !isClue(pcell) && (pcell.anum === CANUM.none || pcell.anum === cell.qnum)) { let npcell = offset(pcell, 1, 0, d); while (isDot(npcell) || isEmpty(npcell) && adjlist(npcell.adjacent).some(nc => nc.anum !== CANUM.none && (nc.anum + cell.qnum) % 2 === 0)) { npcell = offset(npcell, 1, 0, d); } if (npcell.isnull || isClue(npcell) || npcell.anum !== CANUM.none && npcell.anum !== cell.qnum) { add_number(pcell, cell.qnum); } } } if (cell.anum !== CANUM.none) { let cand = HEBI.flatMap(h => { let [sx, sy] = h[cell.anum - 1]; let hl = [0, 1, 2, 3].flatMap(d => { let nh = h.map(([x, y]) => offset(cell, x - sx, y - sy, d)); if (nh.some((c, i) => c.isnull || isDot(c) || isClue(c) || c.anum !== CANUM.none && c.anum !== i + 1)) { return []; } let ncl = nh.flatMap(c => adjlist(c.adjacent)); if (ncl.some(c => !nh.includes(c) && c.anum !== CANUM.none)) { return []; } let hc = nh[0], nc = offset(hc, 1, 0, d); while (!nc.isnull && !isClue(nc) && nc.anum === CANUM.none) { nc = offset(nc, 1, 0, d); } if (nc.anum !== CANUM.none) { return []; } return [nh]; }); return hl; }); if (cand.length > 0) { [0, 1, 2, 3, 4].forEach(i => { if (cand.every(h => h[i] === cand[0][i])) { add_number(cand[0][i], i + 1); } }); let ncl = cand.map(h => h.flatMap(c => adjlist(c.adjacent)) .filter(c => !c.isnull && !isClue(c) && !h.includes(c))); ncl = ncl.reduce((l1, l2) => l1.filter(c => l2.includes(c))); ncl.forEach(c => add_dot(c)); } } if (cell.anum === CANUM.none && !isClue(cell)) { let cl = []; let dfs = function (c) { if (c.isnull || isDot(c) || isClue(c) || cl.includes(c) || cl.length >= 5) { return; } cl.push(c); fourside(dfs, c.adjacent); }; dfs(cell); if (cl.length < 5) { cl.forEach(c => add_dot(c)); } } for (let d = 0; d < 4; d++) { if (cell.anum === 1 && offset(cell, -1, 0, d).anum === 2) { let pcell = offset(cell, 1, 0, d); while (!pcell.isnull && !isClue(pcell)) { add_dot(pcell); pcell = offset(pcell, 1, 0, d); } } } }); } function NikojiAssist() { NoDeadendBorder(); CluePerRegion({ isShaded: isClue, isOthers: c => !isClue(c), isUnshaded: () => false, isNotPassable: (c, nb, nc) => isSide(nb), BridgeType: "link", }); let qcell = new Map(); forEachCell(cell => { if (cell.qnum === CQNUM.none) { return; } if (!qcell.has(cell.qnum)) { qcell.set(cell.qnum, []); } qcell.get(cell.qnum).push(cell); }); let cset = new Set(); Array.from(qcell).forEach(([qnum, clist]) => { let s = new Set(); let cl = []; let dfs = function (x, y) { if (s.has([x, y].join(','))) { return; } s.add([x, y].join(',')); cl = cl.concat(clist.map(c => offset(c, x, y))); [[0, 1], [1, 0], [0, -1], [-1, 0]].forEach(([dx, dy]) => { let nbl = clist.map(c => offset(c, x + dx / 2, y + dy / 2)); let ncl = clist.map(c => offset(c, x + dx, y + dy)); if (nbl.some(b => b.isnull || isSide(b)) || ncl.some(c => cset.has(c))) { nbl.forEach(b => add_side(b)); } if (nbl.some(b => isLink(b))) { nbl.forEach(b => add_link(b)); dfs(x + dx, y + dy); } }); }; dfs(0, 0); cl.forEach(c => cset.add(c)); }); } function UsoTatamiAssist() { NoCrossingBorder(); let s = Array.from(new Array(board.rows), () => new Array(board.cols).fill([])); forEachCell(c => { let x = (c.bx - 1) / 2, y = (c.by - 1) / 2; let s1 = x > 0 ? s[y][x - 1] : []; let s2 = y > 0 ? s[y - 1][x] : []; s[y][x] = [...s1, ...s2.filter(c => !s1.includes(c))]; if (c.qnum !== CQNUM.none) { s[y][x].push(c); } }); RectRegion_Border({ isSizeAble: (w, h, sc, c) => { if (w !== 1 && h !== 1) { return false; } if (sc !== null && w * h === sc.qnum) { return false; } let x = (c.bx - 1) / 2 + w - 1, y = (c.by - 1) / 2 + h - 1; let f = (a, b) => a < 0 || b < 0 ? [] : s[a][b]; if (f(y, x).length - f(y, x - w).length - f(y - h, x).length + f(y - h, x - w).length !== 1) { return false; } sc = f(y, x).find(c => !f(y, x - w).includes(c) && !f(y - h, x).includes(c)); return sc.qnum === CQNUM.quesmark || w * h !== sc.qnum; } }); } function SlashPackAssist() { let add_qsub = function (c, qsub) { // 0b001: O, 0b010: \, 0b100: / qsub = (qsub | c.qsub); if (qsub === 0b111) { return; } if (c === undefined || c.isnull || qsub === c.qsub) { return; } if (step && flg) { return; } flg = true; c.setQsub(qsub); c.draw(); }; let add_slash = function (c, qans) { // 0:/, 1:\ 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(); }; forEachCell(cell => { if (isClue(cell)) { add_qsub(cell, 0b110); } if (cell.qans === CQANS.lslash) { add_qsub(cell, 0b011); } if (cell.qans === CQANS.rslash) { add_qsub(cell, 0b101); } if (cell.qsub === 0b011) { add_slash(cell, CQANS.lslash); } if (cell.qsub === 0b101) { add_slash(cell, CQANS.rslash); } if (!offset(cell, 1, 1).isnull) { let t = [[offset(cell, 0, 0), CQANS.rslash, 0b010], [offset(cell, 1, 0), CQANS.lslash, 0b100], [offset(cell, 1, 1), CQANS.rslash, 0b010], [offset(cell, 0, 1), CQANS.lslash, 0b100]]; if (t.filter(([c, qans, qsub]) => (c.qsub & qsub) === qsub).length === 3) { t.forEach(([c, qans, qsub]) => add_qsub(c, qsub)); } if (t.filter(([c, qans, qsub]) => (c.qsub & qsub) === qsub).length === 2 && t.filter(([c, qans, qsub]) => c.qans === qans).length === 1) { t.forEach(([c, qans, qsub]) => (c.qsub & qsub) !== qsub ? add_slash(c, qans) : undefined); } } }); } function ContextAssist() { BlackNotAdjacent(); GreenConnected(); forEachCell(cell => { if (!isNum(cell)) { return; } let cand = []; let clist = [...adj8list(cell), cell]; let gen = function (l) { if (l.length === 9) { if (l[7] === 1 && l[0] === 1) { return; } if (l[8] === 0 && [1, 3, 5, 7].filter(n => l[n] === 1).length !== cell.qnum) { return; } if (l[8] === 1 && [0, 2, 4, 6].filter(n => l[n] === 1).length !== cell.qnum) { return; } if (l[8] === 1 && [1, 3, 5, 7].some(n => l[n] === 1)) { return; } let bclist = clist.filter((c, i) => l[i] === 1); let tl = []; if (clist.some(c => { if (c.isnull || bclist.includes(c) || tl.includes(c)) { return false; } let n = 0; let olist = []; let dfs = function (c) { if (c.isnull || tl.includes(c)) { return false; } if (!clist.includes(c)) { if (isBlack(c)) { return false; } olist.push(c); return true; } if (bclist.includes(c)) { return false; } tl.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 (!res && n + bclist.length < clist.length) { return true; } return false; })) { return; }; cand.push([...l]); return; } if (l.length > 1 && l[l.length - 1] === 1 && l[l.length - 2] === 1) { return; } let c = clist[l.length]; if (!isBlack(c)) { gen([...l, 0]); } if (!isntBlack(c)) { gen([...l, 1]); } }; gen([]); if (cand.length > 0) { adj8list(cell).forEach((c, i) => { if (cand.every(e => e[i] === cand[0][i])) { (cand[0][i] === 1 ? add_black : add_green)(c); } }); if (cand.every(e => e[8] === cand[0][8])) { (cand[0][8] === 1 ? add_black : add_green)(cell); } } }); } function CoffeeMilkAssist() { let isWhite = c => c.qnum === 1; let isBlack = c => c.qnum === 2; let isGray = c => c.qnum === -2; let add_path = c => { if (c.qnum !== CQNUM.none) { return; } if (isntLine(c.adjborder.top)) { add_line(c.adjborder.left); add_line(c.adjborder.right); } if (isntLine(c.adjborder.left)) { add_line(c.adjborder.top); add_line(c.adjborder.bottom); } }; CellConnected({ isShaded: isGray, isUnshaded: c => adjlist(c.adjborder).every(b => isntLine(b)), add_shaded: add_path, add_unshaded: c => adjlist(c.adjborder).forEach(b => add_cross(b)), isNotPassable: (c, nb, nc) => isCross(nb), isOthers: c => isWhite(c) || isBlack(c), cantDivideShade: (s, o) => s === 0 && o > 0, OnlyOneConnected: false, BridgeType: "line", }); CellConnected({ isShaded: isWhite, isUnshaded: c => adjlist(c.adjborder).every(b => isntLine(b)), add_shaded: add_path, add_unshaded: c => adjlist(c.adjborder).forEach(b => add_cross(b)), isNotPassable: (c, nb, nc) => isCross(nb), isOthers: c => isBlack(c), cantDivideShade: (s, o) => s !== o, OnlyOneConnected: false, BridgeType: "line", }); let nxtc = function (c, d) { let nc = offset(c, 1, 0, d); if (isCross(offset(c, .5, 0, d))) { return board.emptycell; } while (!nc.isnull && nc.qnum === CQNUM.none) { if (isCross(offset(nc, .5, 0, d))) { return board.emptycell; } nc = offset(nc, 1, 0, d); } return nc; } forEachCell(cell => { if (cell.qnum !== CQNUM.none && !isGray(cell) && adjlist(cell.adjborder).filter(b => !isntLine(b)).length === 1) { fourside(add_line, cell.adjborder); } if (isGray(cell) && adjlist(cell.adjborder).filter(b => !isntLine(b)).length === 1) { fourside(add_cross, cell.adjborder); } if (isGray(cell)) { let t = [0, 1, 2, 3].map(d => nxtc(cell, d).qnum); if (cell.lcnt > 0 && t.filter(n => n === 1).length === 1) { add_line(offset(cell, .5, 0, t.indexOf(1))); } if (cell.lcnt > 0 && t.filter(n => n === 2).length === 1) { add_line(offset(cell, .5, 0, t.indexOf(2))); } t = t.filter(n => n > 0); if (t.length === 0 || unique(t).length === 1) { fourside(add_cross, cell.adjborder); } } for (let d = 0; d < 4; d++) { if (cell.qnum === CQNUM.none && isLine(offset(cell, .5, 0, d))) { add_line(offset(cell, -.5, 0, d)); add_cross(offset(cell, 0, .5, d)); add_cross(offset(cell, 0, -.5, d)); } if (cell.qnum === CQNUM.none && isCross(offset(cell, .5, 0, d))) { add_cross(offset(cell, -.5, 0, d)); } if (cell.qnum !== CQNUM.none && !isntLine(offset(cell, .5, 0, d))) { let pcell = nxtc(cell, d); if (pcell.isnull || isWhite(cell) && isBlack(pcell) || isGray(cell) && isGray(pcell)) { add_cross(offset(cell, .5, 0, d)); } let f = c => isGray(c) || c.path !== null && Array.from(c.path.clist).some(c => isGray(c)); if (!pcell.isnull && cell.path !== pcell.path && f(cell) && f(pcell)) { add_cross(offset(cell, .5, 0, d)); } } } }); } function CombiBlockAssist() { for (let i = 0; i < board.tilegraph.components.length; i++) { let tile = board.tilegraph.components[i]; let clist = Array.from(tile.clist); clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => !nc.isnull && nc.tile === tile ? add_link(nb) : undefined)); if (tile.clist[0].block.dotcnt === 2) { clist.forEach(c => fourside(add_link, c.adjborder)); continue; } clist.flatMap(c => adjlist(c.adjborder, c.adjacent).flatMap(([nb, nc]) => isSide(nb) ? nc.tile : [])).forEach(ntile => clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => nc.tile === ntile ? add_side(nb) : undefined))); let tlist = clist.flatMap(c => adjlist(c.adjborder, c.adjacent).flatMap(([nb, nc]) => isLink(nb) && nc.tile !== tile ? nc.tile : [])); if (tlist.length === 0) { tlist = clist.flatMap(c => adjlist(c.adjborder, c.adjacent).flatMap(([nb, nc]) => !isSide(nb) && !nc.isnull ? nc.tile : [])); } tlist = unique(tlist).filter(t => t !== tile); if (tlist.length === 1) { let ntile = tlist[0]; let cclist = [...clist, ...Array.from(ntile.clist)]; cclist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => cclist.includes(nc) ? add_link(nb) : add_side(nb))); continue; } let nshapeSet = new Set(clist .flatMap(c => adjlist(c.adjborder, c.adjacent).flatMap(([nb, nc]) => isSide(nb) ? nc.tile : [])) .flatMap(t => t.clist[0].block.dotcnt === 2 ? getShape(Array.from(t.clist[0].block.clist)) : [])); tlist.forEach(ntile => { let shape = getShape([...clist, ...Array.from(ntile.clist)]); if (!shape.includes('0') || nshapeSet.has(shape)) { clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => nc.tile === ntile ? add_side(nb) : undefined)); } }); } } function BrowniesAssist() { let isCircle = c => c.qnum === -2; forEachCell(cell => { if (cell.qnum2 >= 0) { NShadeInClist({ isShaded: isGreen, isUnshaded: c => !c.isnull && c.qsub === CQSUB.yellow, add_shaded: add_green, add_unshaded: add_yellow, clist: adj8list(cell).filter(c => !c.isnull && c.qnum2 === -1), n: cell.qnum2, }); } if (cell.qnum2 !== -1) { fourside(add_cross, cell.adjborder); return; } if (isCircle(cell) && cell.lcnt === 1 || cell.lcnt === 2) { add_yellow(cell); fourside(add_cross, cell.adjborder); } if (isCircle(cell) && adjlist(cell.adjborder).every(b => isntLine(b))) { add_green(cell); } if (!isCircle(cell) && adjlist(cell.adjborder).every(b => isntLine(b))) { add_yellow(cell); } if (isGreen(cell) && isCircle(cell)) { fourside(add_cross, cell.adjborder); } for (let d = 0; d < 4; d++) { if (isLine(offset(cell, .5, 0, d))) { add_cross(offset(cell, 0, -.5, d)); add_cross(offset(cell, 0, +.5, d)); } if (isLine(offset(cell, .5, 0, d)) && isntLine(offset(cell, -.5, 0, d))) { add_green(cell); fourside(add_cross, cell.adjborder); } if (isGreen(cell) && isGreen(offset(cell, 1, 0, d))) { add_cross(offset(cell, .5, 0, d)); } if (isCross(offset(cell, -.5, 0, d)) || isGreen(cell)) { let pcell = cell; while (!isCircle(pcell) && !isntLine(offset(pcell, .5, 0, d)) && (pcell === cell || !isGreen(pcell))) { pcell = offset(pcell, 1, 0, d); } if (!isCircle(pcell)) { add_cross(offset(cell, .5, 0, d)); } } if (isGreen(cell) && adjlist(cell.adjborder).every(b => b === offset(cell, .5, 0, d) || isntLine(b))) { let pcell = cell; while (!isCircle(pcell)) { add_line(offset(pcell, .5, 0, d)); pcell = offset(pcell, 1, 0, d); } } if (isCircle(cell)) { let pcell = cell; while (!isntLine(offset(pcell, .5, 0, d)) && (pcell === cell || !isCircle(pcell) && isYellow(pcell))) { pcell = offset(pcell, 1, 0, d); } if (isYellow(pcell) || isCircle(pcell)) { add_cross(offset(cell, .5, 0, d)); } } if (isCircle(cell) && isYellow(cell) && adjlist(cell.adjborder).every(b => b === offset(cell, .5, 0, d) || isntLine(b))) { let pcell = cell; while (isYellow(pcell)) { add_line(offset(pcell, .5, 0, d)) pcell = offset(pcell, 1, 0, d); } if (isCircle(offset(pcell, 1, 0, d))) { add_cross(offset(pcell, .5, 0, d)); } } } }); } function ReflectLinkAssist() { forEachCell(cell => cell.ques === 11 ? fourside(add_line, cell.adjborder) : undefined); SingleLoopInCell({ hasCross: true }); forEachCell(cell => { if (cell.lcnt === 2 && cell.ques !== 11) { fourside(add_cross, cell.adjborder); } for (let d = 0; d < 4; d++) { let ncell = offset(cell, 1, 0, d); if (cell.ques !== 11 && ncell.ques !== 11 && cell.lcnt === 1 && ncell.lcnt === 1 && cell.path === ncell.path && board.linegraph.components.length > 1) { add_cross(offset(cell, .5, 0, d)); } } if (cell.ques === CQUES.none) { return; } let d = [-1, -1, 1, 2, 3, 0][cell.ques]; add_line(offset(cell, 0, .5, d)); add_line(offset(cell, .5, 0, d)); add_cross(offset(cell, 0, -.5, d)); add_cross(offset(cell, -.5, 0, d)); if (cell.qnum < 3) { return; } let cand = Array(cell.qnum - 2).fill(0).map((_, i) => i + 1); cand = cand.filter(n => { for (let i = 0; i < n; i++) { if (isntLine(offset(cell, i + .5, 0, d))) { return false; } if (i > 0 && offset(cell, i, 0, d).ques === CQUES.none && (isLine(offset(cell, i, -.5, d)) || isLine(offset(cell, i, +.5, d)))) { return false; } } for (let i = 0; i < cell.qnum - 1 - n; i++) { if (isntLine(offset(cell, 0, i + .5, d))) { return false; } if (i > 0 && offset(cell, 0, i, d).ques === CQUES.none && (isLine(offset(cell, -.5, i, d)) || isLine(offset(cell, +.5, i, d)))) { return false; } } if (isLine(offset(cell, n + .5, 0, d)) || isLine(offset(cell, 0, cell.qnum - 1 - n + .5, d))) { return false; } if (isntLine(offset(cell, n, -.5, d)) && isntLine(offset(cell, n, +.5, d))) { return false; } if (isntLine(offset(cell, -.5, cell.qnum - 1 - n, d)) && isntLine(offset(cell, +.5, cell.qnum - 1 - n, d))) { return false; } return true; }); if (cand.length > 0) { let mx = cand.reduce((res, n) => Math.min(res, n), Infinity); let my = cell.qnum - 1 - cand.reduce((res, n) => Math.max(res, n), 0); for (let i = 0; i < mx; i++) { add_line(offset(cell, i + .5, 0, d)); } for (let i = 0; i < my; i++) { add_line(offset(cell, 0, i + .5, d)); } if (mx + my + 1 === cell.qnum) { add_cross(offset(cell, mx + .5, 0, d)); add_cross(offset(cell, 0, my + .5, d)); } } }); } function UsoOneAssist() { let add_Ocell = function (c) { if (c === undefined || c.isnull || c.qcmp !== 0) { return; } if (step && flg) { return; } flg = true; c.setQcmp(1); c.draw(); }; let add_Xcell = function (c) { if (c === undefined || c.isnull || c.qcmp !== 0) { return; } if (step && flg) { return; } flg = true; c.setQcmp(2); c.draw(); }; let isOcell = c => c.qcmp === 1; let isXcell = c => c.qcmp === 2; GreenConnected(); BlackNotAdjacent(); forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_green(cell); } if (cell.qnum >= 0 && isOcell(cell)) { NShadeInClist({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, clist: adjlist(cell.adjacent), n: cell.qnum, }); } if (cell.qnum >= 0 && cell.qnum === adjlist(cell.adjacent).filter(c => !c.isnull).length) { add_Xcell(cell); } if (cell.qnum >= 0 && isXcell(cell) && adjlist(cell.adjacent).filter(c => !c.isnull && !isGreen(c) && !isBlack(c)).length === 1) { let c = adjlist(cell.adjacent).find(c => !c.isnull && !isGreen(c) && !isBlack(c)); if (adjlist(cell.adjacent).filter(c => isBlack(c)).length === cell.qnum) { add_black(c); } if (adjlist(cell.adjacent).filter(c => isBlack(c)).length === cell.qnum - 1) { add_green(c); } } if (cell.qnum >= 0 && (adjlist(cell.adjacent).filter(c => isBlack(c)).length > cell.qnum || 4 - adjlist(cell.adjacent).filter(c => isntBlack(c)).length < cell.qnum)) { add_Xcell(cell); } if (cell.qnum >= 0 && adjlist(cell.adjacent).every(c => c.isnull || isBlack(c) || isGreen(c)) && adjlist(cell.adjacent).filter(c => isBlack(c)).length === cell.qnum) { add_Ocell(cell); } }); forEachRoom(room => { let l = Array.from(room.clist).filter(c => c.qnum !== CQNUM.none); NShadeInClist({ isShaded: isXcell, isUnshaded: isOcell, add_shaded: add_Xcell, add_unshaded: add_Ocell, clist: l, n: 1, }); }); } function Yajirushi2Assist() { let isDot = isYellow; let add_dot = add_yellow; let isntDot = c => c.isnull || isClue(c) || isOcell(c) || c.anum !== CANUM.none; let isArrowAble = function (c, d) { if (isDot(c) || isntDot(offset(c, 1, 0, d)) || c.anum !== CANUM.none && d !== qdirRemap(c.anum)) { return false; } let nc = offset(c, 2, 0, d); while (isDot(nc)) { nc = offset(nc, 1, 0, d); } if (nc.isnull || isClue(nc) || nc.anum !== CANUM.none && [-1, 3, 1, 0, 2][nc.anum] !== d) { return false; } return true; }; let add_arrow = function (c, d) { if (c === undefined || c.isnull || c.anum !== CANUM.none) { return; } if (step && flg) { return; } flg = true; c.setAnum(d); c.setQsub(CQSUB.none); c.draw(); } CellConnected({ isShaded: isDot, isUnshaded: c => isOcell(c) || c.anum !== CANUM.none || isClue(c), add_shaded: add_dot, add_unshaded: add_Ocell, }); let hasDot = false; forEachCell(cell => { hasDot = hasDot || isDot(cell); }); forEachCell(cell => { if (isNum(cell)) { NShadeInClist({ isShaded: c => isOcell(c) || c.anum !== CANUM.none, isUnshaded: c => isDot(c) || isClue(c), add_shaded: add_Ocell, add_unshaded: add_dot, clist: adjlist(cell.adjacent), n: cell.qnum }) } if (cell.anum !== CANUM.none) { let d = [-1, 1, 3, 2, 0][cell.anum]; add_dot(offset(cell, 1, 0, d)); let ncell = offset(cell, 2, 0, d); while (isDot(ncell)) { ncell = offset(ncell, 1, 0, d); } if (isOcell(ncell)) { add_arrow(ncell, [3, 2, 4, 1][d]); } else if (!ncell.isnull && !isClue(ncell)) { let mcell = offset(ncell, 1, 0, d); while (isDot(mcell)) { mcell = offset(mcell, 1, 0, d); } if (mcell.isnull || isClue(mcell) || mcell.anum !== CANUM.none && [-1, 3, 1, 0, 2][mcell.anum] !== d) { add_arrow(ncell, [3, 2, 4, 1][d]); } } } if (cell.anum === CANUM.none && hasDot && adjlist(cell.adjacent).filter(c => !isntDot(c)).length === 1) { add_dot(adjlist(cell.adjacent).find(c => !isntDot(c))); } if ([0, 1, 2, 3].every(d => !isArrowAble(cell, d))) { add_dot(cell); } if (isOcell(cell) && [0, 1, 2, 3].filter(d => isArrowAble(cell, d)).length === 1) { add_arrow(cell, [4, 1, 3, 2][[0, 1, 2, 3].find(d => isArrowAble(cell, d))]); } }); } function ToichikaAssist() { let isArrowAble = function (c, d) { if (isDot(c) || c.anum !== CANUM.none && d !== qdirRemap(c.anum)) { return false; } let roomset = new Set(Array.from(c.room.clist).flatMap(c => adjlist(c.adjacent).flatMap(nc => nc.isnull ? [] : nc.room))); roomset.add(c.room); let nc = offset(c, 1, 0, d), cl = []; while (isDot(nc) || roomset.has(nc.room) && nc.anum == CANUM.none) { cl.push(nc); if (Array.from(nc.room.clist).every(c => isDot(c) || cl.includes(c))) { return false; } nc = offset(nc, 1, 0, d); } if (nc.isnull || roomset.has(nc.room) || nc.anum !== CANUM.none && [-1, 3, 1, 0, 2][nc.anum] !== d) { return false; } return true; }; forEachCell(cell => { if (isNum(cell)) { add_number(cell, cell.qnum); } if (cell.anum !== CANUM.none) { let d = [-1, 1, 3, 2, 0][cell.anum]; let roomset = new Set(Array.from(cell.room.clist).flatMap(c => adjlist(c.adjacent).flatMap(nc => nc.isnull ? [] : nc.room))); let ncell = offset(cell, 1, 0, d); while (isDot(ncell) || roomset.has(ncell.room)) { add_dot(ncell); ncell = offset(ncell, 1, 0, d); } if (Array.from(ncell.room.clist).filter(c => !isDot(c)).length === 1 && !isDot(ncell)) { add_number(ncell, [3, 2, 4, 1][d]); } else { let mcell = offset(ncell, 1, 0, d); while (isDot(mcell) || roomset.has(mcell.room)) { mcell = offset(mcell, 1, 0, d); } if (mcell.isnull || mcell.anum !== CANUM.none && [-1, 3, 1, 0, 2][mcell.anum] !== d) { add_number(ncell, [3, 2, 4, 1][d]); } } } if (!isDot(cell) && cell.qnum === CANUM.none && [0, 1, 2, 3].every(d => !isArrowAble(cell, d))) { add_dot(cell); } if (!isDot(cell) && cell.qnum === CANUM.none && [0, 1, 2, 3].filter(d => { let nc = offset(cell, 1, 0, d); while (isDot(nc)) { nc = offset(nc, 1, 0, d); } return nc.anum === [3, 2, 4, 1][d]; }).length > 1) { add_dot(cell); } if (!isDot(cell) && Array.from(cell.room.clist).filter(c => !isDot(c)).length === 1 && [0, 1, 2, 3].filter(d => isArrowAble(cell, d)).length === 1) { add_number(cell, [4, 1, 3, 2][[0, 1, 2, 3].find(d => isArrowAble(cell, d))]); } }); forEachRoom(room => { if (Array.from(room.clist).some(c => c.anum !== CANUM.none)) { Array.from(room.clist).forEach(c => c.anum === CANUM.none ? add_dot(c) : undefined); } }); } function ArukoneAssist() { forEachCell(cell => { if (cell.qnum === CQNUM.none) { NShadeInClist({ isShaded: isLine, isUnshaded: isntLine, add_shaded: add_line, add_unshaded: add_cross, n: 2, clist: adjlist(cell.adjborder), }); } let num = undefined; if (cell.path !== null) { num = Array.from(cell.path.clist).find(c => c.qnum !== CQNUM.none); } if (num !== undefined) { num = num.qnum; } if (cell.qnum !== CQNUM.none) { num = cell.qnum; } if (cell.lcnt === 2 || cell.lcnt === 1 && cell.qnum !== CQNUM.none) { adjlist(cell.adjborder).forEach(b => add_cross(b)); } if (cell.qnum === CQNUM.none && adjlist(cell.adjborder).filter(b => isntLine(b)).length >= 3) { adjlist(cell.adjborder).forEach(b => add_cross(b)); } if (num !== undefined) { adjlist(cell.adjborder, cell.adjacent).forEach(([nb, nc]) => { if (nc.isnull || isCross(nb)) { return; } if (nc.path === null && nc.qnum !== CQNUM.none && nc.qnum !== num || nc.path !== null && Array.from(nc.path.clist).some(c => c.qnum !== CQNUM.none && c.qnum !== num)) { add_cross(nb); } }); } let l = adjlist(cell.adjborder, cell.adjacent).filter(([nb, nc]) => isLine(nb) || !nc.isnull && !isCross(nb)); if (cell.qnum === CQNUM.none && cell.lcnt === 1 && l.length === 2 || cell.qnum !== CQNUM.none && l.length === 1) { l.forEach(([nb, nc]) => add_line(nb)); } }); } function ArafAssist() { NoDeadendBorder(); CluePerRegion({ isShaded: c => c.qnum !== CQNUM.none, isOthers: c => c.qnum === CQNUM.none, isUnshaded: () => false, cantDivideShade: (s, o) => s % 2 === 1 || s === 0 && o > 0, isNotPassable: (c, nb, nc) => isSide(nb), BridgeType: "link", }); let m = new Map(); forEachCell(cell => { if (m.has(cell)) { return; } let obj = { cnt: 0, qnum: [] }; let dfs = function (c) { if (c.isnull || m.has(c)) { return; } m.set(c, obj); obj.cnt++; if (c.qnum !== CQNUM.none) { obj.qnum.push(c.qnum); } adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!isLink(nb)) { return; } dfs(nc); }); }; dfs(cell); }); forEachBorder(border => { let [o1, o2] = border.sidecell; o1 = m.get(o1); o2 = m.get(o2); if (o1 === o2) { return; } if (o1.qnum.length + o2.qnum.length > 2 || o1.qnum.some(n1 => n1 !== CQNUM.quesmark && o2.qnum.some(n2 => n2 !== CQNUM.quesmark && Math.abs(n1 - n2) <= 1))) { add_side(border); } if (o1.qnum.length + o2.qnum.length === 2) { let [l, r] = [...o1.qnum, ...o2.qnum].sort((a, b) => a - b); if (o1.cnt + o2.cnt >= r) { add_side(border); } } }); } function AngleLoopAssist() { let add_segment = function (cr1, cr2) { let [x1, y1, x2, y2] = [cr1.bx, cr1.by, cr2.bx, cr2.by]; if (x1 > x2 || x1 === x2 && y1 > y2) { [x1, y1, x2, y2] = [x2, y2, x1, y1]; } if (x1 === x2 && y1 === y2) { return; } if (step && flg) { return; } flg = 1; ui.puzzle.board.segment.addSegmentByAddr(x1, y1, x2, y2); // BUG: no "path" for some segment ui.puzzle.painter.drawSegments(); }; let getSegment = function (cr1, cr2) { let [x1, y1, x2, y2] = [cr1.bx, cr1.by, cr2.bx, cr2.by]; if (x1 > x2 || x1 === x2 && y1 > y2) { [x1, y1, x2, y2] = [x2, y2, x1, y1]; } return ui.puzzle.board.getSegment(x1, y1, x2, y2); }; let getLatticePoint = function (cr1, cr2) { return ui.puzzle.board.getLatticePoint(cr1.bx, cr1.by, cr2.bx, cr2.by); }; let getAngleType = function (cr, cr1, cr2) { let [dx1, dy1] = [cr1.bx - cr.bx, cr1.by - cr.by]; let [dx2, dy2] = [cr2.bx - cr.bx, cr2.by - cr.by]; if (dx1 * dy2 - dx2 - dy1 === 0) { return 0; } let p = dx1 * dx2 + dy1 * dy2; if (p > 0) { return 1; } if (p === 0) { return 2; } if (p < 0) { return 3; } } let crlist = Array.from(board.cross).filter(cr => cr.qnum !== -1); crlist.forEach(cr => { if (cr.seglist.length === 2) { return; } let cand = []; for (let i = 0; i < crlist.length; i++) { let cr1 = crlist[i]; if (cr1 === cr || getLatticePoint(cr, cr1).length > 0) { continue; } for (let j = i + 1; j < crlist.length; j++) { let cr2 = crlist[j]; if (cr2 === cr || getLatticePoint(cr, cr2).length > 0) { continue; } if (cr.seglist.length === 1 && getSegment(cr, cr1) === null && getSegment(cr, cr2) === null) { continue; } if (getAngleType(cr, cr1, cr2) !== cr.qnum) { continue; } cand.push([cr1, cr2]); } } if (cand.length > 0) { cand.reduce((a, b) => a.filter(ncr => b.includes(ncr))).forEach(ncr => add_segment(cr, ncr)); } }); } function VoxasAssist() { let isSide = b => b.isnull || b.qans === 1 || b.ques > 0; forEachBorder(b => { if (b.ques > 0) { add_side(b); } }); const PIECE = [ [[[0, 0], [1, 0]], 0b100], [[[0, 0], [1, 0], [2, 0]], 0b101], [[[0, 0], [0, 1]], 0b110], [[[0, 0], [0, 1], [0, 2]], 0b111], ]; forEachCell(cell => { let ncand = [4, 5, 6, 7]; for (let d = 0; d < 4; d++) { let ques = offset(cell, .5, 0, d).ques; let nca = offset(cell, 1, 0, d).anum; if (ques === 2 && isSide(offset(cell, 1.5, 0, d))) { add_link(offset(cell, -.5, 0, d)); } if (ques === 2 && isLink(offset(cell, 1.5, 0, d))) { add_side(offset(cell, -.5, 0, d)); } if (ques === 4 && isSide(offset(cell, 1.5, 0, d))) { add_side(offset(cell, -.5, 0, d)); } if (ques === 4 && isLink(offset(cell, 1.5, 0, d))) { add_link(offset(cell, -.5, 0, d)); } if (ques === CQUES.none || nca === CANUM.none) { continue; } if (ques === 2) { ncand = ncand.filter(n => ((n ^ nca) & 0b11) === 0b11); }// black if (ques === 4) { ncand = ncand.filter(n => ((n ^ nca) & 0b11) === 0b00); }// white if (ques === 3) { ncand = ncand.filter(n => ((n ^ nca) & 0b11) === 0b01 || ((n ^ nca) & 0b11) === 0b10); }// gray } if (cell.ques === CQUES.wall) { return; } let linklist = [], sidelist = [], namelist = []; PIECE.forEach(([cl, t]) => { if (cell.anum >= 0 && cell.anum !== t || !ncand.includes(t)) { return; } [0, 1, 2].forEach(o => { if (o >= cl.length) { return; } let clist = cl.map(([x, y]) => offset(cell, x - cl[o][0], y - cl[o][1])); if (clist.some(c => c.isnull || c.ques === CQUES.wall)) { return; } if (clist.some(c => c.anum >= 0 && c.anum !== t)) { return; } if (clist.some(c => adjlist(c.adjborder, c.adjacent).some( ([nb, nc]) => { if (nc.isnull || nc.ques === CQUES.wall) { return false; } if (isLink(nb) && !clist.includes(nc) || isSide(nb) && clist.includes(nc)) { return true; } return false; }))) { return; } let linkl = [], sidel = []; clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (clist.includes(nc) && !linkl.includes(nb)) { linkl.push(nb); } if (!clist.includes(nc) && !sidel.includes(nb)) { sidel.push(nb); } })); linklist.push(linkl); sidelist.push(sidel); namelist.push(t); }); }); if (linklist.length > 0) { linklist = linklist.reduce((a, b) => a.filter(i => b.includes(i))); } if (sidelist.length > 0) { sidelist = sidelist.reduce((a, b) => a.filter(i => b.includes(i))); } linklist.forEach(b => add_link(b)); sidelist.forEach(b => add_side(b)); namelist = unique(namelist); if (namelist.length === 1) { add_number(cell, namelist[0]); } }); } function AquariumAssist() { for (let i = 0; i < board.rows; i++) { let cl = []; for (let j = 0; j < board.cols; j++) { cl.push(board.getc(j * 2 + 1, i * 2 + 1)); } NShadeInClist({ isShaded: isBlack, isUnshaded: isDot, add_shaded: add_black, add_unshaded: add_dot, clist: cl, n: board.getex(-1, i * 2 + 1).qnum, }); } for (let i = 0; i < board.cols; i++) { let cl = []; for (let j = 0; j < board.rows; j++) { cl.push(board.getc(i * 2 + 1, j * 2 + 1)); } NShadeInClist({ isShaded: isBlack, isUnshaded: isDot, add_shaded: add_black, add_unshaded: add_dot, clist: cl, n: board.getex(i * 2 + 1, -1).qnum, }); } if (ui.puzzle.config.list.aquarium_regions.val) { // Water in one region must have the same surface level forEachRoom(room => { let cl = Array.from(room.clist); let ub = cl.reduce((n, c) => isDot(c) ? Math.max(n, c.by) : n, -1); let lb = cl.reduce((n, c) => isBlack(c) ? Math.min(n, c.by) : n, Infinity); cl.forEach(c => { if (c.by <= ub) { add_dot(c); } if (c.by >= lb) { add_black(c); } }); }); } if (!ui.puzzle.config.list.aquarium_regions.val) { forEachCell(cell => { if (isBlack(cell)) { let s = new Set(); let dfs = function (c) { if (c.isnull || !isInside(c) || c.by < cell.by || s.has(c)) { return; } s.add(c); add_black(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!isBound(nb)) { dfs(nc); } }); }; dfs(cell); } if (!isBlack(cell) && !isDot(cell)) { let s = new Set(); let dfs = function (c) { if (c.isnull || !isInside(c) || c.by < cell.by || s.has(c) || isDot(cell)) { return; } s.add(c); if (isDot(c)) { add_dot(cell); return; } adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!isBound(nb)) { dfs(nc); } }); }; dfs(cell); } }); } } function BorderBlockAssist() { // TODO: bug when there's a side between the same region let isDot = cr => cr.qnum === 1; forEachCross(cross => { if (!isDot(cross) && cross.lcnt > 0) { NShadeInClist({ isShaded: isSide, isUnshaded: isLink, add_shaded: add_side, add_unshaded: add_link, clist: adjlist(cross.adjborder).filter(b => !b.isnull), n: isEdge(cross) ? 0 : 2, }); } if (!isDot(cross) && adjlist(cross.adjborder).filter(b => isLink(b)).length === 3) { fourside(add_link, cross.adjborder); } if (isDot(cross)) { if (isEdge(cross)) { fourside(add_side, cross.adjborder); } if (adjlist(cross.adjborder).some(b => isLink(b))) { fourside(add_side, cross.adjborder); } } }); let DSU = new Map(); // Disjoint Set Union let DSUfind = function (n) { if (DSU.get(n) !== n) { DSU.set(n, DSUfind(DSU.get(n))); } return DSU.get(n); }; forEachCell(cell => { DSU.set(cell, cell); let cc = cell; if (isLink(offset(cell, -.5, 0))) { let nc = DSUfind(offset(cell, -1, 0)); if (cc.qnum !== CQNUM.none) { DSU.set(nc, cc); } else { DSU.set(cc, nc); } } cc = DSUfind(cell); if (isLink(offset(cell, 0, -.5))) { let nc = DSUfind(offset(cell, 0, -1)); if (cc.qnum !== CQNUM.none) { DSU.set(nc, cc); } else { DSU.set(cc, nc); } } }); forEachBorder(border => { let [c1, c2] = border.sidecell; if (c1.isnull || c2.isnull) { return; } c1 = DSUfind(c1); c2 = DSUfind(c2); if (isNum(c1) && isNum(c2) && c1.qnum !== c2.qnum) { add_side(border); } }); forEachCell(cell => { for (let d = 0; d < 4; d++) { if (!offset(cell, 1, -1, d).isnull && !offset(cell, 1, +1, d).isnull && [[.5, 0], [0, -.5], [0, .5]].every(([x, y]) => (b => !b.isnull && !isSide(b))(offset(cell, x, y, d))) && [[.5, -.5], [.5, +.5]].every(([x, y]) => (cr => !isDot(cr) && cr.lcnt === 1)(offset(cell, x, y, d)))) { let f = (c1, c2) => (new Set([DSUfind(c1).qnum, DSUfind(c2).qnum, CQNUM.none]).size) === 3; if (f(offset(cell, 0, -1, d), offset(cell, 0, +1, d)) || isSide(offset(cell, 1, -.5, d)) && f(offset(cell, 1, -1, d), offset(cell, 0, +1, d)) || isSide(offset(cell, 1, +.5, d)) && f(offset(cell, 0, -1, d), offset(cell, 1, +1, d)) || isSide(offset(cell, 1, -.5, d)) && isSide(offset(cell, 1, +.5, d)) && f(offset(cell, 1, -1, d), offset(cell, 1, +1, d)) ) { add_link(offset(cell, .5, 0, d)); } } if (!offset(cell, 1, -1, d).isnull && !offset(cell, 1, +1, d).isnull && [[.5, -.5], [.5, +.5]].every(([x, y]) => !isDot(offset(cell, x, y, d))) && new Set([DSUfind(offset(cell, 1, -1, d)).qnum, DSUfind(offset(cell, 1, +1, d)).qnum, DSUfind(cell).qnum, CQNUM.none]).size === 4) { add_link(offset(cell, .5, 0, d)); } } }); CellConnected({ isShaded: c => isNum(c), isOthers: c => !isNum(c), isUnshaded: c => false, add_shaded: () => { }, add_unshaded: () => { }, cantDivideShade: (s, o) => s === 0 && o > 0, isNotPassable: (c, nb, nc) => isSide(nb), OnlyOneConnected: false, BridgeType: "link", }); let maxQnum = Array.from(board.cell).reduce((a, b) => Math.max(a, b.qnum), 0); for (let i = 1; i <= maxQnum; i++) { CellConnected({ isShaded: c => c.qnum === i, isUnshaded: c => isNum(DSUfind(c)) && DSUfind(c).qnum !== i, add_shaded: () => { }, add_unshaded: () => { }, isNotPassable: (c, nb, nc) => isSide(nb), BridgeType: "link", }); } } function ChainedBlockAssist() { SizeRegion_Cell({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, OneNumPerRegion: true, NoUnshadedNum: true, }); CluePerRegion({ isShaded: c => c.qnum !== CQNUM.none, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, isOthers: isBlack, }); forEachCell(cell => { if (!isBlack(cell)) { return; } let cl = []; let dfs = function (c) { if (c.isnull || cl.includes(c) || !isBlack(c)) { return; } cl.push(c); fourside(dfs, c.adjacent); }; dfs(cell); let ol = unique(cl.flatMap(c => adj8list(c)).filter(c => !c.isnull && !isGreen(c) && !cl.includes(c))); if (ol.length === 1) { add_black(ol[0]); } }); let ord = new Map(); let nshp = new Map(); let nqnum = new Map(); let n = 0; forEachCell(cell => { if (!isBlack(cell) || ord.has(cell)) { return; } n++; nshp.set(n, []); nqnum.set(n, []); let dfs2 = function (c) { if (c.isnull || ord.has(c) || !isBlack(c)) { return; } let cl = [], finished = true; let dfs = function (c) { if (!c.isnull && !isBlack(c) && !isGreen(c)) { finished = false; } if (c.isnull || cl.includes(c) || !isBlack(c)) { return; } cl.push(c); ord.set(c, n); fourside(dfs, c.adjacent); }; dfs(c); if (finished) { nshp.get(n).push(getShape(cl)); nqnum.get(n).push(cl.length); } else { cl.forEach(c => isNum(c) ? nqnum.get(n).push(c.qnum) : undefined); } let dc = cl.flatMap(c => adjdiaglist(c)); dc = unique(dc); dc.forEach(nc => dfs2(nc)); } dfs2(cell); }); forEachCell(cell => { if (!isBlack(cell) && !isGreen(cell)) { let t = unique(adj8list(cell).filter(c => isBlack(c)).map(c => ord.get(c))); let tt = t.flatMap(n => nshp.get(n)); if (unique(tt).length < tt.length) { add_green(cell); } let A000105 = [1, 1, 1, 2, 5, 12,]; // https://oeis.org/A000105 tt = t.flatMap(n => nqnum.get(n)); if (A000105.some((maxn, i) => tt.filter(n => n === i).length > maxn)) { add_green(cell); } } if (isBlack(cell)) { let cl = []; let dfs = function (c) { if (c.isnull || cl.includes(c) || !isBlack(c)) { return; } cl.push(c); fourside(dfs, c.adjacent); }; dfs(cell); let ol = unique(cl.flatMap(c => adjlist(c.adjacent)).filter(c => !c.isnull && !isGreen(c) && !isBlack(c))); if (cl.some(c => c.qnum === cl.length + 1)) { let t = ol.filter(c => nshp.get(ord.get(cell)).includes(getShape([...cl, c]))); t.forEach(c => add_green(c)); } if (nshp.get(ord.get(cell)).includes(getShape(cl)) && ol.length === 1) { add_black(ol[0]); ord.set(ol[0], ord.get(cell)); } } }); } function AhoniNarikireAssist() { SizeRegion_Border({ OneNumPerRegion: true, }); forEachCell(cell => { if (cell.qnum > 0 && cell.qnum % 3 !== 0) { let l = []; let dfs = function (c) { if (l.includes(c)) { return; } l.push(c); fourside((nb, nc) => { if (isLink(nb)) { dfs(nc); } }, c.adjborder, c.adjacent); } dfs(cell); let lbx = l.reduce((a, b) => Math.min(a, b.bx), Infinity); let rbx = l.reduce((a, b) => Math.max(a, b.bx), 0); let lby = l.reduce((a, b) => Math.min(a, b.by), Infinity); let rby = l.reduce((a, b) => Math.max(a, b.by), 0); for (let i = lbx; i <= rbx; i += 2) { for (let j = lby; j <= lby; j += 2) { if (i < rbx) { add_link(board.getb(i + 1, j)); } if (j < rby) { add_link(board.getb(i, j + 1)); } } } let f = (l => { if (l.some(b => isSide(b))) { l.forEach(b => add_side(b)); } }); f(Array((rbx - lbx) / 2 + 1).fill(0).map((_, i) => board.getb(lbx + i * 2, lby - 1))); f(Array((rbx - lbx) / 2 + 1).fill(0).map((_, i) => board.getb(lbx + i * 2, rby + 1))); f(Array((rby - lby) / 2 + 1).fill(0).map((_, i) => board.getb(lbx - 1, lby + i * 2))); f(Array((rby - lby) / 2 + 1).fill(0).map((_, i) => board.getb(rbx + 1, lby + i * 2))); } }); } function MaxiLoopAssist() { SingleLoopInCell({ isPass: () => true, }); let ln = new Map(); // line segment length let sc = new Map(); forEachCell(cell => { if (ln.has(cell) || cell.room.top.qnum < 1) { return; } let l = []; let dfs = function (c) { if (l.includes(c)) { return; } l.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (nb.isnull || isBound(nb) || !isLine(nb)) { return; } dfs(nc); }); }; dfs(cell); l.forEach(c => { ln.set(c, l.length); sc.set(c, cell); }); }); forEachBorder(border => { if (isBound(border) || isCross(border) || isLine(border)) { return; } let [c1, c2] = border.sidecell; if (!ln.has(c1) || !ln.has(c2) || c1.room.top.qnum < 1) { return; } if (!sc.has(c1) || !sc.has(c2) || sc.get(c1) === sc.get(c2)) { return; } if (ln.get(c1) + ln.get(c2) > c1.room.top.qnum) { add_cross(border); } }); } function ChoconaAssist() { RectRegion_Cell({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, isSizeAble: (w, h, sc, c) => { let s = new Set(); for (let i = -1; i <= w + 1; i++) { for (let j = -1; j <= h + 1; j++) { let cc = offset(c, i, j); if (cc.isnull || s.has(cc.room)) { continue; } s.add(cc.room); let l = Array.from(cc.room.clist); let qnum = cc.room.top.qnum; if (qnum >= 0 && l.filter(lc => isBlack(lc) || lc.bx >= c.bx && lc.bx <= c.bx + (w - 1) * 2 && lc.by >= c.by && lc.by <= c.by + (h - 1) * 2 ).length > qnum) { return false; } if (qnum >= 0 && l.filter(lc => isGreen(lc) || (lc.bx === c.bx - 2 || lc.bx === c.bx + w * 2) && (lc.by >= c.by && lc.by <= c.by + (h - 1) * 2) || (lc.by === c.by - 2 || lc.by === c.by + h * 2) && (lc.bx >= c.bx && lc.bx <= c.bx + (w - 1) * 2) ).length > l.length - qnum) { return false; } } } return true; } }); forEachRoom(room => { if (room.top.qnum === CQNUM.none || room.top.qnum === CQNUM.quesmark) { return; } NShadeInClist({ n: room.top.qnum, clist: Array.from(room.clist), }); }); } function MejilinkAssist() { forEachBorder(b => b.ques === 1 ? add_cross(b) : undefined); SingleLoopInBorder(); for (let i = 0; i < board.tilegraph.components.length; i++) { let tile = board.tilegraph.components[i]; if (tile.count < 0) { continue; } let clist = Array.from(tile.clist); let blist = []; clist.forEach(c => blist.push(...adjlist(c.adjborder).filter(b => b.ques === 0))); NShadeInClist({ isShaded: isCross, isUnshaded: isLine, add_shaded: add_cross, add_unshaded: add_line, n: clist.length, clist: blist, }); } } function JuosanAssist() { let add_bar = function (c, n) { // 12:| 13:- if (c === undefined || c.isnull || c.qans !== CQANS.none || ![12, 13].includes(n)) { return; } if (step && flg) { return; } flg = true; c.setQans(n); c.draw(); }; forEachCell(cell => { if ([[-2, -1], [-1, 1], [1, 2]].some(l => l.every(x => (c => !c.isnull && c.qans === 12)(offset(cell, x, 0))))) { add_bar(cell, 13); } if ([[-2, -1], [-1, 1], [1, 2]].some(l => l.every(y => (c => !c.isnull && c.qans === 13)(offset(cell, 0, y))))) { add_bar(cell, 12); } }); forEachRoom(room => { let qnum = room.top.qnum; let clist = Array.from(room.clist); if (qnum === CQNUM.none || qnum === CQNUM.quesmark) { return; } if (qnum > clist.filter(c => c.qans !== 12).length || qnum * 2 === clist.length) { NShadeInClist({ isShaded: c => c.qans === 12, isUnshaded: c => c.qans === 13, add_shaded: c => add_bar(c, 12), add_unshaded: c => add_bar(c, 13), n: qnum, clist: clist, }); } if (qnum > clist.filter(c => c.qans !== 13).length) { NShadeInClist({ isShaded: c => c.qans === 13, isUnshaded: c => c.qans === 12, add_shaded: c => add_bar(c, 13), add_unshaded: c => add_bar(c, 12), n: qnum, clist: clist, }); } }); } function IslandsAssist() { CellConnected_InRegion({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, SizeClue: true, }); BlackNotAdjacent_OverBorder(); forEachRoom(room => { if (room.top.qnum !== CQNUM.none) { return; } let clist = Array.from(room.clist); let s = new Set(), t, maxn = 0, minn = Math.max(1, clist.filter(c => isBlack(c)).length); let dfs = function (c) { if (c.isnull || c.room !== room || isGreen(c) || s.has(c)) { return; } s.add(c); t++; adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => !isBound(nb) ? dfs(nc) : undefined); }; clist.forEach(c => { t = 0; dfs(c); maxn = Math.max(maxn, t); }); let cand = Array(maxn - minn + 1).fill(0).map((_, i) => minn + i); clist.forEach(c => adjlist(c.adjacent).forEach(nc => { if (nc.isnull || nc.room === room) { return; } let t = nc.room.top.qnum; let nclist = Array.from(nc.room.clist); if (nclist.every(c => isBlack(c) || isGreen(c))) { t = nclist.filter(c => isBlack(c)).length; } cand = cand.filter(n => n !== t); })); if (cand.length === 1) { NShadeConnectedInRoom({ room: room, qnum: cand[0] }); } }); } function BoxAssist() { let f = function (l, qnum) { let cand = Array(1 << (l.length)).fill(0).map((_, i) => i); let t = parseInt(l.map(c => isBlack(c) || isDot(c) ? '1' : '0').join(''), 2); cand = cand.filter(n => (n & t) === 0); t = parseInt(l.map(c => isBlack(c) ? '1' : '0').join(''), 2); cand = cand.map(n => (n | t)); cand = cand.filter(n => n.toString(2).padStart(l.length, '0').split('').map((c, i) => c === '1' ? i + 1 : 0).reduce((a, b) => a + b) === qnum); let b = cand.reduce((a, b) => (a & b), (1 << l.length) - 1); let d = cand.reduce((a, b) => (a & ~b), (1 << l.length) - 1); b.toString(2).padStart(l.length, '0').split('').forEach((c, i) => c === '1' ? add_black(l[i]) : undefined); d.toString(2).padStart(l.length, '0').split('').forEach((c, i) => c === '1' ? add_dot(l[i]) : undefined); }; for (let i = 0; i < board.cols; i++) { let l = []; for (let j = 0; j < board.rows; j++) { l.push(board.getc(i * 2 + 1, j * 2 + 1)); } f(l, board.getobj(i * 2 + 1, -1).qnum); } for (let j = 0; j < board.rows; j++) { let l = []; for (let i = 0; i < board.cols; i++) { l.push(board.getc(i * 2 + 1, j * 2 + 1)); } f(l, board.getobj(-1, j * 2 + 1).qnum); } } function RassiSilaiAssist() { CellConnected_InRegion({ isShaded: () => true, isUnshaded: () => false, add_shaded: () => { }, add_unshaded: () => { }, ByLine: true, }); let add_Ocell = function (c) { if (c === undefined || c.isnull || c.qsub !== CQSUB.none) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.circle); c.draw(); }; let add_Xcell = function (c) { if (c === undefined || c.isnull || c.qsub !== CQSUB.none) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.cross); c.draw(); }; forEachBorder(border => { if (isBound(border)) { add_cross(border); } if ((border.sidecell[0].path ?? -1) === border.sidecell[1].path) { add_cross(border); } if (!isCross(border) && !isBound(border) && border.sidecell.every(c => adjlist(c.adjborder).filter(b => isntLine(b)).length >= 2)) { add_line(border); } }); forEachCell(cell => { if (cell.ques === 7) { fourside(add_cross, cell.adjborder); return; } if (adjlist(cell.adjborder).filter(b => isntLine(b)).length === 3) { add_Ocell(cell); } if (cell.lcnt === 2) { add_Xcell(cell); } if (adjlist(cell.adjborder, cell.adjacent).filter(([nb, nc]) => !isBound(nb) && !isntLine(nb) && nc.room === cell.room && adjlist(nc.adjborder).filter(b => isBound(b) || isntLine(b)).length >= 2).length >= 2) { add_Xcell(cell); } if (isOcell(cell)) { adj8list(cell).forEach(c => add_Xcell(c)); NShadeInClist({ isShaded: isLine, isUnshaded: isntLine, add_shaded: add_line, add_unshaded: add_cross, n: 1, clist: adjlist(cell.adjborder), }); } if (isXcell(cell)) { NShadeInClist({ isShaded: isLine, isUnshaded: isntLine, add_shaded: add_line, add_unshaded: add_cross, n: 2, clist: adjlist(cell.adjborder), }); } }); forEachRoom(room => { NShadeInClist({ isShaded: isOcell, isUnshaded: isXcell, add_shaded: add_Ocell, add_unshaded: add_Xcell, n: 2, clist: Array.from(room.clist), }); }); } function BosanowaAssist() { let add_bn = function (b, n) { if (b === undefined || b.isnull || b.qsub !== -1) { return; } if (step && flg) { return; } flg = true; b.setQsub(n); b.draw(); }; forEachCell(cell => { if (cell.ques !== 0) { return; } if (cell.qnum !== CQNUM.none) { add_number(cell, cell.qnum); } adjlist(cell.adjborder, cell.adjacent).forEach(([nb, nc]) => { if (!nc.isnull && cell.anum !== CANUM.none && nc.anum !== CANUM.none) { add_bn(nb, Math.abs(cell.anum - nc.anum)); } }); let n = 0; adjlist(cell.adjborder, cell.adjacent).forEach(([nb, nc]) => { if (!nc.isnull && nc.ques === 0 && nb.qsub !== -1) { n += nb.qsub; } }); if (adjlist(cell.adjborder, cell.adjacent).every(([nb, nc]) => nc.isnull || nc.ques !== 0 || nb.qsub !== -1)) { add_number(cell, n); } if (n === cell.anum) { adjlist(cell.adjborder, cell.adjacent).forEach(([nb, nc]) => nc.ques === 0 ? add_bn(nb, 0) : undefined); } if (cell.anum !== CANUM.none && adjlist(cell.adjborder, cell.adjacent).filter(([nb, nc]) => !nc.isnull && nc.ques === 0 && nb.qsub === -1).length === 1) { let [nb, nc] = adjlist(cell.adjborder, cell.adjacent).find(([nb, nc]) => !nc.isnull && nc.ques === 0 && nb.qsub === -1); add_bn(nb, cell.anum - n); } for (let d = 0; d < 4; d++) { let nb = offset(cell, .5, 0, d); let nc = offset(cell, 1, 0, d); if (cell.anum !== CANUM.none && nb.qsub !== -1 && cell.anum < nb.qsub * 2) { add_number(nc, cell.anum + nb.qsub); } if (cell.anum !== CANUM.none && nb.qsub === 0) { add_number(nc, cell.anum); } } }); } function RippleEffectAssist() { forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_sudokunum(cell, cell.qnum); } if (cell.anum !== CANUM.none) { return; } let cand = new Array(cell.room.clist.length).fill(0).map((_, i) => i + 1); let t = Array.from(cell.room.clist).map(c => c.anum); cand = cand.filter(n => !t.includes(n)); for (let d = 0; d < 4; d++) { for (let i = 1; !offset(cell, i, 0, d).isnull; i++) { if (offset(cell, i, 0, d).anum < i) { continue; } cand = cand.filter(n => n !== offset(cell, i, 0, d).anum); } } let fn = function (clist) { let a = new Array(cell.room.clist.length).fill(0).map((_, i) => i + 1); clist.forEach((c, i) => { if (cell === c || c.isnull) { return; } let b = c.anum === -1 && c.snum.every(n => n === -1); a = b ? [] : a.filter(n => n !== c.anum && !c.snum.includes(n)); if (c.anum !== -1) { cand = cand.filter(n => n !== c.anum); } if (c.anum === -1 && c.snum.filter(n => n !== -1).length > 0) { for (let j = i + 1; j < clist.length; j++) { let c2 = clist[j]; if (cell === c2 || c2.isnull || c2.anum !== -1 || c2.snum.every(n => n === -1)) { continue; } let l2 = new Set([...c.snum, ...c2.snum].filter(n => n !== -1)); if (l2.size === 2) { cand = cand.filter(n => !l2.has(n)); } for (let k = j + 1; k < clist.length; k++) { let c3 = clist[k]; if (cell === c3 || c3.isnull || c3.anum !== -1 || c3.snum.every(n => n === -1)) { continue; } let l3 = new Set([...c.snum, ...c2.snum, ...c3.snum].filter(n => n !== -1)); if (l3.size === 3) { cand = cand.filter(n => !l3.has(n)); } for (let l = k + 1; l < clist.length; l++) { let c4 = clist[l]; if (cell === c4 || c4.isnull || c4.anum !== -1 || c4.snum.every(n => n === -1)) { continue; } let l4 = new Set([...c.snum, ...c2.snum, ...c3.snum, ...c4.snum].filter(n => n !== -1)); if (l4.size === 4) { cand = cand.filter(n => !l4.has(n)); } } } } } }); if (a.length === 1) { add_sudokunum(cell, a[0]); } } fn(Array.from(cell.room.clist)); add_candidate(cell, cand); }); } function HerugolfAssist() { forEachCell(cell => { if (cell.qnum === CQNUM.none) { return; } let fn = function (c, d, x) { if (c.qnum !== CQNUM.none && c.lcnt === 1 && !isLine(offset(c, .5, 0, d))) { return false; } if (c.qnum === CQNUM.none && c.lcnt === 2 && !isLine(offset(c, .5, 0, d))) { return false; } for (let i = 1; i <= x; i++) { let nc = offset(c, i, 0, d); if (nc.qnum !== CQNUM.none) { return false; } if (i < x && nc.ques === 31) { return false; } if (i < x && (isLine(offset(nc, 0, -.5, d)) || isLine(offset(nc, 0, +.5, d)))) { return false; } if (i === x) { if (nc.ques === 6) { return false; } if (x === 1 && nc.ques !== 31) { return false; } if (nc.lcnt === 2 && !isLine(offset(c, i - .5, 0, d))) { return false; } if (nc.ques === 31 && nc.lcnt === 1 && !isLine(offset(c, i - .5, 0, d))) { return false; } } } return true; } let cand = []; let dfs = function (c, n, p, clist) { if (c.ques === 31) { cand.push(p); return; } if (n === 0) { return; } for (let d = 0; d < 4; d++) { if (p.length !== 0 && (d + 2) % 4 === p[p.length - 1]) { continue; } let l = Array(n).fill(0).map((_, i) => offset(c, i + 1, 0, d)); if (l.some(c => clist.includes(c))) { continue; } if (fn(c, d, n)) { dfs(offset(c, n, 0, d), n - 1, [...p, d], [...clist, ...l]); } } } dfs(cell, cell.qnum, [], [cell]); let c = cell, n = cell.qnum; for (let i = 0; cand.length > 0 && cand.every(s => s.length > i && s[i] === cand[0][i]); i++) { for (let k = 0; k < n; k++) { add_line(offset(c, .5 + k, 0, cand[0][i])); } c = offset(c, n, 0, cand[0][i]); n--; } }); } function MartiniAssist() { BlackNotAdjacent_OverBorder(); BlackConnected_InRegion(); CellConnected({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, DiagDir: true, }); forEachCell(cell => { if (cell.qnum === 0) { add_black(cell); return; } if (cell.qnum > 0 || cell.qnum === -2) { add_green(cell); } if (!isBlack(cell) && !isGreen(cell)) { let cset = new Set(); let dfs = function (c) { if (!isGreen(c) || cset.has(c)) { return; } cset.add(c); fourside(dfs, c.adjacent); }; fourside(dfs, cell.adjacent); let l = Array.from(cset).flatMap(c => c.qnum > 0 ? c.qnum : []); if (l.length > 0 && l.some(n => n !== l[[0]])) { add_black(cell); } if (l.length > 0 && Array.from(cset).filter(c => c.qnum !== CQNUM.none && c.qnum !== 0).length > l[0]) { add_black(cell); } } }); } function WallLogicAssist() { let emptycnt = board.cols * board.rows; forEachCell(cell => { if (cell.ques === 1) { emptycnt--; } if (cell.qnum !== CQNUM.none) { emptycnt -= (cell.qnum + 1); } }); forEachCell(cell => { if (cell.qnum > 0) { let ol = [0, 1, 2, 3].map(d => { let n = 0, anum = [4, 1, 3, 2][d]; while ((c => !c.isnull && c.ques !== 1 && !isNum(c) && (c.anum === CANUM.none || c.anum === anum))(offset(cell, n + 1, 0, d))) { n++; } return n; }); for (let d = 0; d < 4; d++) { for (let i = 1; i <= cell.qnum - ol.reduce((a, b) => a + b) + ol[d]; i++) { add_number(offset(cell, i, 0, d), [4, 1, 3, 2][d]); } } } if (emptycnt === 0 && cell.qnum === CQNUM.none && cell.ques !== 1 && cell.anum === CANUM.none) { let cl = [0, 1, 2, 3].map(d => { let n = 1; while ((c => !c.isnull && c.ques !== 1 && c.qnum === CQNUM.none && (c.anum === CANUM.none || c.anum === [3, 2, 4, 1][d]))(offset(cell, n, 0, d))) { n++; } let nc = offset(cell, n, 0, d); if (nc.qnum <= 0) { return -1; } let ol = [0, 1, 2, 3].map(d => { let n = 0, anum = [4, 1, 3, 2][d]; while ((c => !c.isnull && c.anum === anum)(offset(nc, n + 1, 0, d))) { n++; } return n; }); if (nc.qnum - ol.reduce((a, b) => a + b) + ol[(d + 2) % 4] < n) { return -1; } return n; }); if (cl.filter(n => n !== -1).length === 1) { cl.forEach((n, d) => { for (let i = 0; i < n; i++) { add_number(offset(cell, i, 0, d), [3, 2, 4, 1][d]); } }); } } if (cell.anum !== CANUM.none) { let d = [-1, 3, 1, 0, 2][cell.anum]; let nc = offset(cell, 1, 0, d); if (!nc.isnull && !isNum(nc)) { add_number(nc, cell.anum); } } }); } function BosnianRoadAssist() { let isntBlack = c => c.isnull || isDot(c) || c.qnum !== CQNUM.none; forEachCell(cell => { let clist = adj8list(cell); if (cell.qnum >= 0) { NShadeInClist({ isUnshaded: isntBlack, add_unshaded: add_dot, n: cell.qnum, clist: clist, }); } if (isBlack(cell)) { NShadeInClist({ isUnshaded: isntBlack, add_unshaded: add_dot, n: 2, clist: adjlist(cell.adjacent), }); } if (adjlist(cell.adjacent).filter(c => isntBlack(c)).length >= 3) { add_dot(cell); } if (adjlist(cell.adjacent).filter(c => isBlack(c)).length >= 3) { add_dot(cell); } if (board.sblkmgr.components.length > 1 && clist.some((c1, i) => !c1.isnull && c1.sblk !== null && clist.some((c2, j) => Math.abs(i - j) > 1 && Math.abs(i - j) < 7 && c1.sblk === c2.sblk))) { add_dot(cell); } for (let d = 0; d < 4; d++) { if (isBlack(offset(cell, 1, 0, d)) && isBlack(offset(cell, 0, 1, d)) && isntBlack(offset(cell, 1, 1, d))) { add_black(cell); } if (isBlack(offset(cell, 1, 0, d)) && isBlack(offset(cell, 0, 1, d)) && isBlack(offset(cell, -1, -1, d))) { add_dot(cell); } if (isBlack(offset(cell, 1, 1, d)) && isBlack(offset(cell, 1, -1, d)) && isBlack(offset(cell, -1, 0, d))) { add_dot(cell); } if (isBlack(offset(cell, 1, 1, d)) && isBlack(offset(cell, 1, -1, d)) && isBlack(offset(cell, -1, 1, d))) { add_dot(cell); } if (isBlack(offset(cell, 1, 1, d)) && (isntBlack(offset(cell, 1, 0, d)) && isntBlack(offset(cell, 0, 1, d)) || isBlack(offset(cell, 1, 0, d)) && isBlack(offset(cell, 0, 1, d)))) { add_dot(cell); } if ([[-1, 0], [0, -1], [1, -1], [2, 0]].every(([x, y]) => isntBlack(offset(cell, x, y, d)))) { add_dot(cell); } } }); } function SnakeAssist() { let isntBlack = c => c.isnull || isDot(c) || c.bx < 0 || c.by < 0; CellConnected({ isShaded: isBlack, isUnshaded: isntBlack, add_shaded: add_black, add_unshaded: add_dot, }); let heads = []; forEachCell(cell => { if (cell.qnum === 2 || isBlack(cell) && adjlist(cell.adjacent).filter(c => isntBlack(c)).length === 3) { heads.push(cell); } }); for (let i = 1; i < board.cols * 2; i += 2) { let l = []; for (let j = 1; j < board.rows * 2; j += 2) { l.push(board.getc(i, j)); } NShadeInClist({ isUnshaded: isntBlack, add_unshaded: add_dot, n: board.getex(i, -1).qnum, clist: l, }); } for (let j = 1; j < board.rows * 2; j += 2) { let l = []; for (let i = 1; i < board.cols * 2; i += 2) { l.push(board.getc(i, j)); } NShadeInClist({ isUnshaded: isntBlack, add_unshaded: add_dot, n: board.getex(-1, j).qnum, clist: l, }); } forEachCell(cell => { let clist = adj8list(cell); if (cell.qnum !== CQNUM.none) { add_black(cell); } if (cell.qnum === 1 || isBlack(cell) && heads.length === 2 && !heads.includes(cell)) {// white NShadeInClist({ isUnshaded: isntBlack, add_unshaded: add_dot, n: 2, clist: adjlist(cell.adjacent), }); } if (cell.qnum === 2) {// black NShadeInClist({ isUnshaded: isntBlack, add_unshaded: add_dot, n: 1, clist: adjlist(cell.adjacent), }); } if (isBlack(cell) && adjlist(cell.adjacent).filter(c => isBlack(c)).length === 2) { adjlist(cell.adjacent).forEach(c => add_dot(c)); } if (adjlist(cell.adjacent).filter(c => isBlack(c)).length >= 3) { adjlist(cell.adjacent).forEach(c => add_dot(c)); add_dot(cell); } if (board.sblkmgr.components.length > 1 && clist.some((c1, i) => !c1.isnull && c1.sblk !== null && c1.sblk !== undefined && clist.some((c2, j) => Math.abs(i - j) > 1 && Math.abs(i - j) < 7 && c1.sblk === c2.sblk))) { add_dot(cell); } for (let d = 0; d < 4; d++) { if (isBlack(offset(cell, 1, 0, d)) && isBlack(offset(cell, 0, 1, d)) && isntBlack(offset(cell, 1, 1, d))) { add_black(cell); } if (isBlack(offset(cell, 1, 0, d)) && isBlack(offset(cell, 0, 1, d)) && isBlack(offset(cell, -1, -1, d))) { add_dot(cell); } if (isBlack(offset(cell, 1, 1, d)) && isBlack(offset(cell, 1, -1, d)) && isBlack(offset(cell, -1, 0, d))) { add_dot(cell); } if (isBlack(offset(cell, 1, 1, d)) && isBlack(offset(cell, 1, -1, d)) && isBlack(offset(cell, -1, 1, d))) { add_dot(cell); } if (isBlack(offset(cell, 1, 1, d)) && (isntBlack(offset(cell, 1, 0, d)) && isntBlack(offset(cell, 0, 1, d)) || isBlack(offset(cell, 1, 0, d)) && isBlack(offset(cell, 0, 1, d)))) { add_dot(cell); } } }); } function LookAirAssist() { let SquSize = new Map(); let cset = new Set(); forEachCell(cell => { if (cset.has(cell) || !isBlack(cell)) { return; } let cl = [], isFinished = true; let dfs = function (c) { if (!c.isnull && !isGreen(c) && !isBlack(c)) { isFinished = false; return; } if (c.isnull || !isBlack(c) || cset.has(c)) { return; } cset.add(c); cl.push(c); fourside(dfs, c.adjacent); }; dfs(cell); if (isFinished) { cl.forEach(c => SquSize.set(c, Math.sqrt(cl.length))); } }); RectRegion_Cell({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, isSizeAble: (w, h, sc, c) => { if (w !== h) { return false; } for (let i = 0; i < w; i++) { for (let j = 0; j < h; j++) { for (let d = 0; d < 4; d++) { let cc = offset(offset(c, i, j), 1, 0, d); while (isGreen(cc)) { cc = offset(cc, 1, 0, d); } if (SquSize.get(cc) === w) { return false; } } } } return true; } }); forEachCell(cell => { NShadeInClist({ clist: [...adjlist(cell.adjacent), cell], n: cell.qnum, }); if (cell.qnum === 4) { add_black(cell); } if (cell.qnum === 2) { add_green(cell); } for (let d = 0; d < 4; d++) { if (cell.qnum === 3 && isntBlack(offset(cell, -1, 0, d))) { add_black(offset(cell, 1, 0, d)); } if (isGreen(offset(cell, 1, 0, d)) && (isntBlack(offset(cell, -1, 0, d)) || (isntBlack(offset(cell, -1, -1, d)) || isntBlack(offset(cell, 0, -1, d))) && (isntBlack(offset(cell, -1, +1, d)) || isntBlack(offset(cell, 0, +1, d))))) { let cc = offset(cell, 1, 0, d); while (isGreen(cc)) { cc = offset(cc, 1, 0, d); } if (SquSize.get(cc) === 1) { add_green(cell); } } } }); } function TrenAssist() { forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_green(cell); } let l = [0, 1, 2, 3].map(d => ![1, 2].every(dd => ((nb, nc) => nc.isnull || nc.qnum === CQNUM.none || isSide(nb))(offset(cell, dd - .5, 0, d), offset(cell, dd, 0, d)))); if (cell.qnum === CQNUM.none && l.every(e => e === false)) { add_yellow(cell); } if (cell.qsub === CQNUM.green && cell.qnum === CQNUM.none && l.filter(e => e === true).length === 1) { l.forEach((e, d) => { if (e === false) { return; } add_green(offset(cell, 1, 0, d)); add_side(offset(cell, 1, -.5, d)); add_side(offset(cell, 1, +.5, d)); }); } for (let d = 0; d < 4; d++) { let ncell = offset(cell, 1, 0, d); if (cell.qnum !== CQNUM.none && ncell.qnum !== CQNUM.none) { add_side(offset(cell, .5, 0, d)); } if (isGreen(cell) && isYellow(ncell)) { add_side(offset(cell, .5, 0, d)); } if (isGreen(cell) && adjlist(cell.adjborder).every(b => b.isnull || isSide(b) || b === offset(cell, .5, 0, d))) { add_green(offset(cell, 1, 0, d)); add_side(offset(cell, 1, -.5, d)); add_side(offset(cell, 1, +.5, d)); } } // TODO: test all possible placement for one clue }); } function CocktailLampAssist() { BlackConnected_InRegion(true); BlackNotAdjacent_OverBorder(); No2x2Black(); CellConnected({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, DiagDir: true, }); } function MinesweeperAssist() { forEachCell(cell => { if (cell.qnum < 0) { return; } NShadeInClist({ isShaded: isBlack, isUnshaded: c => isDot(c) || c.qnum !== CQNUM.none, add_shaded: add_black, add_unshaded: add_dot, clist: adj8list(cell), n: cell.qnum, }); let ncl = []; for (let dx = -2; dx <= 2; dx++) { for (let dy = -2; dy <= 2; dy++) { if (dx === 0 && dy === 0) { continue; } let ncell = offset(cell, dx, dy); if (ncell.isnull || ncell.qnum < 0) { continue; } ncl.push(ncell); } } for (let i = 0; i < ncl.length; i++) { let ncell = ncl[i]; let l1 = adj8list(cell), l2 = adj8list(ncell); l1 = l1.filter(c => !c.isnull && c.qnum === CQNUM.none && !isDot(c) && !adj8list(ncell).includes(c)); l2 = l2.filter(c => !c.isnull && c.qnum === CQNUM.none && !isDot(c) && !adj8list(cell).includes(c)); if (l2.length - ncell.qnum === l1.filter(c => isBlack(c)).length - cell.qnum) { l2.forEach(c => add_black(c)); l1.forEach(c => add_dot(c)); } if (l1.length - cell.qnum === l2.filter(c => isBlack(c)).length - ncell.qnum) { l1.forEach(c => add_black(c)); l2.forEach(c => add_dot(c)); } for (let j = i + 1; j < ncl.length; j++) { let mcell = ncl[j]; let cl = [cell, ncell, mcell]; let g = cl.map(c => adj8list(c).filter(cc => !cc.isnull && cc.qnum === CQNUM.none && !isDot(cc))); let a = unique(g.flat()); let r = Array(8).fill(0).map((_, i) => a.filter(c => ((i & (1 << 0)) === 0) ^ (g[0].includes(c))) .filter(c => ((i & (1 << 1)) === 0) ^ (g[1].includes(c))) .filter(c => ((i & (1 << 2)) === 0) ^ (g[2].includes(c))) ); let n = r.map(l => Array(l.filter(c => !isBlack(c)).length + 1).fill(0) .map((_, i) => i + l.filter(c => isBlack(c)).length)); let cand = getComb([n[3], n[5], n[6], n[7]]); cand = cand.map(([n3, n5, n6, n7]) => [0, cl[0].qnum - n3 - n5 - n7, cl[1].qnum - n3 - n6 - n7, n3, cl[2].qnum - n5 - n6 - n7, n5, n6, n7]); cand = cand.filter(l => l.every((m, i) => m >= n[i][0] && m <= n[i][n[i].length - 1])); r.forEach((l, i) => { if (cand.length > 0 && cand.every(l => l[i] === cand[0][i])) { NShadeInClist({ isShaded: isBlack, isUnshaded: c => isDot(c) || c.qnum !== CQNUM.none, add_shaded: add_black, add_unshaded: add_dot, clist: l, n: cand[0][i], }); } }); } } }); } function TilepaintAssist() { forEachRoom(room => { let clist = Array.from(room.clist); if (clist.some(c => isBlack(c))) { clist.forEach(c => add_black(c)); } if (clist.some(c => isGreen(c))) { clist.forEach(c => add_green(c)); } }); let list = [...Array.from(board.cell), ...Array.from(board.excell)]; list = [...list.filter(c => c.qnum > 0).map(c => { let pc = offset(c, 1, 0), clist = []; while (!pc.isnull && pc.ques === CQUES.none) { clist.push(pc); pc = offset(pc, 1, 0); } return [c.qnum, clist]; }), ...list.filter(c => c.qnum2 > 0).map(c => { let pc = offset(c, 0, 1), clist = []; while (!pc.isnull && pc.ques === CQUES.none) { clist.push(pc); pc = offset(pc, 0, 1); } return [c.qnum2, clist]; })]; list = list.filter(([n, l]) => n !== CQNUM.none && l.length > 0); list.forEach(([n, clist]) => { n -= clist.filter(c => isBlack(c)).length; clist = clist.filter(c => !isBlack(c) && !isDot(c)); let rlist = new Set(); clist.forEach(c => rlist.add(c.room)); rlist = Array.from(rlist).map(room => [room, clist.filter(c => c.room === room).length]); rlist.forEach(([room, cnt]) => { if (cnt > n) { Array.from(room.clist).forEach(c => add_green(c)); } if (clist.length - cnt < n) { Array.from(room.clist).forEach(c => add_black(c)); } }); }); } function GoatsAndWolvesAssist() { CluePerRegion({ isShaded: c => c.qnum !== CQNUM.none, isOthers: c => c.qnum === CQNUM.none, isUnshaded: () => false, isNotPassable: (c, nb, nc) => isSide(nb), BridgeType: "link", }); forEachCross(cross => { if (cross.qnum === -1) { for (let d = 0; d < 4; d++) { if (isLink(offset(cross, -.5, 0, d))) { add_link(offset(cross, .5, 0, d)); } if (isSide(offset(cross, -.5, 0, d))) { add_side(offset(cross, .5, 0, d)); } } } if (cross.qnum === 1 && adjlist(cross.adjborder).every(b => !b.isnull)) { let blist = adjlist(cross.adjborder); if (blist.filter(b => isLink(b)).length >= 3) { blist.forEach(b => add_link(b)); } if (blist.filter(b => isSide(b)).length === 2) { blist.forEach(b => add_link(b)); } if (blist.filter(b => isSide(b)).length === 1 && blist.filter(b => isLink(b)).length === 2) { blist.forEach(b => add_side(b)); } } }); let DSU = new Map(); // Disjoint Set Union let DSUfind = function (n) { if (DSU.get(n) !== n) { DSU.set(n, DSUfind(DSU.get(n))); } return DSU.get(n); }; forEachCell(cell => { DSU.set(cell, cell); let cc = cell; if (isLink(offset(cell, -.5, 0))) { let nc = DSUfind(offset(cell, -1, 0)); if (cc.qnum !== CQNUM.none) { DSU.set(nc, cc); } else { DSU.set(cc, nc); } } cc = DSUfind(cell); if (isLink(offset(cell, 0, -.5))) { let nc = DSUfind(offset(cell, 0, -1)); if (cc.qnum !== CQNUM.none) { DSU.set(nc, cc); } else { DSU.set(cc, nc); } } }); forEachCell(cell => { for (let d = 0; d < 4; d++) { if (offset(cell, 0, 1, d).isnull) { continue; } let cc = DSUfind(cell); let nc = DSUfind(offset(cell, 0, 1, d)); if ((new Set([CQNUM.none, cc.qnum, nc.qnum])).size === 3) { add_side(offset(cell, 0, .5, d)); } } }); } function TawamurengaAssist() { let isntBlack = c => c.isnull || c.qsub === CQSUB.dot || c.qnum !== CQNUM.none; forEachCell(cell => { if (cell.by < board.rows * 2 - 1) { if (isBlack(cell) && isntBlack(offset(cell, -.5, 1))) { add_black(offset(cell, +.5, 1)); } if (isBlack(cell) && isntBlack(offset(cell, +.5, 1))) { add_black(offset(cell, -.5, 1)); } if (isntBlack(offset(cell, -.5, 1)) && isntBlack(offset(cell, +.5, 1))) { add_dot(cell); } } if (!isBlack(cell) && !isDot(cell) && !isNum(cell)) { if ([offset(cell, -2, 0), offset(cell, -1, 0)].every(c => isBlack(c))) { add_dot(cell); } if ([offset(cell, +1, 0), offset(cell, -1, 0)].every(c => isBlack(c))) { add_dot(cell); } if ([offset(cell, +1, 0), offset(cell, +2, 0)].every(c => isBlack(c))) { add_dot(cell); } } if (isNum(cell)) { let clist = [[-.5, -1], [.5, -1], [-1, 0], [1, 0], [-.5, 1], [.5, 1]].map(([x, y]) => offset(cell, x, y)); let setb = 0b111111, setd = 0b000000, n = 0; for (let j = 0; j <= 0b111111; j++) { let t = j.toString(2).padStart(6, '0').split(''); if (t.filter(n => n === '1').length !== cell.qnum) { continue; } if (t.some((n, i) => n === '0' && isBlack(clist[i]) || n === '1' && isntBlack(clist[i]))) { continue; } if (t[0] === '1' && t[2] === '0') { continue; } if (t[1] === '1' && t[3] === '0') { continue; } if (t[2] === '1' && t[4] === '0' && cell.by < board.rows * 2 - 1 && isntBlack(offset(cell, -1.5, 1))) { continue; } if (t[3] === '1' && t[5] === '0' && cell.by < board.rows * 2 - 1 && isntBlack(offset(cell, +1.5, 1))) { continue; } if (t[0] === '1' && t[1] === '1' && (isBlack(offset(cell, -1.5, -1)) || isBlack(offset(cell, +1.5, -1)))) { continue; } if (t[4] === '1' && t[5] === '1' && (isBlack(offset(cell, -1.5, +1)) || isBlack(offset(cell, +1.5, +1)))) { continue; } setb &= j; setd |= j; n++; } setb = setb.toString(2).padStart(6, '0'); setd = setd.toString(2).padStart(6, '0'); for (let j = 0; j < 6; j++) { if (setb[j] === '1') { add_black(clist[j]); } if (setd[j] === '0') { add_dot(clist[j]); } } } }); } function NondangoAssist() { forEachRoom(room => { NShadeInClist({ clist: Array.from(room.clist).filter(c => c.ques !== 8), n: 1, isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, }); }); forEachCell(cell => { if (cell.ques === 8) { return; } for (let d = 0; d < 4; d++) { let fn = function (c1, c2) { if (c1.isnull || c2.isnull) { return; } if (c1.ques === 8 || c2.ques === 8) { return; } if (isBlack(c1) && isBlack(c2)) { add_green(cell); } if (isGreen(c1) && isGreen(c2)) { add_black(cell); } if ([c1, c2].every(c => isGreen(c) || c.room === cell.room)) { Array.from(cell.room.clist).filter(c => c.ques !== 8 && ![cell, c1, c2].includes(c)).forEach(c => add_green(c)); } }; fn(offset(cell, 0, 1, d), offset(cell, 0, 2, d)); fn(offset(cell, 1, 1, d), offset(cell, 2, 2, d)); fn(offset(cell, 0, -1, d), offset(cell, 0, 1, d)); fn(offset(cell, -1, -1, d), offset(cell, 1, 1, d)); } }); } function RailPoolAssist() { SingleLoopInCell({ isPassable: c => c.ques !== CQUES.wall, isPass: c => c.ques !== CQUES.wall, }); forEachCell(cell => { if (cell.ques === CQUES.wall) { return; } let qnums = Array.from(cell.room.clist).flatMap(c => c.qnums); if (qnums.length !== 0 && !qnums.includes(CQNUM.quesmark)) { let maxd = qnums.reduce((a, b) => Math.max(a, b)); for (let d = 0; d < 2; d++) { let cand = []; if (!isntLine(offset(cell, -.5, 0, d)) && !isntLine(offset(cell, +.5, 0, d)) && !isLine(offset(cell, 0, -.5, d)) && !isLine(offset(cell, 0, +.5, d))) { cand.push([0, 0]); } let ll = 0, rr = 0; while (-ll < maxd && !isntLine(offset(cell, 0, ll - .5, d)) && (ll === 0 || !isLine(offset(cell, -.5, ll, d)) && !isLine(offset(cell, +.5, ll, d)))) { ll--; } while (+rr < maxd && !isntLine(offset(cell, 0, rr + .5, d)) && (rr === 0 || !isLine(offset(cell, -.5, rr, d)) && !isLine(offset(cell, +.5, rr, d)))) { rr++; } for (let l = ll; l <= 0; l++) { for (let r = 0; r <= rr; r++) { if (!qnums.includes(r - l)) { continue; } if (l !== 0 && r !== 0 && (isLine(offset(cell, -.5, 0, d)) || isLine(offset(cell, +.5, 0, d)))) { continue; } if (isLine(offset(cell, 0, l - .5, d)) || isLine(offset(cell, 0, r + .5, d))) { continue; } cand.push([l, r]); } } if (cand.length > 0) { let lm = cand.reduce((a, b) => Math.max(a, b[0]), -Infinity); let rm = cand.reduce((a, b) => Math.min(a, b[1]), Infinity); for (let dd = lm; dd < rm; dd++) { add_line(offset(cell, 0, dd + .5, d)); } if (cand.every(([l, r]) => l === lm)) { add_cross(offset(cell, 0, lm - .5, d)); } if (cand.every(([l, r]) => r === rm)) { add_cross(offset(cell, 0, rm + .5, d)); } } } } for (let d = 0; d < 4; d++) { if (qnums.length === 1 && qnums[0] === 1 && isntLine(offset(cell, 0, -.5, d))) { add_line(offset(cell, 0, .5, d)); } if (qnums.length === 1 && offset(cell, 0, -.5, d).isnull && !isBound(offset(cell, 0, .5, d))) { add_line(offset(cell, 0, .5, d)); } } }); } function DoubleBackAssist() { SingleLoopInCell({ isPassable: c => c.ques !== CQUES.wall, isPass: c => c.ques !== CQUES.wall, }); forEachRoom(room => { let oblist = []; Array.from(room.clist).forEach(cell => { fourside((nb, nc) => { if (!nc.isnull && nc.room !== room && nb.ques) { oblist.push(nb); } }, cell.adjborder, cell.adjacent); }); if (oblist.filter(b => b.line).length === 4) { oblist.forEach(b => add_cross(b)); } if (oblist.filter(b => !isCross(b)).length === 4) { oblist.forEach(b => add_line(b)); } }); } function HashiwokakeroAssist() { let add_line = function (b, n = 1) { if (b === undefined || b.isnull || n <= b.line || isCross(b)) { return; } if (![1, 2].includes(n)) { return; } if (step && flg) { return; } b.setLineVal(n); b.draw(); flg ||= b.line; }; CellConnected({ isShaded: c => c.lcnt > 0 || c.qnum !== CQNUM.none, isUnshaded: c => adjlist(c.adjborder).every(b => isntLine(b)), add_shaded: c => { if (c.qnum !== CQNUM.none) { return; } if (isntLine(c.adjborder.top)) { add_line(c.adjborder.left); add_line(c.adjborder.right); } if (isntLine(c.adjborder.left)) { add_line(c.adjborder.top); add_line(c.adjborder.bottom); } }, add_unshaded: c => adjlist(c.adjborder).forEach(b => add_cross(b)), isNotPassable: (c, nb, nc) => isCross(nb), BridgeType: "line", }); forEachCell(cell => { for (let d = 0; d < 4; d++) { if (cell.qnum === CQNUM.none && isntLine(offset(cell, .5, 0, d))) { add_cross(offset(cell, -.5, 0, d)); } if (cell.qnum === CQNUM.none && offset(cell, .5, 0, d).line) { add_line(offset(cell, -.5, 0, d), offset(cell, .5, 0, d).line); } } if (cell.qnum === CQNUM.none && cell.lcnt === 2) { adjlist(cell.adjborder).forEach(b => add_cross(b)); } if (cell.qnum === adjlist(cell.adjborder).reduce((a, b) => a + b.line, 0)) { adjlist(cell.adjborder).forEach(b => add_cross(b)); } if (adjlist(cell.adjborder).filter(b => !b.isnull && !isCross(b)).length === 1) { adjlist(cell.adjborder).forEach(b => add_line(b, 1)); } if (cell.qnum !== CQNUM.none) { let l = [0, 1, 2, 3].map(d => { let nc = offset(cell, 1, 0, d); if (isCross(offset(cell, .5, 0, d))) { return 0; } while (!nc.isnull && nc.qnum === CQNUM.none) { if (isCross(offset(nc, .5, 0, d))) { return 0; } nc = offset(nc, 1, 0, d); } if (nc.isnull) { return 0; } if (nc.qnum === CQNUM.quesmark) { return 2; } if (board.linegraph.components.length > 0 && cell.qnum === 1 && nc.qnum === 1) { return 0; } let ln = adjlist(nc.adjborder); ln.splice((d + 2) % 4, 1); ln = nc.qnum - ln.reduce((a, b) => a + b.line, 0); if (board.linegraph.components.length > 0 && cell.qnum === 2 && nc.qnum === 2) { return Math.min(ln, 1); } return Math.min(ln, 2); }); for (let d = 0; d < 4; d++) { if (l[d] === 0) { add_cross(offset(cell, .5, 0, d)); } let n = Math.min(cell.qnum - l.reduce((a, b) => a + b) + l[d], 2); if (cell.qnum >= 0 && n >= 1) { add_line(offset(cell, .5, 0, d), n); } } } }); } function CountryRoadAssist() { let add_Ocell = function (c) { if (c === undefined || c.isnull || c.qsub !== CQSUB.none) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.circle); c.draw(); }; let add_Xcell = function (c) { if (c === undefined || c.isnull || c.qsub !== CQSUB.none) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.cross); c.draw(); }; CellConnected_InRegion({ isShaded: c => c.qsub === CQSUB.circle, isUnshaded: c => c.qsub === CQSUB.cross, add_shaded: add_Ocell, add_unshaded: add_Xcell, SizeClue: true, ByLine: true, }); RoomPassOnce({ LengthClue: true, }); SingleLoopInCell({ isPass: c => c.qsub === CQSUB.circle, isNotPassable: c => c.qsub === CQSUB.cross, }); forEachRoom(room => { NShadeInClist({ isShaded: c => c.qsub === CQSUB.circle, isUnshaded: c => c.qsub === CQSUB.cross, add_shaded: add_Ocell, add_unshaded: add_Xcell, n: room.top.qnum, clist: Array.from(room.clist), }); if (Array.from(room.clist).length === 1) { add_Ocell(room.clist[0]); } }); forEachCell(cell => { if (adjlist(cell.adjborder).every(b => isntLine(b))) { add_Xcell(cell); } if (cell.qsub === CQSUB.cross) { adjlist(cell.adjborder).forEach(b => add_cross(b)); for (let d = 0; d < 4; d++) { let nb = offset(cell, 0.5, 0, d); let nc = offset(cell, 1, 0, d) if (!nb.isnull && nb.ques) { add_Ocell(nc); } } } if (cell.lcnt > 0) { add_Ocell(cell); } if (adjlist(cell.adjborder).filter(b => isntLine(b)).length === 2) { adjlist(cell.adjborder, cell.adjacent).filter(([nb, nc]) => !nc.isnull && !isntLine(nb) && isBound(nb)).forEach(([nb, nc]) => add_Ocell(nc)); } if (cell.room.top.qnum === 1 && adjlist(cell.adjborder).filter(b => isBound(b) && !isntLine(b)).length < 2) { add_Xcell(cell); } }); } function MochikoroAssist() { let cluecnt = 0; forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_green(cell); cluecnt++; } }); No2x2Black(); RectRegion_Cell({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, isSizeAble: (w, h, sc, c) => { if (sc !== null && sc.qnum !== w * h) { return false; } if (cluecnt > 1 && [offset(c, -1, -1), offset(c, -1, h), offset(c, w, -1), offset(c, w, h)].every(c => c.isnull || isBlack(c))) { return false; } for (let i = 0; i < w; i++) { for (let j = 0; j < h; j++) { if (offset(c, i, j).qnum > 0 && (offset(c, i, j) !== sc && sc !== null || offset(c, i, j).qnum !== w * h)) { return false; } } } return true; }, }); GreenConnectedDiagonally(); } function NumberlinkAssist() { forEachCell(cell => { let num = undefined; if (cell.path !== null) { num = Array.from(cell.path.clist).find(c => c.qnum !== CQNUM.none); } if (num !== undefined) { num = num.qnum; } if (cell.qnum !== CQNUM.none) { num = cell.qnum; } if (cell.lcnt === 2 || cell.lcnt === 1 && cell.qnum !== CQNUM.none) { adjlist(cell.adjborder).forEach(b => add_cross(b)); } if (cell.qnum === CQNUM.none && adjlist(cell.adjborder).filter(b => isntLine(b)).length >= 3) { adjlist(cell.adjborder).forEach(b => add_cross(b)); } if (num !== undefined) { adjlist(cell.adjborder, cell.adjacent).forEach(([nb, nc]) => { if (nc.isnull || isCross(nb)) { return; } if (nc.path === null && nc.qnum !== CQNUM.none && nc.qnum !== num || nc.path !== null && Array.from(nc.path.clist).some(c => c.qnum !== CQNUM.none && c.qnum !== num)) { add_cross(nb); } }); } let l = adjlist(cell.adjborder, cell.adjacent).filter(([nb, nc]) => isLine(nb) || !nc.isnull && !isCross(nb)); if (cell.qnum === CQNUM.none && cell.lcnt === 1 && l.length === 2 || cell.qnum !== CQNUM.none && l.length === 1) { l.forEach(([nb, nc]) => add_line(nb)); } }); // using uniquity forEachCross(cross => { if (adjlist(cross.adjborder).filter(b => isLine(b)).length === 2) { adjlist(cross.adjborder).forEach(b => add_cross(b)); } }); } function SukoroAssist() { let add_Ocell = function (c) { if (c === undefined || c.isnull || c.anum !== CANUM.none || c.qsub !== CQSUB.none || c.qnum !== CQNUM.none) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.circle); c.draw(); }; let add_number = function (c, n) { if (c === undefined || c.isnull || c.anum !== CANUM.none) { return; } if (step && flg) { return; } flg = true; c.setAnum(n); c.setQsub(CQSUB.none); c.draw(); }; CellConnected({ isShaded: c => c.anum !== CANUM.none || c.qsub === CQSUB.circle, isUnshaded: c => c.qsub === CQSUB.cross, add_shaded: add_Ocell, add_unshaded: add_Xcell, }); let not1c = new Set(); CellConnected({ isShaded: c => c.anum !== CANUM.none && c.anum !== 1, isUnshaded: c => c.qsub === CQSUB.cross || c.anum === 1, add_shaded: c => { not1c.add(c); add_Ocell(c); }, add_unshaded: add_Xcell, }); forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_number(cell, cell.qnum); } if (cell.anum !== CANUM.none) { if (adjlist(cell.adjacent).filter(c => c.anum !== CANUM.none || c.qsub === CQSUB.circle).length === cell.anum) { adjlist(cell.adjacent).forEach(c => add_Xcell(c)); } if (adjlist(cell.adjacent).filter(c => !c.isnull && c.qsub !== CQSUB.cross).length === cell.anum) { adjlist(cell.adjacent).forEach(c => add_Ocell(c)); } } let l = [2, 3, 4]; if (!not1c.has(cell)) { l.push(1); } l = l.filter(n => n >= adjlist(cell.adjacent).filter(c => c.anum !== CANUM.none || c.qsub === CQSUB.circle).length); l = l.filter(n => n <= adjlist(cell.adjacent).filter(c => !c.isnull && c.qsub !== CQSUB.cross).length); l = l.filter(n => adjlist(cell.adjacent).every(c => c.anum !== n)); if (cell.qsub === CQSUB.circle && l.length === 1) { add_number(cell, l[0]); } if (l.length === 0) { add_Xcell(cell); } for (let d = 0; d < 4; d++) { if (cell.anum === 3 && offset(cell, 1, 1, d).anum === 1) { add_Ocell(offset(cell, 0, -1, d)); add_Ocell(offset(cell, -1, 0, d)); add_Xcell(offset(cell, 1, 2, d)); add_Xcell(offset(cell, 2, 1, d)); } let l = [offset(cell, 1, -1, d), offset(cell, 1, 1, d), offset(cell, 2, 0, d)]; if (cell.qsub === CQSUB.none && offset(cell, 1, 0, d).qsub === CQSUB.circle && l.every(c => c.isnull || c.qsub !== CQSUB.none || c.anum !== CANUM.none || c === cell) && l.filter(c => c.isnull || c.qsub === CQSUB.cross).length === 2 && adjlist(cell.adjacent).every(c => c.isnull || c.qsub !== CQSUB.none || c.anum !== CANUM.none) && adjlist(cell.adjacent).filter(c => c.isnull || c.qsub === CQSUB.cross).length === 2 ) { add_Xcell(cell); } } }); } function SashiganeAssist() { NoDeadendBorder(); forEachCell(cell => { // add side around arrow if (cell.qdir !== 0 && cell.qdir !== 5) { let d = qdirRemap(cell.qdir); [1, 2, 3].forEach(n => add_side(offset(cell, .5, 0, d + n))); } // at most 2 link for each cell if (adjlist(cell.adjborder).filter(b => isLink(b)).length === 2) { adjlist(cell.adjborder).forEach(b => add_side(b)); } // extend the endpoint of a L if (adjlist(cell.adjborder).filter(b => !b.isnull && !isSide(b)).length === 1) { let d = [0, 1, 2, 3].find(n => (b => !b.isnull && !isSide(b))(offset(cell, .5, 0, n))); add_link(offset(cell, .5, 0, d)); let ncell = offset(cell, 1, 0, d); while ([d + 1, d - 1].every(n => (b => b.isnull || isSide(b))(offset(ncell, .5, 0, n)))) { add_side(offset(ncell, 0, -.5, d)); add_side(offset(ncell, 0, +.5, d)); add_link(offset(ncell, .5, 0, d)); ncell = offset(ncell, 1, 0, d); } if (ncell.qnum !== CQNUM.none) { add_side(offset(ncell, .5, 0, d)); } if (!offset(ncell, 1, -1, d).isnull && [[-.5, -1], [0, -1.5], [1.5, 0], [1, .5]].every(([x, y]) => (b => b.isnull || isSide(b))(offset(ncell, x, y, d)))) { add_side(offset(ncell, .5, 0, d)); } if (!offset(ncell, -1, -1, d).isnull && [[.5, -1], [0, -1.5], [-1.5, 0], [-1, .5]].every(([x, y]) => (b => b.isnull || isSide(b))(offset(ncell, x, y, d)))) { add_side(offset(ncell, .5, 0, d)); } if (!offset(ncell, 1, -1, d).isnull && [[1, -1.5], [1.5, -1], [1.5, 0], [1, .5]].every(([x, y]) => (b => b.isnull || isSide(b))(offset(ncell, x, y, d)))) { add_side(offset(ncell, .5, 0, d)); } if (!offset(ncell, -1, -1, d).isnull && [[-1, -1.5], [-1.5, -1], [-1.5, 0], [-1, .5]].every(([x, y]) => (b => b.isnull || isSide(b))(offset(ncell, x, y, d)))) { add_side(offset(ncell, .5, 0, d)); } if (!offset(ncell, 1, -1, d).isnull && [[-.5, -1], [0, -1.5], [1, -1.5], [1.5, -1]].every(([x, y]) => (b => b.isnull || isSide(b))(offset(ncell, x, y, d)))) { add_side(offset(ncell, .5, 0, d)); } if (!offset(ncell, -1, -1, d).isnull && [[.5, -1], [0, -1.5], [-1, -1.5], [-1.5, -1]].every(([x, y]) => (b => b.isnull || isSide(b))(offset(ncell, x, y, d)))) { add_side(offset(ncell, -.5, 0, d)); } if ((b => b.isnull || isSide(b))(offset(ncell, .5, 0, d))) { if ((b => b.isnull || isSide(b))(offset(ncell, -.5, 0, d))) { add_side(offset(ncell, 1, +.5, d)); add_side(offset(ncell, 1, -.5, d)); add_link(offset(ncell, .5, 0, d)); } if ((b => b.isnull || isSide(b))(offset(ncell, +.5, 0, d))) { add_side(offset(ncell, -1, +.5, d)); add_side(offset(ncell, -1, -.5, d)); add_link(offset(ncell, -.5, 0, d)); } } } // the turning of L if (cell.qnum !== CQNUM.none || cell.qdir === 5) { let ext = [0, 0, 0, 0], lnk = [0, 0, 0, 0]; for (let d = 0; d < 4; d++) { while ((c => !c.isnull && c.qnum === CQNUM.none && c.qdir !== 5 && !isSide(offset(c, -.5, 0, d)) && [d + 1, d - 1].every(n => !isLink(offset(c, .5, 0, n))))(offset(cell, 0, -ext[d] - 1, d))) { ext[d] = ext[d] + 1; } while (isLink(offset(cell, 0, -lnk[d] - .5, d))) { lnk[d] = lnk[d] + 1; } if (isLink(offset(cell, .5, 0, d))) { add_side(offset(cell, -.5, 0, d)); } if (isSide(offset(cell, .5, 0, d))) { add_link(offset(cell, -.5, 0, d)); } } for (let d = 0; d < 4; d++) { if (cell.qnum >= 0) { ext[d] = Math.min(ext[d], cell.qnum - Math.max(lnk[(d + 1) % 4], lnk[(d + 3) % 4], 1) - 1); } while (isLink(offset(cell, 0, -ext[d] - .5, d))) { ext[d] = ext[d] - 1; } if (lnk.reduce((a, b) => a + b) + 1 === cell.qnum || lnk[d] + 2 === cell.qnum) { add_side(offset(cell, 0, -lnk[d] - .5, d)); } } for (let d = 0; d < 4; d++) { if (ext[d] === 0 || cell.qnum >= 0 && ext[d] + Math.max(ext[(d + 1) % 4], ext[(d + 3) % 4]) + 1 < cell.qnum) { add_side(offset(cell, 0, -.5, d)); add_link(offset(cell, 0, .5, d)); add_side(offset(cell, -.5, 1, d)); add_side(offset(cell, +.5, 1, d)); } if (cell.qnum >= 0 && isLink(offset(cell, .5, 0, d))) { for (let i = 1; i < cell.qnum - Math.max(ext[(d + 1) % 4], ext[(d + 3) % 4]); i++) { add_link(offset(cell, 0, -i + .5, d)); add_side(offset(cell, -.5, -i, d)); add_side(offset(cell, +.5, -i, d)); } if (ext[d] + Math.max(ext[(d + 1) % 4], ext[(d + 3) % 4]) + 1 === cell.qnum) { add_side(offset(cell, 0, -ext[d] - .5, d)); } } } } for (let d = 0; d < 4; d++) { // ╺┓ ┏┓ // ╻ ┃ -> ┏┛┃ // ┗━┛ ┗━┛ if (!offset(cell, 1, 1, d).isnull && [[1, -.5], [1.5, 0], [1.5, 1], [1, 1.5], [0, 1.5], [-.5, 1]].every(([x, y]) => (b => isSide(b) || b.isnull)(offset(cell, x, y, d)))) { add_side(offset(cell, 0, .5, d)); add_side(offset(cell, .5, 0, d)); } } }); forEachBorder(border => { // ensure L shape if (!isSide(border) && !isLink(border)) { let clist = []; let dfs = function (c) { if (c.isnull || clist.includes(c)) { return; } clist.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (isLink(nb)) { dfs(nc); } }) }; dfs(border.sidecell[0]); dfs(border.sidecell[1]); let maxx = clist.reduce((a, b) => Math.max(a, b.bx), 0); let maxy = clist.reduce((a, b) => Math.max(a, b.by), 0); let minx = clist.reduce((a, b) => Math.min(a, b.bx), Infinity); let miny = clist.reduce((a, b) => Math.min(a, b.by), Infinity); let l = [minx, maxx].flatMap(x => [[x, miny], [x, maxy]]); let fn = c => { for (let d = 0; d < 4; d++) { let fn = b => isLink(b) || b === border; if (fn(offset(c, .5, 0, d)) && fn(offset(c, 0, -.5, d))) { return true; } } return false; }; if (clist.filter(c => c.qnum !== CQNUM.none || c.qdir === 5 || fn(c)).length > 1 || l.every(([x, y]) => clist.some(c => c.bx !== x && c.by !== y) || adjlist(board.getc(x, y).adjborder).filter(b => isSide(b) || b.isnull).length > 2)) { add_side(border); } } }); } function TentsAssist() { let add_tent = function (c) { if (c === undefined || c.isnull || isDot(c) || c.qnum !== CQNUM.none || c.anum !== CANUM.none) { return; } if (step && flg) { return; } flg = true; c.setAnum(2); c.draw(); }; let isTentAble = (nb, nc) => !nc.isnull && nc.bx > 0 && nc.by > 0 && !isDot(nc) && nc.qnum === CQNUM.none && !isCross(nb); forEachCell(cell => { if (cell.qnum === 1) { // tree adjlist(cell.adjborder, cell.adjacent).forEach(([nb, nc]) => { if (isDot(nc) || nc.qnum === 1) { add_cross(nb); } }); let l = adjlist(cell.adjborder, cell.adjacent).filter(([nb, nc]) => isTentAble(nb, nc)); if (l.length === 1) { add_link(l[0][0]); add_tent(l[0][1]); } if (adjlist(cell.adjborder).some(b => isLink(b))) { adjlist(cell.adjborder).forEach(b => add_cross(b)); } for (let d = 0; d < 4; d++) { if (l.length === 2 && isTentAble(offset(cell, .5, 0, d), offset(cell, 1, 0, d)) && isTentAble(offset(cell, 0, .5, d), offset(cell, 0, 1, d))) { add_dot(offset(cell, 1, 1, d)); } if (l.length === 2 && isTentAble(offset(cell, .5, 0, d), offset(cell, 1, 0, d)) && isTentAble(offset(cell, -.5, 0, d), offset(cell, -1, 0, d))) { add_dot(offset(cell, 0, 1, d)); } if (!isTentAble(offset(cell, -.5, 0, d), offset(cell, -1, 0, d)) && offset(cell, 1, 0, d).qnum !== 1) { [[1, -.5], [1.5, 0], [1, .5],].forEach(([x, y]) => { add_cross(offset(cell, x, y, d)); }); } } } if (cell.anum === 2) { // tent for (let i = -1; i <= 1; i++) { for (let j = -1; j <= 1; j++) { add_dot(offset(cell, i, j)); } } let l = adjlist(cell.adjborder, cell.adjacent).filter(([nb, nc]) => nc.qnum === 1 && !isCross(nb)); if (l.length === 1) { add_link(l[0][0]); } if (adjlist(cell.adjborder).some(b => isLink(b))) { adjlist(cell.adjborder).forEach(b => add_cross(b)); } } if (cell.qnum === CQNUM.none && adjlist(cell.adjborder, cell.adjacent).every(([nb, nc]) => nc.isnull || nc.bx < 0 || nc.by < 0 || nc.qnum === CQNUM.none || isCross(nb))) { add_dot(cell); } }); let fn = function (clist, qnum) { if (qnum === CQNUM.none) { return; } clist = clist.filter(c => !c.isnull && !isDot(c) && c.qnum === CQNUM.none); if (clist.filter(c => c.anum === 2).length === qnum) { clist.forEach(c => add_dot(c)); } for (let i = 0; i < clist.length; i++) { if (offset(clist[i], 1, 0) === clist[i + 1] || offset(clist[i], 0, 1) === clist[i + 1]) { clist[i] = [clist[i], clist[i + 1]]; clist.splice(i + 1, 1); } else { clist[i] = [clist[i]]; } } if (clist.length === qnum) { clist.forEach(l => { if (l.length === 1) { add_tent(l[0]); } if (l.length === 2) { if (offset(l[0], 1, 0) === l[1]) { [[0, -1], [0, 1], [1, -1], [1, 1]].forEach(([x, y]) => add_dot(offset(l[0], x, y))); } if (offset(l[0], 0, 1) === l[1]) { [[-1, 0], [1, 0], [-1, 1], [1, 1]].forEach(([x, y]) => add_dot(offset(l[0], x, y))); } } }); } } for (let i = 0; i < board.cols; i++) { let qnum = board.getobj(i * 2 + 1, -1).qnum; let clist = []; for (let j = 0; j < board.rows; j++) { clist.push(board.getc(i * 2 + 1, j * 2 + 1)); } fn(clist, qnum); } for (let j = 0; j < board.rows; j++) { let qnum = board.getobj(-1, j * 2 + 1).qnum; let clist = []; for (let i = 0; i < board.cols; i++) { clist.push(board.getc(i * 2 + 1, j * 2 + 1)); } fn(clist, qnum); } } function DominionAssist() { BlackDomino(); let m = new Map(); forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_green(cell); } if (isNum(cell)) { if (!m.has(cell.qnum)) { m.set(cell.qnum, 0); } m.set(cell.qnum, m.get(cell.qnum) + 1); } }); Array.from(m).forEach(([qnum, n]) => { if (n === 1) { return; } CellConnected({ isShaded: c => c.qnum === qnum, isUnshaded: c => isBlack(c) || [...adjlist(c.adjacent), c].some(c => isNum(c) && c.qnum !== qnum), add_shaded: add_green, add_unshaded: () => { }, }); }); forEachCell(cell => { if (isBlack(cell) || isGreen(cell)) { return; } let clist = []; let flg = false; let dfs = function (c) { if (flg || clist.includes(c) || !isGreen(c)) { return; } if (isNum(c) && clist.some(cc => isNum(cc) && cc.qnum !== c.qnum)) { flg = true; return; } clist.push(c); fourside(dfs, c.adjacent); } fourside(dfs, cell.adjacent); if (flg) { add_black(cell); } }); } function StostoneAssist() { BlackConnected_InRegion(true); BlackNotAdjacent_OverBorder(); forEachRoom(room => NShadeInClist({ n: room.top.qnum, clist: Array.from(room.clist), AtLeastOne: true })); for (let i = 0; i < board.cols; i++) { let clist = []; for (let j = 0; j < board.rows; j++) { clist.push(board.getc(i * 2 + 1, j * 2 + 1)); } NShadeInClist({ n: board.rows / 2, clist: clist }); } } function KakuroAssist() { let list = [...Array.from(board.cell), ...Array.from(board.excell)]; list = [...list.filter(c => c.qnum > 0).map(c => { let pc = offset(c, 1, 0), clist = []; while (!pc.isnull && pc.ques === CQUES.none) { clist.push(pc); pc = offset(pc, 1, 0); } return [c.qnum, clist]; }), ...list.filter(c => c.qnum2 > 0).map(c => { let pc = offset(c, 0, 1), clist = []; while (!pc.isnull && pc.ques === CQUES.none) { clist.push(pc); pc = offset(pc, 0, 1); } return [c.qnum2, clist]; })]; list = list.filter(([n, l]) => l.length > 0); list.forEach(([n, clist]) => { let cand = clist.map(c => { if (c.anum !== CANUM.none) { return [c.anum]; } if (c.snum.some(n => n !== -1)) { return c.snum.filter(n => n !== -1); } return [1, 2, 3, 4, 5, 6, 7, 8, 9]; }); cand = cand.reduce((a, b) => a.flatMap(i => b.flatMap(j => i.includes(j) ? [] : [[...i, j]])), [[]]); cand = cand.filter(l => new Set(l).size === l.length); cand = cand.filter(l => l.reduce((a, b) => a + b) === n); cand = (m => m[0].map((_, i) => m.map(x => x[i])))(cand); cand = cand.map(l => unique(l)); cand.forEach((a, i) => { let c = clist[i]; if (a.length === 1) { add_sudokunum(c, a[0]); } if (a.length <= 4) { add_candidate(c, a); } }); }); forEachCell(cell => { if (cell.ques !== CQUES.none) { return; } let cand = [...Array(9).keys()].map(n => n + 1); for (let d = 0; d < 4; d++) { let nc = offset(cell, 1, 0, d); while (!nc.isnull && nc.ques === CQUES.none) { cand = cand.filter(n => n !== nc.anum); nc = offset(nc, 1, 0, d); } } add_candidate(cell, cand); }); } function NanameguriAssist() { SingleLoopInCell(); // 31 = \ , 32 = / // form1~5 : █ ◣ ◢ ◥ ◤ let roomdata = board.getDiagonalRegions(); roomdata.forEach(room => { let edges = Array.from(room.edges ?? []); let inner = Array.from(room.inner ?? []); if (edges.filter(b => isLine(b)).length === 2) { edges.filter(b => !isLine(b)).forEach(b => add_cross(b)); } if (edges.filter(b => !isCross(b) && b.inside).length === 2) { edges.filter(b => !isCross(b) && b.inside).forEach(b => add_line(b)); } if (edges.some(c => c.lcnt === 2) && edges.every(c => c.lcnt === 2)) { Array.from(room.inner).forEach(b => add_cross(b)); } let forms = [room.form1, room.form2, room.form3, room.form4, room.form5].map(o => Array.from(o ?? [])); edges.forEach(b => { let [c1, c2] = b.sidecell; if (!b.isvert && [0, 1, 2].some(n => forms[n].includes(c1)) && [0, 3, 4].some(n => forms[n].includes(c2))) { add_cross(b); } if (b.isvert && [0, 2, 3].some(n => forms[n].includes(c1)) && [0, 1, 4].some(n => forms[n].includes(c2))) { add_cross(b); } }); if ([...edges, ...inner].some(b => isLine(b))) { let sb = [...edges, ...inner].find(b => isLine(b)); let sc = sb.sidecell.find(c => forms.flat().includes(c)); let abset = new Set([...edges, ...inner]); let bset = new Set(); let dfs = function (c) { if (!forms.flat().includes(c)) { return; } adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!abset.has(nb) || bset.has(nb) || isCross(nb)) { return; } bset.add(nb); dfs(nc); }); }; dfs(sc); [...edges, ...inner].forEach(b => { if (!bset.has(b)) { add_cross(b); } }); } }); forEachCell(cell => { if (cell.ques === 31 || cell.ques === 32) { let fn = (b1, b2, b3, b4) => { if ([b1, b2].some(b => !b.inside || isCross(b)) || [b3, b4].some(b => isLine(b))) { add_cross(b1); add_cross(b2); add_line(b3); add_line(b4); } }; let fn2 = (c1, c2) => { if (c1.isnull || c2.isnull) { return false; } if (c1.lcnt !== 1 || c2.lcnt !== 1) { return false; } return c1.path === c2.path && board.linegraph.components.length > 1; }; if (cell.ques === 31) { fn(offset(cell, 0, -.5), offset(cell, .5, 0), offset(cell, 0, .5), offset(cell, -.5, 0)); fn(offset(cell, 0, .5), offset(cell, -.5, 0), offset(cell, 0, -.5), offset(cell, .5, 0)); } if (cell.ques === 32) { fn(offset(cell, .5, 0), offset(cell, 0, .5), offset(cell, -.5, 0), offset(cell, 0, -.5)); fn(offset(cell, -.5, 0), offset(cell, 0, -.5), offset(cell, .5, 0), offset(cell, 0, .5)); } for (let d = 0; d < 4; d++) { if (offset(cell, -.5, -.5, d).qsub !== CRQSUB.none) { add_inout(offset(cell, .5, .5, d), offset(cell, -.5, -.5, d).qsub ^ (d % 2 !== 0) ^ (cell.ques !== 31)); } if (fn2(offset(cell, 0, -1, d), offset(cell, 1, 0, d)) && [31, 32, 31, 32][d] === cell.ques) { add_cross(offset(cell, 0, -.5, d)); add_cross(offset(cell, .5, 0, d)); } } } }); } function OneRoomOneDoorAssist() { BlackNotAdjacent(); GreenConnected(); CellConnected({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, isNotPassable: (c, nb, nc) => nb.ques === 1, cantDivideShade: () => true, OnlyOneConnected: false, UnshadeEmpty: false, ForceSearch: true, }); let bset = new Set(); forEachBorder(border => { if (border.ques && border.sidecell.every(c => isGreen(c))) { bset.add(JSON.stringify(border.sidecell.map(c => roomId(c.room)).sort())); } }); forEachBorder(border => { if (border.ques && border.sidecell.some(c => isGreen(c)) && bset.has(JSON.stringify(border.sidecell.map(c => roomId(c.room)).sort()))) { border.sidecell.forEach(c => add_black(c)); } }); forEachCell(cell => { if (!isBlack(cell) && !isGreen(cell)) { for (let d1 = 0; d1 < 4; d1++) { if (!isGreen(offset(cell, 1, 0, d1)) || !offset(cell, .5, 0, d1).ques) { continue; } for (let d2 = d1 + 1; d2 < 4; d2++) { if (!isGreen(offset(cell, 1, 0, d2)) || !offset(cell, .5, 0, d2).ques) { continue; } if (offset(cell, 1, 0, d1).room === offset(cell, 1, 0, d2).room) { add_black(cell); } } } let list = adjlist(cell.adjacent).flatMap(c => adjlist(c.adjborder, c.adjacent).map(([nb, nc]) => [c, nb, nc])); list = list.filter(([c, nb, nc]) => !c.isnull && !nb.isnull && !nc.isnull && nc !== cell && nb.ques && !isBlack(c) && !isGreen(c) && isGreen(nc)); list.forEach(([c, nb, nc]) => { if (list.some(([oc, onb, onc]) => c.room === oc.room && onb !== nb && nc.room === onc.room)) { add_green(cell); return; } }); } }); forEachRoom(room => NShadeInClist({ n: room.top.qnum, clist: Array.from(room.clist) })); } function DosunFuwariAssist() { let add_ball = function (c, t) {// 1 for balloon, 2 for iron ball if (c === undefined || c.isnull || c.qsub === CQSUB.dot || c.qans !== CQANS.none) { return; } if (step && flg) { return; } flg = true; c.setQans(t); c.draw(); }; let nb = new Set(), ni = new Set(); for (let x = 0; x < board.cols; x++) { let list = []; for (let y = 0; y < board.rows; y++) { let cell = board.cell[y * board.cols + x]; if (cell.ques === 1) { list = []; continue; } if (isDot(cell) || cell.qans === 2) { list = null; } if (list !== null && list.some(c => c.room === cell.room)) { list = null; } if (Array.from(cell.room.clist).some(c => c !== cell && c.qans === 1)) { list = null; } if (list === null) { nb.add(cell); } else { list.push(cell); } if ((c => !c.isnull && c.qans === 2)(offset(cell, 0, -1))) { add_ball(cell, 2); } } list = []; for (let y = board.rows - 1; y >= 0; y--) { let cell = board.cell[y * board.cols + x]; if (cell.ques === 1) { list = []; continue; } if (isDot(cell) || cell.qans === 1) { list = null; } if (list !== null && list.some(c => c.room === cell.room)) { list = null; } if (Array.from(cell.room.clist).some(c => c !== cell && c.qans === 2)) { list = null; } if (list === null) { ni.add(cell); } else { list.push(cell); } if ((c => !c.isnull && c.qans === 1)(offset(cell, 0, +1))) { add_ball(cell, 1); } if (nb.has(cell) && ni.has(cell)) { add_dot(cell); } } } forEachRoom(room => { let clist = Array.from(room.clist); if (clist.filter(c => !nb.has(c)).length === 1) { add_ball(clist.filter(c => !nb.has(c))[0], 1); } if (clist.filter(c => !ni.has(c)).length === 1) { add_ball(clist.filter(c => !ni.has(c))[0], 2); } }); } function HeterominoAssist() { const N = 3; SizeRegion_Border({ isLinkable: (c, nb, nc) => !nb.isnull && !isSide(nb) && c.ques !== CQUES.wall && nc.ques !== CQUES.wall, isSideable: (c, nb, nc) => !nb.isnull && !isLink(nb), allSize: N, }); const HETERO = [ [[[0, 0], [1, 0], [2, 0]], "━"], [[[0, 0], [0, 1], [0, 2]], "┃"], [[[0, 0], [1, 0], [1, 1]], "┓"], [[[0, 0], [-1, 1], [0, 1]], "┛"], [[[0, 0], [0, 1], [1, 1]], "┗"], [[[0, 0], [1, 0], [0, 1]], "┏"], ]; forEachCell(cell => { if (cell.ques === CQUES.wall) { return; } let linklist = [], sidelist = [], namelist = []; HETERO.forEach(([cl, t]) => { [...Array(N).keys()].forEach(o => { let clist = cl.map(([x, y]) => offset(cell, x - cl[o][0], y - cl[o][1])); if (clist.some(c => c.isnull || c.ques === CQUES.wall)) { return; } if (clist.some(c => c.anum !== CANUM.none && c.anum !== t)) { return; } if (clist.some(c => adjlist(c.adjborder, c.adjacent).some( ([nb, nc]) => { if (nc.isnull || nc.ques === CQUES.wall) { return false; } if (isLink(nb) && !clist.includes(nc) || isSide(nb) && clist.includes(nc)) { return true; } if (clist.includes(nc)) { return false; } if (nc.anum !== CANUM.none && nc.anum === t) { return true; } let nclist = []; let dfs = function (c) { if (nclist.includes(c)) { return; } nclist.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (isLink(nb)) { dfs(nc); } }); }; dfs(nc); return getShape(clist, false) === getShape(nclist, false); }))) { return; } let linkl = [], sidel = []; clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (clist.includes(nc) && !linkl.includes(nb)) { linkl.push(nb); } if (!clist.includes(nc) && !sidel.includes(nb)) { sidel.push(nb); } })); linklist.push(linkl); sidelist.push(sidel); namelist.push(t); }); }); if (linklist.length > 0) { linklist = linklist.reduce((a, b) => a.filter(i => b.includes(i))); } if (sidelist.length > 0) { sidelist = sidelist.reduce((a, b) => a.filter(i => b.includes(i))); } linklist.forEach(b => add_link(b)); sidelist.forEach(b => add_side(b)); namelist = unique(namelist); if (namelist.length === 1) { add_number(cell, namelist[0]); } }); } function TetrominousAssist() { const N = 4; SizeRegion_Border({ isLinkable: (c, nb, nc) => !nb.isnull && !isSide(nb), isSideable: (c, nb, nc) => !nb.isnull && !isLink(nb), allSize: N, }); forEachBorder(b => { if (b.ques === 1) { add_side(b); } }); const TETRO = [// 0~4 : ILOST [[[0, 0], [1, 0], [2, 0], [3, 0]], 0], [[[0, 0], [1, 0], [2, 0], [2, 1]], 1], [[[0, 0], [0, 1], [1, 0], [1, 1]], 2], [[[0, 0], [1, 0], [1, 1], [2, 1]], 3], [[[0, 0], [1, 0], [1, 1], [2, 0]], 4], ]; forEachCell(cell => { if (cell.qnum >= 0) { add_number(cell, cell.qnum); } for (let d = 0; d < 4; d++) { if (cell.anum >= 0 && offset(cell, 1, 0, d).anum >= 0 && cell.anum === offset(cell, 1, 0, d).anum) { add_link(offset(cell, .5, 0, d)); } if (cell.anum >= 0 && offset(cell, 1, 0, d).anum >= 0 && cell.anum !== offset(cell, 1, 0, d).anum) { add_side(offset(cell, .5, 0, d)); } if (isLink(offset(cell, .5, 0, d)) && cell.anum !== CANUM.none) { add_number(offset(cell, 1, 0, d), cell.anum); } } if (cell.ques === CQUES.wall) { return; } if (cell.anum >= 0 || adjlist(cell.adjborder).filter(b => b.isnull || isSide(b)).length === 3) { let linklist = [], sidelist = [], namelist = []; TETRO.forEach(([cl, t]) => { if (cell.anum >= 0 && cell.anum !== t) { return; } getComb([[0, 1, 2, 3], [1, -1], [...Array(N).keys()]]).forEach(([d, f, o]) => { let clist = cl.map(([x, y]) => offset(cell, x - cl[o][0], (y - cl[o][1]) * f, d)); if (clist.some(c => c.isnull || c.ques === CQUES.wall)) { return; } if (clist.some(c => c.anum >= 0 && c.anum !== t)) { return; } if (clist.some(c => adjlist(c.adjborder, c.adjacent).some( ([nb, nc]) => { if (nc.isnull || nc.ques === CQUES.wall) { return false; } if (isLink(nb) && !clist.includes(nc) || isSide(nb) && clist.includes(nc)) { return true; } if (clist.includes(nc)) { return false; } if (nc.anum >= 0 && nc.anum === t) { return true; } let nclist = []; let dfs = function (c) { if (nclist.includes(c)) { return; } nclist.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (isLink(nb)) { dfs(nc); } }); }; dfs(nc); return getShape(clist) === getShape(nclist); }))) { return; } let linkl = [], sidel = []; clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (clist.includes(nc) && !linkl.includes(nb)) { linkl.push(nb); } if (!clist.includes(nc) && !sidel.includes(nb)) { sidel.push(nb); } })); linklist.push(linkl); sidelist.push(sidel); namelist.push(t); }); }); if (linklist.length > 0) { linklist = linklist.reduce((a, b) => a.filter(i => b.includes(i))); } if (sidelist.length > 0) { sidelist = sidelist.reduce((a, b) => a.filter(i => b.includes(i))); } linklist.forEach(b => add_link(b)); sidelist.forEach(b => add_side(b)); namelist = unique(namelist); if (namelist.length === 1) { add_number(cell, namelist[0]); } } }); } function PentominousAssist() { const N = 5; SizeRegion_Border({ isLinkable: (c, nb, nc) => !nb.isnull && !isSide(nb), isSideable: (c, nb, nc) => !nb.isnull && !isLink(nb), allSize: N, }); forEachBorder(b => { if (b.ques === 1) { add_side(b); } }); const PENTO = [// 0~11 : FILNPTUVWXYZ [[[0, 0], [1, 0], [1, 1], [1, 2], [2, 1]], 0], [[[0, 0], [1, 0], [2, 0], [3, 0], [4, 0]], 1], [[[0, 0], [1, 0], [2, 0], [3, 0], [3, 1]], 2], [[[0, 0], [1, 0], [2, 0], [2, 1], [3, 1]], 3], [[[0, 0], [1, 0], [2, 0], [1, 1], [2, 1]], 4], [[[0, 0], [1, 0], [1, 1], [1, 2], [2, 0]], 5], [[[0, 0], [0, 1], [1, 0], [2, 0], [2, 1]], 6], [[[0, 0], [1, 0], [2, 0], [2, 1], [2, 2]], 7], [[[0, 0], [1, 0], [1, 1], [2, 1], [2, 2]], 8], [[[0, 0], [0, 1], [0, 2], [-1, 1], [1, 1]], 9], [[[0, 0], [1, 0], [1, 1], [2, 0], [3, 0]], 10], [[[0, 0], [1, 0], [1, 1], [1, 2], [2, 2]], 11], ]; forEachCell(cell => { if (cell.qnum >= 0) { add_number(cell, cell.qnum); } for (let d = 0; d < 4; d++) { if (cell.anum >= 0 && offset(cell, 1, 0, d).anum >= 0 && cell.anum === offset(cell, 1, 0, d).anum) { add_link(offset(cell, .5, 0, d)); } if (cell.anum >= 0 && offset(cell, 1, 0, d).anum >= 0 && cell.anum !== offset(cell, 1, 0, d).anum) { add_side(offset(cell, .5, 0, d)); } if (isLink(offset(cell, .5, 0, d)) && cell.anum !== CANUM.none) { add_number(offset(cell, 1, 0, d), cell.anum); } } if (cell.ques === CQUES.wall) { return; } if (cell.anum >= 0 || adjlist(cell.adjborder).filter(b => b.isnull || isSide(b)).length === 3) { let linklist = [], sidelist = [], namelist = []; PENTO.forEach(([cl, t]) => { if (cell.anum >= 0 && cell.anum !== t) { return; } getComb([[0, 1, 2, 3], [1, -1], [...Array(N).keys()]]).forEach(([d, f, o]) => { let clist = cl.map(([x, y]) => offset(cell, x - cl[o][0], (y - cl[o][1]) * f, d)); if (clist.some(c => c.isnull || c.ques === CQUES.wall)) { return; } if (clist.some(c => c.anum >= 0 && c.anum !== t)) { return; } if (clist.some(c => adjlist(c.adjborder, c.adjacent).some( ([nb, nc]) => { if (nc.isnull || nc.ques === CQUES.wall) { return false; } if (isLink(nb) && !clist.includes(nc) || isSide(nb) && clist.includes(nc)) { return true; } if (clist.includes(nc)) { return false; } if (nc.anum >= 0 && nc.anum === t) { return true; } let nclist = []; let dfs = function (c) { if (nclist.includes(c)) { return; } nclist.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (isLink(nb)) { dfs(nc); } }); }; dfs(nc); return getShape(clist) === getShape(nclist); }))) { return; } let linkl = [], sidel = []; clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (clist.includes(nc) && !linkl.includes(nb)) { linkl.push(nb); } if (!clist.includes(nc) && !sidel.includes(nb)) { sidel.push(nb); } })); linklist.push(linkl); sidelist.push(sidel); namelist.push(t); }); }); if (linklist.length > 0) { linklist = linklist.reduce((a, b) => a.filter(i => b.includes(i))); } if (sidelist.length > 0) { sidelist = sidelist.reduce((a, b) => a.filter(i => b.includes(i))); } linklist.forEach(b => add_link(b)); sidelist.forEach(b => add_side(b)); namelist = unique(namelist); if (namelist.length === 1) { add_number(cell, namelist[0]); } } }); } function FourCellsAssist() { SizeRegion_Border({ isLinkable: (c, nb, nc) => !nb.isnull && !isSide(nb), isSideable: (c, nb, nc) => !nb.isnull && !isLink(nb), allSize: 4, }); const TETRO = [// 0~4 : ILOST [[[0, 0], [1, 0], [2, 0], [3, 0]], 0], [[[0, 0], [1, 0], [2, 0], [2, 1]], 1], [[[0, 0], [0, 1], [1, 0], [1, 1]], 2], [[[0, 0], [1, 0], [1, 1], [2, 1]], 3], [[[0, 0], [1, 0], [1, 1], [2, 0]], 4], ]; let cset = new Set(); forEachCell(cell => { if (cell.ques === CQUES.wall) { return; } if (cset.has(cell)) { return; } else { let clist = []; let dfs = function (c) { if (clist.includes(c)) { return; } clist.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (isLink(nb)) { dfs(nc); } }); }; dfs(cell); if (clist.length === 4) { clist.forEach(c => cset.add(c)); return; } } if (cell.qnum >= 0) { let list = adjlist(cell.adjborder, cell.adjacent); if (list.filter(([nb, nc]) => isSide(nb) || nc.isnull || nc.ques === CQUES.wall).length === cell.qnum) { list.filter(([nb, nc]) => !isSide(nb)).forEach(([nb, nc]) => add_link(nb)); } if (list.filter(([nb, nc]) => !isLink(nb)).length === cell.qnum) { list.filter(([nb, nc]) => !isLink(nb)).forEach(([nb, nc]) => add_side(nb)); } } let linklist = [], sidelist = []; TETRO.forEach(([cl, t]) => { getComb([[0, 1, 2, 3], [1, -1], [...Array(4).keys()]]).forEach(([d, f, o]) => { let clist = cl.map(([x, y]) => offset(cell, x - cl[o][0], (y - cl[o][1]) * f, d)); if (clist.some(c => c.isnull || c.ques === CQUES.wall)) { return; } if (clist.some(c => c.qnum >= 0 && adjlist(c.adjacent).filter(nc => !clist.includes(nc)).length !== c.qnum)) { return; } if (clist.some(c => adjlist(c.adjborder, c.adjacent).some( ([nb, nc]) => { if (nc.isnull || nc.ques === CQUES.wall) { return false; } if (isLink(nb) && !clist.includes(nc) || isSide(nb) && clist.includes(nc)) { return true; } return false; }))) { return; } let linkl = [], sidel = []; clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (clist.includes(nc) && !linkl.includes(nb)) { linkl.push(nb); } if (!clist.includes(nc) && !sidel.includes(nb)) { sidel.push(nb); } })); if (clist.flatMap(c => adjlist(c.adjacent).filter(nc => !nc.isnull && nc.ques !== CQUES.wall && nc.qnum >= 0)) .some(c => adjlist(c.adjborder, c.adjacent).filter(([nb, nc]) => isSide(nb) || sidel.includes(nb) || nc.isnull || nc.ques === CQUES.wall).length > c.qnum)) { return; } linklist.push(linkl); sidelist.push(sidel); }); }); if (linklist.length > 0) { linklist = linklist.reduce((a, b) => a.filter(i => b.includes(i))); } if (sidelist.length > 0) { sidelist = sidelist.reduce((a, b) => a.filter(i => b.includes(i))); } linklist.forEach(b => add_link(b)); sidelist.forEach(b => add_side(b)); }); } function FiveCellsAssist() { SizeRegion_Border({ isLinkable: (c, nb, nc) => !nb.isnull && !isSide(nb), isSideable: (c, nb, nc) => !nb.isnull && !isLink(nb), allSize: 5, }); const PENTO = [// 0~11 : FILNPTUVWXYZ [[[0, 0], [1, 0], [1, 1], [1, 2], [2, 1]], 0], [[[0, 0], [1, 0], [2, 0], [3, 0], [4, 0]], 1], [[[0, 0], [1, 0], [2, 0], [3, 0], [3, 1]], 2], [[[0, 0], [1, 0], [2, 0], [2, 1], [3, 1]], 3], [[[0, 0], [1, 0], [2, 0], [1, 1], [2, 1]], 4], [[[0, 0], [1, 0], [1, 1], [1, 2], [2, 0]], 5], [[[0, 0], [0, 1], [1, 0], [2, 0], [2, 1]], 6], [[[0, 0], [1, 0], [2, 0], [2, 1], [2, 2]], 7], [[[0, 0], [1, 0], [1, 1], [2, 1], [2, 2]], 8], [[[0, 0], [0, 1], [0, 2], [-1, 1], [1, 1]], 9], [[[0, 0], [1, 0], [1, 1], [2, 0], [3, 0]], 10], [[[0, 0], [1, 0], [1, 1], [1, 2], [2, 2]], 11], ]; let cset = new Set(); forEachCell(cell => { if (cell.ques === CQUES.wall) { return; } if (cset.has(cell)) { return; } else { let clist = []; let dfs = function (c) { if (clist.includes(c)) { return; } clist.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (isLink(nb)) { dfs(nc); } }); }; dfs(cell); if (clist.length === 5) { clist.forEach(c => cset.add(c)); return; } } if (cell.qnum >= 0) { let list = adjlist(cell.adjborder, cell.adjacent); if (list.filter(([nb, nc]) => isSide(nb) || nc.isnull || nc.ques === CQUES.wall).length === cell.qnum) { list.filter(([nb, nc]) => !isSide(nb)).forEach(([nb, nc]) => add_link(nb)); } if (list.filter(([nb, nc]) => !isLink(nb)).length === cell.qnum) { list.filter(([nb, nc]) => !isLink(nb)).forEach(([nb, nc]) => add_side(nb)); } } let linklist = [], sidelist = []; PENTO.forEach(([cl, t]) => { getComb([[0, 1, 2, 3], [1, -1], [...Array(5).keys()]]).forEach(([d, f, o]) => { let clist = cl.map(([x, y]) => offset(cell, x - cl[o][0], (y - cl[o][1]) * f, d)); if (clist.some(c => c.isnull || c.ques === CQUES.wall)) { return; } if (clist.some(c => c.qnum >= 0 && adjlist(c.adjacent).filter(nc => !clist.includes(nc)).length !== c.qnum)) { return; } if (clist.some(c => adjlist(c.adjborder, c.adjacent).some( ([nb, nc]) => { if (nc.isnull || nc.ques === CQUES.wall) { return false; } if (isLink(nb) && !clist.includes(nc) || isSide(nb) && clist.includes(nc)) { return true; } return false; }))) { return; } let linkl = [], sidel = []; clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (clist.includes(nc) && !linkl.includes(nb)) { linkl.push(nb); } if (!clist.includes(nc) && !sidel.includes(nb)) { sidel.push(nb); } })); if (clist.flatMap(c => adjlist(c.adjacent).filter(nc => !nc.isnull && nc.ques !== CQUES.wall && nc.qnum >= 0)) .some(c => adjlist(c.adjborder, c.adjacent).filter(([nb, nc]) => isSide(nb) || sidel.includes(nb) || nc.isnull || nc.ques === CQUES.wall).length > c.qnum)) { return; } linklist.push(linkl); sidelist.push(sidel); }); }); if (linklist.length > 0) { linklist = linklist.reduce((a, b) => a.filter(i => b.includes(i))); } if (sidelist.length > 0) { sidelist = sidelist.reduce((a, b) => a.filter(i => b.includes(i))); } linklist.forEach(b => add_link(b)); sidelist.forEach(b => add_side(b)); }); } function SchoolTripAssist() { let add_bed = function (c, d) { // NUDLR = 01234 if (c === undefined || c.isnull || c.qnum !== CQNUM.none || ![CQANS.none, 41, 46].includes(c.qans) || 41 + d === c.qans) { return; } if (d === 5 && c.qans === 41) { return; } if (step && flg) { return; } flg = true; c.setQans(41 + d); if (d > 0 && d <= 4) { let nc = [null, offset(c, 0, -1), offset(c, 0, 1), offset(c, -1, 0), offset(c, 1, 0)][d]; let nd = [null, 2, 1, 4, 3][d]; nc.setQans(46 + nd); } c.draw(); }; let add_black = function (c) { if (c.qnum !== CQNUM.none || c.qnums.length > 0) { return; } if (c === undefined || c.isnull || c.lcnt !== 0 || c.qans !== CQANS.none) { return; } if (step && flg) { return; } flg = true; c.setQans(CQANS.black); if (c.qsub === CQSUB.dot) { c.setQsub(CQSUB.none); } c.draw(); }; let add_dot = function (c) { if (c === undefined || c.isnull || c.qnum !== CQNUM.none || [CQANS.black, 41, 42, 43, 44, 45].includes(c.qans) || c.qsub !== CQSUB.none) { return; } if (step && flg) { return; } flg ||= c.lcnt === 0; c.setQsub(CQSUB.dot); c.draw(); }; let isEmpty = c => !c.isnull && c.qans === CQANS.none && c.qnum === CQNUM.none; CellConnected({ isShaded: isBlack, isUnshaded: c => c.isnull || c.qans > 1 || c.qnum !== CQNUM.none, add_shaded: add_black, add_unshaded: c => add_bed(c, 5), }); No2x2Cell({ isShaded: isBlack, add_unshaded: c => add_bed(c, 5), }); forEachCell(cell => { if (cell.qnum !== CQNUM.none && cell.qnum !== CQNUM.quesmark) { let fn = c => [41, 42, 43, 44, 45].includes(c.qans) || (isEmpty(c) || c.qans === 46) && !isDot(c) && [offset(c, 1, 0), offset(c, 0, -1), offset(c, -1, 0)].some(c => isEmpty(c) || c.qans === 46); if (adjlist(cell.adjacent).filter(c => fn(c)).length === cell.qnum) { adjlist(cell.adjacent).filter(c => fn(c)).forEach(c => add_bed(c, 0)); } if (adjlist(cell.adjacent).filter(c => [41, 42, 43, 44, 45].includes(c.qans)).length === cell.qnum) { fourside(add_dot, cell.adjacent); } } if (isEmpty(cell) || cell.qans === 41 || cell.qans === 46) { let tmp = [ [cell, offset(cell, 1, 0), 4], [cell, offset(cell, 0, -1), 1], [cell, offset(cell, -1, 0), 3], [offset(cell, -1, 0), cell, 4], [offset(cell, 0, 1), cell, 1], [offset(cell, 1, 0), cell, 3], ].filter(([hc, bc, d]) => { if (hc.isnull || bc.isnull) { return false; } if (!isEmpty(hc) && hc.qans !== 41 && hc.qans !== 46) { return false; } if (!isEmpty(bc) && bc.qans !== 46) { return false; } if (isDot(hc)) { return false; } return true; }); if (cell.qans === 46 && tmp.length === 2 && tmp[0][0] === tmp[1][1] && tmp[0][1] === tmp[1][0]) { add_bed(tmp[0][0], 5); add_bed(tmp[0][1], 5); } if (tmp.every(([hc, bc, d]) => hc !== cell)) { add_dot(cell); } if (tmp.length === 0) { add_black(cell); } if ((cell.qans === 41 || cell.qans === 46) && tmp.length === 1) { add_bed(tmp[0][0], tmp[0][2]); } } for (let d = 0; d < 4; d++) { if ([45, 42, 44, 43][d] !== cell.qans) { continue; } let tmp = [[-1, 0], [2, 0], [0, 1], [0, -1], [1, 1], [1, -1],].map(([x, y]) => offset(cell, x, y, d)); if (tmp.filter(c => isBlack(c) || isEmpty(c)).length === 1) { tmp.forEach(c => add_black(c)); } } }); } function AntMillAssist() { BlackDomino(); const squ = 1, crs = 2; let isDominoAble = function (border) { if (border.isnull) { return false; } if (isGreen(border.sidecell[0]) || isGreen(border.sidecell[1])) { return false; } if (border.ques === crs) { return false; } let clist, nclist; if (border.isvert) { clist = [offset(border, +1.5, +1), offset(border, +1.5, -1), offset(border, -1.5, +1), offset(border, -1.5, -1)]; nclist = [offset(border, +1.5, 0), offset(border, -1.5, 0), offset(border, +.5, +1), offset(border, +.5, -1), offset(border, -.5, +1), offset(border, -.5, -1)]; } else { clist = [offset(border, +1, +1.5), offset(border, -1, +1.5), offset(border, +1, -1.5), offset(border, -1, -1.5)]; nclist = [offset(border, 0, +1.5), offset(border, 0, -1.5), offset(border, +1, +.5), offset(border, -1, +.5), offset(border, +1, -.5), offset(border, -1, -.5)]; } clist = clist.filter(c => !c.isnull && !isGreen(c)); return clist.length >= 2 && nclist.every(c => !isBlack(c)); } forEachBorder(border => { if (border.ques === squ) { if (isBlack(border.sidecell[0])) { add_black(border.sidecell[1]); } if (isBlack(border.sidecell[1])) { add_black(border.sidecell[0]); } if (isGreen(border.sidecell[0])) { add_green(border.sidecell[1]); } if (isGreen(border.sidecell[1])) { add_green(border.sidecell[0]); } if (!isDominoAble(border)) { add_green(border.sidecell[0]); add_green(border.sidecell[1]); } } if (border.ques === crs) { if (isBlack(border.sidecell[0])) { add_green(border.sidecell[1]); } if (isBlack(border.sidecell[1])) { add_green(border.sidecell[0]); } if (isGreen(border.sidecell[0])) { add_black(border.sidecell[1]); } if (isGreen(border.sidecell[1])) { add_black(border.sidecell[0]); } } if (isBlack(border.sidecell[0]) && isBlack(border.sidecell[1])) { let clist; if (border.isvert) { clist = [offset(border, +1.5, +1), offset(border, +1.5, -1), offset(border, -1.5, +1), offset(border, -1.5, -1)]; } else { clist = [offset(border, +1, +1.5), offset(border, -1, +1.5), offset(border, +1, -1.5), offset(border, -1, -1.5)]; } clist = clist.filter(c => !c.isnull && !isGreen(c)); if (clist.length === 2) { clist.forEach(c => add_black(c)); } if (clist.filter(c => isBlack(c)).length === 2) { clist.forEach(c => add_green(c)); } } }); forEachCell(cell => { for (let d = 0; d < 4; d++) { if (isBlack(cell) && !isDominoAble(offset(cell, .5, 0, d))) { add_green(offset(cell, 1, 0, d)); } } if (adjlist(cell.adjborder).filter(b => b.ques === squ).length > 1) { add_green(cell); } if (adjlist(cell.adjborder).every(b => !isDominoAble(b))) { add_green(cell); } }); } function DoubleChocoAssist() { CluePerRegion({ isShaded: c => c.ques === 0, isOthers: c => c.ques === 6, isUnshaded: () => false, isNotPassable: (c, nb, nc) => isSide(nb), cantDivideShade: (s, o) => s !== o, BridgeType: "link", }); SizeRegion_Border({ isLinkable: (c, nb, nc) => c.ques === nc.ques, isSideable: (c, nb, nc) => c.ques === nc.ques, hasAnum: true, }); let cset = new Set(); forEachCell(cell => { let clist = []; if (cset.has(cell)) { return; } let dfs = function (c) { if (clist.includes(c)) { return; } clist.push(c); cset.add(c); fourside((nb, nc) => { if (!isSide(nb) && !nc.isnull && c.ques === nc.ques) { dfs(nc); } }, c.adjborder, c.adjacent); }; dfs(cell); let ncset = new Set(); clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!isSide(nb) && !nc.isnull && c.ques !== nc.ques) { ncset.add(nc); } })); if (ncset.size === 1) { clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!isSide(nb) && clist.includes(nc)) { add_link(nb); } })); } }); cset = new Set(); forEachCell(cell => { let clist = []; if (cset.has(cell)) { return; } let dfs = function (c) { if (clist.includes(c)) { return; } clist.push(c); cset.add(c); fourside((nb, nc) => { if (isLink(nb) && c.ques === nc.ques) { dfs(nc); } }, c.adjborder, c.adjacent); }; dfs(cell); if (clist.every(c => adjlist(c.adjborder, c.adjacent).every(([nb, nc]) => isLink(nb) || isSide(nb) || nc.isnull || c.ques !== nc.ques))) { let oclist = [], olclist = []; clist.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!isSide(nb) && !nc.isnull && c.ques !== nc.ques) { oclist.push(nc); if (isLink(nb)) { olclist.push(nc); } } })); if (olclist.length !== 0) { oclist = [olclist[0]]; } let cand = clist.flatMap((cc) => { let r = [clist.map(c => [c.bx - cc.bx, c.by - cc.by])]; r = [...r, ...r.map(l => l.map(([x, y]) => [-y, x]))]; r = [...r, ...r.map(l => l.map(([x, y]) => [-x, y]))]; r = [...r, ...r.map(l => l.map(([x, y]) => [x, -y]))]; r = r.flatMap(l => oclist.map(oc => l.map(([x, y]) => board.getc(oc.bx + x, oc.by + y)))); r = r.filter(l => l.every(c => !c.isnull && c.ques !== cell.ques && (c.qnum === CQNUM.none || c.qnum === clist.length) && !adjlist(c.adjborder, c.adjacent).some(([nb, nc]) => isLink(nb) && !l.includes(nc) && !clist.includes(nc) || isSide(nb) && l.includes(nc)))); if (olclist.length !== 0) { r = r.filter(l => olclist.every(c => l.includes(c))); } return r; }); if (cand.length > 0) { let icset = new Set([...cand.reduce((a, b) => a.filter(c => b.includes(c))), ...clist]); let ucset = new Set(cand.flat()); Array.from(icset).forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (icset.has(nc)) { add_link(nb); } if (!icset.has(nc) && !ucset.has(nc)) { add_side(nb); } })); } } }); } function MyopiaAssist() { SingleLoopInBorder(); forEachCell(cell => { if (cell.qnum !== CQNUM.none) { if (cell.qnum === 15 && board.linegraph.components.length > 0 && adjlist(cell.adjborder).every(b => !b.line)) { adjlist(cell.adjborder).forEach(b => add_cross(b)); } let arr = [0b0001, 0b0100, 0b0010, 0b1000].map(n => (cell.qnum & n) !== 0); let dist = []; for (let di = 0.5; di < Math.min(board.rows, board.cols); di++) { let temp = [0, 1, 2, 3].map(n => offset(cell, 0, -di, n)); if (temp.some((b, i) => b.isnull && arr[i])) { break; } if (temp.some((b, i) => b.line && !arr[i])) { break; } if (temp.every((b, i) => !arr[i] || !b.isnull && !isCross(b))) { dist.push(di); } if (temp.some((b, i) => b.line && arr[i])) { break; } } let l = dist.reduce((a, b) => Math.min(a, b), Math.min(board.rows, board.cols)); for (let di = 0.5; di <= l; di++) { let temp = [0, 1, 2, 3].map(n => offset(cell, 0, -di, n)); temp.forEach((b, i) => { if (di < l || !arr[i]) { add_cross(b); } if (dist.length === 1 && di === l && arr[i]) { add_line(b); } }); } } }); } function PutteriaAssist() { forEachCell(cell => { if (cell.qnum === CQNUM.quesmark) { add_dot(cell); } if (cell.qnum !== CQNUM.quesmark && cell.qnum !== CQNUM.none) { add_number(cell, cell.qnum); } if (cell.anum !== CANUM.none) { fourside((nc) => { add_dot(nc); }, cell.adjacent); } // 2 2 2·2 // 2 2 -> 2·2 for (let d = 0; d < 4; d++) { let list = [cell, offset(cell, 0, 1, d), offset(cell, 2, 0, d), offset(cell, 2, 1, d)]; if (list.every(c => !c.isnull && c.room.clist.length === 2)) { add_dot(offset(cell, 1, 0, d)); add_dot(offset(cell, 1, 1, d)); } } }); forEachRoom(room => { let clist = Array.from(room.clist).filter(c => c.qsub !== CQSUB.dot && c.qnum !== CQNUM.quesmark); if (clist.some(c => c.anum !== CANUM.none)) { clist.forEach(c => add_dot(c)); } if (clist.length === 1) { add_number(clist[0], room.clist.length); return; } if (clist.length === 2) { for (let d = 0; d < 4; d++) { if (offset(clist[0], 1, 1, d) === clist[1]) { add_dot(offset(clist[0], 0, 1, d)); add_dot(offset(clist[0], 1, 0, d)); } if (offset(clist[0], 2, 0, d) === clist[1]) { add_dot(offset(clist[0], 1, 0, d)); } } } if (clist.length > 0 && clist.every(c => c.bx === clist[0].bx)) { for (let j = 0; j < board.rows; j++) { let ac = board.getc(clist[0].bx, j * 2 + 1); if (ac.room !== room && ac.room.clist.length === room.clist.length) { add_dot(ac); } } } if (clist.length > 0 && clist.every(c => c.by === clist[0].by)) { for (let i = 0; i < board.cols; i++) { let ac = board.getc(i * 2 + 1, clist[0].by); if (ac.room !== room && ac.room.clist.length === room.clist.length) { add_dot(ac); } } } }); let fn = function (clist) { let set = new Set(); clist.forEach(cell => { if (cell.anum !== CANUM.none) { set.add(cell.anum); } }); clist.forEach(cell => { if (set.has(cell.room.clist.length)) { add_dot(cell); } }); } for (let i = 0; i < board.cols; i++) { let clist = []; for (let j = 0; j < board.rows; j++) { clist.push(board.getc(i * 2 + 1, j * 2 + 1)); } fn(clist); } for (let j = 0; j < board.rows; j++) { let clist = []; for (let i = 0; i < board.cols; i++) { clist.push(board.getc(i * 2 + 1, j * 2 + 1)); } fn(clist); } } function FillominoAssist() { SizeRegion_Border({ isGroupable: (sc, c) => !adjlist(c.adjborder, c.adjacent).some(([nb, nc]) => sc.anum === nc.anum && isSide(nb)), hasAnum: true, }); forEachCell(cell => { if (isNum(cell)) { add_number(cell, cell.qnum); } if (cell.anum !== CANUM.none) { fourside((nb, nc) => { if (nb.isnull || nc.isnull) { return; } if (nc.anum === cell.anum) { add_link(nb); } if (nc.anum !== CANUM.none && nc.anum !== cell.anum) { add_side(nb); } if (nb.qsub === BQSUB.link) { add_number(nc, cell.anum); } }, cell.adjborder, cell.adjacent); let clist = []; let dfs = function (c) { if (clist.includes(c)) { return; } clist.push(c); fourside((nb, nc) => { if (!isSide(nb) && !nc.isnull && (nc.anum === cell.anum || nc.anum === CANUM.none)) { dfs(nc); } }, c.adjborder, c.adjacent); }; dfs(cell); if (clist.length === cell.anum) { clist.forEach(c => add_number(c, cell.anum)); } } if (cell.anum === CANUM.none) { let clist = [], blist = []; let dfs = function (c) { if (clist.includes(c)) { return; } clist.push(c); fourside((nb, nc) => { if (isLink(nb)) { dfs(nc); } if (!nc.isnull && !isSide(nb) && !isLink(nb)) { blist.push(nb); } }, c.adjborder, c.adjacent); }; dfs(cell); if (clist.every(c => adjlist(c.adjborder, c.adjacent).every(([nb, nc]) => nc.isnull || isSide(nb) || isLink(nb)))) { clist.forEach(c => add_number(c, clist.length)); } if (blist.length === 1 && clist.some(c => adjlist(c.adjacent).some(c => !c.isnull && !clist.includes(c) && c.anum === clist.length))) { add_link(blist[0]); } } if (cell.anum === CANUM.none) { let temp = adjlist(cell.adjborder, cell.adjacent); temp = temp.filter(([nb, nc]) => !nb.isnull && !nc.isnull && nc.anum !== CANUM.none); temp = temp.map(([nb, nc]) => nc.anum); temp.forEach(n => { if (temp.filter(nn => nn === n).length <= 1) { return; } let clist = []; let dfs = function (c) { if (clist.includes(c)) { return; } clist.push(c); fourside((nb, nc) => { if (!nc.isnull && nc.anum === n) { dfs(nc); } }, c.adjborder, c.adjacent); }; dfs(cell); if (clist.length > n) { fourside((nb, nc) => { if (!nc.isnull && nc.anum === n) { add_side(nb); } }, cell.adjborder, cell.adjacent); } }) } if (adjlist(cell.adjborder).every(b => isntLink(b))) { add_number(cell, 1); } for (let d = 0; d < 4; d++) { let bl = [[-.5, 0], [0, -.5], [0, .5], [1, -.5], [1, .5], [1.5, 0]].map(([x, y]) => offset(cell, x, y, d)); if (bl.every(b => isntLink(b))) { add_link(offset(cell, .5, 0, d)); } } }); } function NawabariAssist() { RectRegion_Border({ isSizeAble: (w, h, sc, c) => { for (let i = 0; i < w; i++) { for (let j = 0; j < h; j++) { if (offset(c, i, j).qnum === CQNUM.none) { continue; } if (sc !== null && sc !== offset(c, i, j)) { return false; } sc = offset(c, i, j); } } if (sc === null) { return false; } let sn = 0; sn += (c.bx === sc.bx ? 1 : 0); sn += (c.by === sc.by ? 1 : 0); sn += (c.bx + (w - 1) * 2 === sc.bx ? 1 : 0); sn += (c.by + (h - 1) * 2 === sc.by ? 1 : 0); return sc.qnum === CQNUM.quesmark || sc.qnum === sn; } }); } function BarnsAssist() { SingleLoopInCell({ isPass: c => true, isPathable: b => !b.isnull && !b.ques && !isCross(b), }); } function SkyscrapersAssist() { let size = Math.max(board.rows, board.cols); LatinSquare(); let arr = () => Array(size).fill().map((_, i) => i + 1); let fn = function (clist, n1, n2) { let cands = clist.map(c => c.anum !== -1 ? [c.anum] : c.snum.some(n => n !== -1) ? c.snum.filter(n => n !== -1) : arr()); if (n1 > 1) { for (let i = 1; i < n1; i++) { cands[i - 1] = cands[i - 1].filter(n => n <= size - n1 + i); } } if (n1 === 1) { cands[0] = [size]; } if (n2 > 1) { for (let i = 1; i < n2; i++) { cands[size - i] = cands[size - i].filter(n => n <= size - n2 + i); } } if (n2 === 1) { cands[size - 1] = [size]; } if (size <= 9) { let cc = Array(size).fill().map((_, i) => new Set()); let dfs = function (i, a) { if (i === size) { if (n1 !== -1 && n1 !== a.reduce(([h, c], n) => h < n ? [n, c + 1] : [h, c], [0, 0])[1]) { return; } if (n2 !== -1 && n2 !== [...a].reverse().reduce(([h, c], n) => h < n ? [n, c + 1] : [h, c], [0, 0])[1]) { return; } a.forEach((n, i) => cc[i].add(n)); return; } cands[i].forEach(n => { if (a.includes(n)) { return; } dfs(i + 1, [...a, n]); }); } dfs(0, []); cands = cc.map(set => Array.from(set)); } clist.forEach((c, i) => add_candidate(c, cands[i])); } for (let i = 1; i < size * 2; i += 2) { let row = [], col = []; for (let j = 1; j < size * 2; j += 2) { row.push(board.getc(j, i)); col.push(board.getc(i, j)); } fn(row, board.getex(-1, i).qnum, board.getex(size * 2 + 1, i).qnum); fn(col, board.getex(i, -1).qnum, board.getex(i, size * 2 + 1).qnum); } } function MinarismAssist() { let size = Math.max(board.rows, board.cols); LatinSquare({ ext: (cell, cand) => { let arr = () => Array(size).fill().map((_, i) => i + 1); for (let d = 0; d < 4; d++) { if (offset(cell, .5, 0, d).isnull) { continue; } let ncell = offset(cell, 1, 0, d); let nlist = ncell.snum.filter(n => n !== -1); if (nlist.length === 0) { nlist = arr(); } if (ncell.anum !== -1) { nlist = [ncell.anum]; } if (offset(cell, .5, 0, d).qnum !== BQNUM.none) { let dt = offset(cell, .5, 0, d).qnum; cand = cand.filter(n => nlist.includes(n - dt) || nlist.includes(n + dt)); } if ([4, 1, 3, 2][d] === offset(cell, .5, 0, d).qdir) { cand = cand.filter(n => nlist.some(m => n > m)); } if ([3, 2, 4, 1][d] === offset(cell, .5, 0, d).qdir) { cand = cand.filter(n => nlist.some(m => n < m)); } } return cand; } }); } function KropkiAssist() { let size = Math.max(board.rows, board.cols); LatinSquare({ ext: (cell, cand) => { let arr = () => Array(size).fill().map((_, i) => i + 1); for (let d = 0; d < 4; d++) { if (offset(cell, .5, 0, d).isnull) { continue; } let ncell = offset(cell, 1, 0, d); let nlist = ncell.snum.filter(n => n !== -1); if (nlist.length === 0) { nlist = arr(); } if (ncell.anum !== -1) { nlist = [ncell.anum]; } if (offset(cell, .5, 0, d).qnum === BQNUM.wcir) { cand = cand.filter(n => nlist.includes(n - 1) || nlist.includes(n + 1)); } if (offset(cell, .5, 0, d).qnum === BQNUM.bcir) { cand = cand.filter(n => nlist.includes(n / 2) || nlist.includes(n * 2)); } if (offset(cell, .5, 0, d).qnum === BQNUM.none) { cand = cand.filter(n => nlist.some(m => ![n, n - 1, n + 1, n / 2, n * 2].includes(m))); } } return cand; } }); } function MidloopAssist() { SingleLoopInCell({ isPass: c => c.qnum === CQNUM.bcir, }); let isDot = c => !c.isnull && c.qnum === CQNUM.bcir; forEachCell(cell => { for (let d = 0; d < 4; d++) { if (isDot(cell) && offset(cell, .5, .5, d).qsub !== CRQSUB.none) { add_inout(offset(cell, -.5, -.5, d), offset(cell, .5, .5, d).qsub ^ 1); } } }); let isSegmentAble = function (c, l, d) { if (c.isnull || offset(c, l, 0, d).isnull) { return false; } if (isLine(offset(c, -.5, 0, d)) || isLine(offset(c, l + .5, 0, d))) { return false; } if (isntLine(offset(c, 0, -.5, d)) && isntLine(offset(c, 0, +.5, d))) { return false; } if (isntLine(offset(c, l, -.5, d)) && isntLine(offset(c, l, +.5, d))) { return false; } for (let i = 0; i < l; i++) { if (isntLine(offset(c, i + .5, 0, d))) { return false; } if (i > 0 && (isLine(offset(c, i, -.5, d)) || isLine(offset(c, i, +.5, d)))) { return false; } } for (let i = 0; i <= l; i += .5) { if (i * 2 !== l && isDot(offset(c, i, 0, d))) { return false; } } return true; } for (let j = board.minby + 1; j <= board.maxby - 1; j++) { for (let i = board.minbx + 1; i <= board.maxbx - 1; i++) { let obj = board.getobj(i, j); if (!isDot(obj)) { continue; } if (i % 2 === 0 && j % 2 === 0) { continue; } if (i % 2 === 0 || j % 2 === 0) { add_line(obj); let d = (i % 2 === 0 ? 0 : 1); let cand = Array(Math.max(board.rows, board.cols)).fill(0).map((_, i) => i); cand = cand.filter(i => isSegmentAble(offset(obj, -i - .5, 0, d), i * 2 + 1, d)); if (cand.length === 0) { continue; } let t = cand.reduce((a, b) => Math.min(a, b)); for (let i = 1; i <= t; i++) { add_line(offset(obj, -i, 0, d)); add_line(offset(obj, +i, 0, d)); } if (cand.length === 1) { add_cross(offset(obj, -t - 1, 0, d)); add_cross(offset(obj, +t + 1, 0, d)); } } if (i % 2 === 1 && j % 2 === 1) { let candh = Array(board.cols).fill(0).map((_, i) => i + 1); let candv = Array(board.rows).fill(0).map((_, i) => i + 1); candh = candh.filter(i => isSegmentAble(offset(obj, -i, 0, 0), i * 2, 0)); candv = candv.filter(i => isSegmentAble(offset(obj, -i, 0, 1), i * 2, 1)); let d = -1, cand = []; if (candh.length === 0) { add_line(offset(obj, 0, -.5)); add_line(offset(obj, 0, +.5)); d = 1; cand = candv; } if (candv.length === 0) { add_line(offset(obj, -.5, 0)); add_line(offset(obj, +.5, 0)); d = 0; cand = candh; } if (d !== -1) { if (cand.length === 0) { continue; } let t = cand.reduce((a, b) => Math.min(a, b)); for (let i = 0; i < t; i++) { add_line(offset(obj, -i - .5, 0, d)); add_line(offset(obj, +i + .5, 0, d)); } if (cand.length === 1) { add_cross(offset(obj, -t - .5, 0, d)); add_cross(offset(obj, +t + .5, 0, d)); } } } } } } function PipelinkAssist() { SingleLoopInCell({ isPass: c => true, hasCross: true }); forEachCell(cell => { // 11:╋; 12:┃; 13:━; 14:┗; 15:┛; 16:┓; 17:┏ const tmp = [ // ques, u, l, d, r [11, 1, 1, 1, 1], [12, 1, 0, 1, 0], [13, 0, 1, 0, 1], [14, 1, 0, 0, 1], [15, 1, 1, 0, 0], [16, 0, 1, 1, 0], [17, 0, 0, 1, 1], ]; if (cell.ques === CQUES.none) { return; } adjlist(cell.adjborder).forEach((b, i) => tmp[cell.ques - 11][i + 1] === 1 ? add_line(b) : add_cross(b)); }); forEachCell(cell => { let list = adjlist(cell.adjborder); let linecnt = list.filter(b => !b.isnull && b.line).length; let crosscnt = list.filter(b => b.isnull || isCross(b)).length; if (linecnt === 3 || crosscnt === 2) { fourside(add_line, cell.adjborder); } if (linecnt === 2 && crosscnt === 1) { fourside(add_cross, cell.adjborder); } for (let d = 0; d < 4; d++) { if (!isLine(offset(cell, .5, 0, d)) || isLine(offset(cell, -.5, 0, d))) { continue; } let dd = d; let pcell = offset(cell, 1, 0, dd); while (pcell !== cell) { if (!offset(pcell, 0.5, 0, dd).line) { if ((b => !b.isnull && !isCross(b))(offset(pcell, .5, 0, dd))) { break; } if (isLine(offset(pcell, .5, 0, dd - 1)) === isLine(offset(pcell, .5, 0, dd + 1))) { break; } dd = (offset(pcell, .5, 0, dd - 1).line ? dd + 3 : dd + 1) % 4; } pcell = offset(pcell, 1, 0, dd); } if (pcell === cell && board.linegraph.components.length > 1) { fourside(add_line, cell.adjborder); } if ((adjlist(cell.adjborder).some(b => isntLine(b)) || dd === d) && board.linegraph.components.length > 1 && (b => !b.isnull && !isCross(b))(offset(pcell, .5, 0, dd))) { pcell = offset(pcell, 1, 0, dd); while (pcell !== cell && isLine(offset(pcell, .5, 0, dd))) { pcell = offset(pcell, 1, 0, dd); } if (pcell === cell) { add_cross(offset(pcell, -.5, 0, dd)); } } } }); } function RingringAssist() { let isWall = c => c.ques === 1; forEachCell(cell => { if (isWall(cell)) { fourside(add_cross, cell.adjborder); } if (cell.lcnt === 3) { fourside(add_line, cell.adjborder); } if (cell.lcnt === 2 && adjlist(cell.adjborder).some(b => isntLine(b))) { fourside(add_cross, cell.adjborder); } let templist = adjlist(cell.adjborder, cell.adjacent); templist = templist.filter(([nb, nc]) => !(nc.isnull || isWall(nc) || isCross(nb))); if (templist.length === 2) { templist.forEach(([nb, nc]) => add_line(nb)); } }); // make the right turning forEachCell(cell => { for (let d = 0; d < 4; d++) { if (!offset(cell, 0, -.5, d).line || !offset(cell, -.5, 0, d).line) { continue; } if (!isntLine(offset(cell, 0, .5, d))) { continue; } let pcell = cell; while (offset(pcell, 0, -.5, d).line) { pcell = offset(pcell, 0, -1, d); if (isntLine(offset(pcell, 0, -.5, d))) { add_line(offset(pcell, -.5, 0, d)); add_cross(offset(pcell, .5, 0, d)); break; } if (offset(pcell, .5, 0, d).line) { fourside(add_line, pcell.adjborder); } } } for (let d = 0; d < 4; d++) { if (!offset(cell, 0, -.5, d).line || !offset(cell, .5, 0, d).line) { continue; } if (!isntLine(offset(cell, 0, .5, d))) { continue; } let pcell = cell; while (offset(pcell, 0, -.5, d).line) { pcell = offset(pcell, 0, -1, d); if (isntLine(offset(pcell, 0, -.5, d))) { add_line(offset(pcell, .5, 0, d)); add_cross(offset(pcell, -.5, 0, d)); break; } if (offset(pcell, -.5, 0, d).line) { fourside(add_line, pcell.adjborder); } } } }); forEachCell(cell => { for (let d = 0; d < 4; d++) { if (!offset(cell, 0, .5, d).line || !offset(cell, .5, 0, d).line) { continue; } if (!isntLine(offset(cell, 0, -.5, d)) && !isntLine(offset(cell, -.5, 0, d))) { continue; } let h = 1, w = 1; while (offset(cell, 0, h + .5, d).line) { h++; } while (offset(cell, w + .5, 0, d).line) { w++; } let fg = false; do { fg = false; for (let i = 1; i <= h; i++) { while (isWall(offset(cell, w, i, d)) || offset(cell, w, i - .5, d).qsub === BQSUB.cross) { add_line(offset(cell, w + .5, 0, d)); w++; fg = true; } } for (let i = 1; i <= w; i++) { while (isWall(offset(cell, i, h, d)) || offset(cell, i - .5, h, d).qsub === BQSUB.cross) { add_line(offset(cell, 0, h + .5, d)); h++; fg = true; } } } while (fg); if (isntLine(offset(cell, w + .5, 0, d))) { for (let i = 1; i <= h; i++) { add_line(offset(cell, w, i - .5, d)); } if (isntLine(offset(cell, 0, h + .5, d)) || isntLine(offset(cell, w, h + .5, d))) { add_cross(offset(cell, 0, h + .5, d)); add_cross(offset(cell, w, h + .5, d)); } } if (isntLine(offset(cell, 0, h + .5, d))) { for (let i = 1; i <= w; i++) { add_line(offset(cell, i - .5, h, d)); } if (isntLine(offset(cell, w + .5, 0, d)) || isntLine(offset(cell, w + .5, h, d))) { add_cross(offset(cell, w + .5, 0, d)); add_cross(offset(cell, w + .5, h, d)); } } } }); } function NonogramAssist() { // deduce each clue let f = function (nl, cl) { let len = cl.length; let ll = new Array(nl.length); ll[ll.length - 1] = len - nl[ll.length - 1]; for (let i = ll.length - 2; i >= 0; i--) { ll[i] = ll[i + 1] - nl[i] - 1; } let dcnt = new Array(len); let bcnt = new Array(len); for (let i = 0; i < len; i++) { dcnt[i] = (i > 0 ? dcnt[i - 1] : 0) + (cl[i].qsub === CQSUB.dot ? 1 : 0); bcnt[i] = (i > 0 ? bcnt[i - 1] : 0) + (cl[i].qans ? 1 : 0); } let res = []; const MAXSIT = 1000; let gen = function (n = 0, l = []) { if (res.length > MAXSIT) { return; } if (n === nl.length) { if (l.length > len) { l = l.slice(0, len); } if (l.length > 0 && bcnt[len - 1] > bcnt[l.length - 1]) { return; } if (l.length < len) { l = [...l, ...Array(len - l.length).fill(0)]; } res.push(l); return; } for (let i = l.length; i <= ll[n]; i++) { if (i + nl[n] < len && cl[i + nl[n]].qans) { continue; } if ((i > 0 ? bcnt[i - 1] : 0) !== (l.length > 0 ? bcnt[l.length - 1] : 0)) { continue; } if (dcnt[i + nl[n] - 1] > (i > 0 ? dcnt[i - 1] : 0)) { continue; } gen(n + 1, [...l, ...Array(i - l.length).fill(0), ...Array(nl[n]).fill(1), 0]); if (res.length > MAXSIT) { return; } } }; gen(); if (res.length === 0 || res.length > MAXSIT) { return; } for (let i = 0; i < len; i++) { if (res.every(l => l[i] === res[0][i])) { if (res[0][i] === 0) { add_dot(cl[i]); } if (res[0][i] === 1) { add_black(cl[i]); } } } }; for (let i = 0; i < board.rows; i++) { let nl = [], cl = []; for (let j = board.minbx + 1; j <= -1; j += 2) { nl.push(board.getex(j, i * 2 + 1).qnum); } for (let j = 0; j < board.cols; j++) { cl.push(board.getc(j * 2 + 1, i * 2 + 1)); } nl = nl.filter(n => n >= 0); f(nl, cl); } for (let i = 0; i < board.cols; i++) { let nl = [], cl = []; for (let j = board.minby + 1; j <= -1; j += 2) { nl.push(board.getex(i * 2 + 1, j).qnum); } for (let j = 0; j < board.rows; j++) { cl.push(board.getc(i * 2 + 1, j * 2 + 1)); } nl = nl.filter(n => n >= 0); f(nl, cl); } } function SudokuAssist() { LatinSquare({ hasBox: true }); } function CirclesAndSquaresAssist() { forEachCell(c => { if (c.qnum === CQNUM.wcir) add_green(c); }); forEachCell(c => { if (c.qnum === CQNUM.bcir) add_black(c); }); No2x2Black(); BlackConnected(); RectRegion_Cell({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, isSizeAble: (w, h, sc, c) => w === h, }) } function TatamibariAssist() { RectRegion_Border({ isSizeAble: (w, h, sc, c) => { if (sc !== null && !(sc.qnum === 1 && w === h || sc.qnum === 2 && w < h || sc.qnum === 3 && w > h)) { return false; } for (let i = 0; i < w; i++) { for (let j = 0; j < h; j++) { if (sc !== null && offset(c, i, j) !== sc && offset(c, i, j).qnum !== CQNUM.none) { return false; } if (sc === null && offset(c, i, j).qnum !== CQNUM.none) { sc = offset(c, i, j); } } } if (sc === null) { return false; } return sc.qnum === CQNUM.quesmark || sc.qnum === 1 && w === h || sc.qnum === 2 && w < h || sc.qnum === 3 && w > h; } }); NoCrossingBorder(); } function PencilsAssist() { let add_tip = function (c, dir) { // 1=↑, 2=↓, 3=←, 4=→ if (c === undefined || c.isnull || isGreen(c) || c.anum !== CANUM.none) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.yellow); c.setAnum(dir); c.draw(); } let add_cross = function (b) { if (b === undefined || b.isnull || b.line || b.qsub !== BQSUB.none || b.qans || b.ques) { return; } if (step && flg) { return; } b.setQsub(BQSUB.cross); b.draw(); flg ||= isCross(b); }; for (let i = 0; i < board.border.length; i++) { let border = board.border[i]; if (border.qans && isCross(border)) { border.setQsub(BQSUB.none); } } // record cells with line that belongs to a tip let cllist = []; forEachCell(cell => { if (cell.anum !== CANUM.none) { let dfs = function (c) { if (c.isnull || cllist.includes(c)) { return; } cllist.push(c); fourside((nb, nc) => { if (!nb.isnull && nb.line) { dfs(nc); } }, c.adjborder, c.adjacent); } dfs(cell); } }); let isTipAble = function (c, dir) { if (c.isnull || cllist.includes(c) && c.anum !== dir || isGreen(c)) { return false; } return true; } forEachCell(cell => { if (cell.qdir !== 0) { add_tip(cell, cell.qdir); } if (cell.qnum !== CQNUM.none) { add_green(cell); } if (cell.qnum === 1) { fourside(add_side, cell.adjborder); } if (cell.qnum > 1) { // extend clue for (let d = 0; d < 2; d++) { let ll = 0, rr = 0; while (isCross(offset(cell, ll - .5, 0, d)) && offset(cell, ll - 1, 0, d).qsub !== CQSUB.yellow) { ll--; } while (isCross(offset(cell, rr + .5, 0, d)) && offset(cell, rr + 1, 0, d).qsub !== CQSUB.yellow) { rr++; } let lc = ll, rc = rr; while ((c => !c.isnull && c.qsub !== CQSUB.yellow && (c.qnum < 0 || c.qnum === cell.qnum))(offset(cell, lc - 1, 0, d)) && !offset(cell, lc - .5, 0, d).qans && lc > 1 - cell.qnum + rr) { lc--; } while ((c => !c.isnull && c.qsub !== CQSUB.yellow && (c.qnum < 0 || c.qnum === cell.qnum))(offset(cell, rc + 1, 0, d)) && !offset(cell, rc + .5, 0, d).qans && rc < cell.qnum - 1 + ll) { rc++; } if ((b => b.isnull || b.qans)(offset(cell, 0, .5, d)) && (b => b.isnull || b.qans)(offset(cell, 0, -.5, d))) { for (let j = rc - cell.qnum + 1; j <= lc + cell.qnum - 1; j++) { add_green(offset(cell, j, 0, d)); add_side(offset(cell, j, -.5, d)); add_side(offset(cell, j, +.5, d)); if (j < lc + cell.qnum - 1) { add_cross(offset(cell, j + .5, 0, d)); } } if (rc - lc + 1 === cell.qnum) { add_side(offset(cell, rc + .5, 0, d)); add_side(offset(cell, lc - .5, 0, d)); if (!isTipAble(offset(cell, rc + 1, 0, d), d === 0 ? 4 : 1)) { add_tip(offset(cell, lc - 1, 0, d), d === 0 ? 3 : 2) } if (!isTipAble(offset(cell, lc - 1, 0, d), d === 0 ? 3 : 2)) { add_tip(offset(cell, rc + 1, 0, d), d === 0 ? 4 : 1) } } } if (rc - lc + 1 === cell.qnum && !isTipAble(offset(cell, rc + 1, 0, d), d === 0 ? 4 : 1) && !isTipAble(offset(cell, lc - 1, 0, d), d === 0 ? 3 : 2)) { add_side(offset(cell, +.5, 0, d)); add_side(offset(cell, -.5, 0, d)); } if (rc - lc + 1 < cell.qnum) { add_side(offset(cell, +.5, 0, d)); add_side(offset(cell, -.5, 0, d)); } } } // add tip if (isGreen(cell) && (b => b.isnull || b.qans)(offset(cell, -.5, 0)) && (b => b.isnull || b.qans)(offset(cell, 0, -.5))) { let dc = 0, rc = 0; while ((b => !b.isnull && isCross(b))(offset(cell, 0, dc + .5))) { dc++; } while ((b => !b.isnull && isCross(b))(offset(cell, rc + .5, 0))) { rc++; } if ((b => b.isnull || b.qans)(offset(cell, 0, dc + .5)) && (b => b.isnull || b.qans)(offset(cell, rc + .5, 0))) { let list = []; if (dc === 0) { list.push([offset(cell, -1, 0), 3]); list.push([offset(cell, rc + 1, 0), 4]); } if (rc === 0) { list.push([offset(cell, 0, -1), 1]); list.push([offset(cell, 0, dc + 1), 2]); } list = list.filter(p => isTipAble(p[0], p[1])); if (list.length === 1) { add_tip(list[0][0], list[0][1]); } } } // extend to match line and pencil if (cell.anum !== CANUM.none) { add_yellow(cell); let d = qdirRemap(cell.anum); add_green(offset(cell, -1, 0, d)); add_side(offset(cell, -.5, 0, d)); add_side(offset(cell, -1, -.5, d)); add_side(offset(cell, -1, +.5, d)); let pc = cell, lc = cell, llc = cell; while (offset(pc, -.5, 0, d).qsub === BQSUB.cross || pc === cell) { let list = adjlist(lc.adjborder, lc.adjacent); list = list.filter(([nb, nc]) => { if (nc === llc) { return false; } if (nc.isnull || isGreen(nc) || nc.anum !== CANUM.none) { return false; } if (cllist.includes(nc) && !nb.line) { return false; } if (nb.isnull || isCross(nb) || nb.qans) { return false; } return true; }); if (list.some(p => p[0].line)) { list = list.filter(p => p[0].line); } if (list.length !== 1) { break; } add_line(list[0][0]); fourside((nb, nc) => { if (isYellow(nc)) { add_cross(nb); } }, lc.adjborder, lc.adjacent); llc = lc; lc = list[0][1]; pc = offset(pc, -1, 0, d); add_yellow(lc); } while (lc.lcnt === 2 || lc === cell && lc.lcnt === 1) { add_cross(offset(pc, -.5, 0, d)); pc = offset(pc, -1, 0, d); add_green(pc); add_side(offset(pc, 0, -.5, d)); add_side(offset(pc, 0, +.5, d)); [llc, lc] = [lc, adjlist(lc.adjborder, lc.adjacent).find(([nb, nc]) => !nb.isnull && nb.line && nc !== llc)[1]]; } if (adjlist(lc.adjborder, lc.adjacent).every(([nb, nc]) => nc === llc || nb.isnull || isCross(nb) || nb.qans)) { add_side(offset(pc, -.5, 0, d)); } if (pc !== cell && (b => b.isnull || b.qans)(offset(pc, -.5, 0, d))) { fourside((nb, nc) => { if (isYellow(nc)) { add_cross(nb); } }, lc.adjborder, lc.adjacent); } } if (cell.lcnt === 0 && adjlist(cell.adjborder, cell.adjacent).every(([nb, nc]) => nb.isnull || nb.qans || isCross(nb) || isGreen(nc) || nc.lcnt === 2 || nc.lcnt === 1 && nc.anum !== CANUM.none)) { add_green(cell); } if (adjlist(cell.adjborder, cell.adjacent).every(([nb, nc]) => { if (nc.isnull || nb.qans && isGreen(nc)) { return true; } if (cllist.includes(nc) && nc.anum === CANUM.none) { return true; } if (nc.anum !== CANUM.none && offset(nc, -1, 0, qdirRemap(nc.anum)) !== cell) { return true; } return false; })) { add_yellow(cell); } if (cell.lcnt > 0) { add_yellow(cell); } if (isYellow(cell) && adjlist(cell.adjborder).filter(b => !b.isnull && !b.qans && !isCross(b)).length === 1) { add_line(adjlist(cell.adjborder).find(b => !b.isnull && !b.qans && !isCross(b))); } if ([[offset(cell, -1, 0), 3], [offset(cell, 1, 0), 4], [offset(cell, 0, -1), 1], [offset(cell, 0, 1), 2]].every(([c, d]) => !isTipAble(c, d)) && adjlist(cell.adjborder).filter(b => !b.isnull && !b.qans).length === 1) { add_cross(adjlist(cell.adjborder).find(b => !b.isnull && !b.qans)); } for (let d = 0; d < 4; d++) { let nc = offset(cell, 1, 0, d); if (nc.isnull) { continue; } // cross between cells in cllist if (cllist.includes(cell) && cllist.includes(nc)) { add_cross(offset(cell, .5, 0, d)); } // add side between inner and outer if (cell.qsub !== CQSUB.none && nc.qsub !== CQSUB.none && cell.qsub !== nc.qsub) { add_side(offset(cell, .5, 0, d)); } // extend pencil by cross mark if (isGreen(cell) && (b => isCross(b) && !b.qans)(offset(cell, .5, 0, d))) { add_side(offset(cell, 0, -.5, d)); add_side(offset(cell, 0, +.5, d)); add_side(offset(cell, 1, -.5, d)); add_side(offset(cell, 1, +.5, d)); add_green(nc); } // different clue if (cell.qnum > 0 && nc.qnum > 0 && cell.qnum !== nc.qnum) { add_side(offset(cell, .5, 0, d)); } } }); } function MoonOrSunAssist() { let add_Xcell = function (c) { if (c === undefined || c.isnull || c.qnum === CQNUM.none || c.lcnt > 0) { return; } if (step && flg) { return; } flg = true; c.setQsub(CQSUB.cross); c.draw(); }; let roomType = c => { if (c.anum !== CANUM.none) return c.anum; if (c.room.count.moon.exists === 0) return 1; if (c.room.count.sun.exists === 0) return 2; if (c.room.count.sun.passed > 0) return 1; if (c.room.count.moon.passed > 0) return 2; if (c.room.count.sun.exists === c.room.clist.length) return 1; if (c.room.count.moon.exists === c.room.clist.length) return 2; for (let i = 0; i < c.room.clist.length; i++) { let c2 = c.room.clist[i]; if (c2.anum !== CANUM.none) return c2.anum; if (c2.qnum === CQNUM.moon && c2.qsub === CQSUB.cross) { return 1; } if (c2.qnum === CQNUM.sun && c2.qsub === CQSUB.cross) { return 2; } if (c2.qnum === CQNUM.moon && c2.lcnt > 0) { return 2; } if (c2.qnum === CQNUM.sun && c2.lcnt > 0) { return 1; } } return 0; } RoomPassOnce(); CellConnected_InRegion({ isShaded: c => c.lcnt > 0 || c.qnum !== CQNUM.none && c.qnum === roomType(c), isUnshaded: c => adjlist(c.adjborder).every(b => isntLine(b)), add_shaded: () => { }, add_unshaded: () => { }, ByLine: true, }); SingleLoopInCell({ isPass: c => c.qnum !== CQNUM.none && roomType(c) === c.qnum }); CellConnected_InRegion({ isShaded: c => isNum(c) && roomType(c) === c.qnum, isUnshaded: c => isNum(c) && roomType(c) !== 0 && roomType(c) !== c.qnum, add_shaded: () => { }, add_unshaded: () => { }, ByLine: true, }); forEachCell(cell => { if (cell.anum === CANUM.none && roomType(cell) !== 0) { let t = roomType(cell); Array.from(cell.room.clist).forEach(c => add_number(c, t)); } fourside((nb, nc) => { if (nc.isnull || cell.room === nc.room) { return; } if (nb.line && roomType(nc) !== 0) { add_number(cell, 3 - roomType(nc)); } if (roomType(nc) !== 0 && cell.qnum === roomType(nc)) { add_cross(nb); } if (roomType(cell) !== 0 && roomType(nc) !== 0 && roomType(cell) === roomType(nc)) { add_cross(nb); } }, cell.adjborder, cell.adjacent); if (cell.qnum === CQNUM.none) { return; } fourside((nb, nc) => { if (nc.isnull || nc.qnum === CQNUM.none) { return; } if ((cell.qnum === nc.qnum) ^ (cell.room === nc.room)) { add_cross(nb); } }, cell.adjborder, cell.adjacent); if (adjlist(cell.adjborder).every(b => b.isnull || isCross(b))) { add_Xcell(cell); } if (roomType(cell) !== 0 && cell.qnum !== roomType(cell)) { add_Xcell(cell); } if (cell.qsub === CQSUB.cross) { fourside(add_cross, cell.adjborder); } }); forEachRoom(room => { let t = roomType(room.clist[0]); if (t === 0) { return; } let nrl = new Set(); Array.from(room.clist).forEach(c => fourside( (nb, nc) => { if (!nc.isnull && nc.room !== room && !isCross(nb)) { nrl.add(nc.room); } }, c.adjborder, c.adjacent )); if (nrl.size <= 2) { Array.from(nrl).forEach(nroom => add_number(nroom.clist[0], 3 - t)); } }); } function ShikakuAssist() { let s = Array.from(new Array(board.rows), () => new Array(board.cols).fill([])); forEachCell(c => { let x = (c.bx - 1) / 2, y = (c.by - 1) / 2; let s1 = x > 0 ? s[y][x - 1] : []; let s2 = y > 0 ? s[y - 1][x] : []; s[y][x] = [...s1, ...s2.filter(c => !s1.includes(c))]; if (c.qnum !== CQNUM.none) { s[y][x].push(c); } }); RectRegion_Border({ isSizeAble: (w, h, sc, c) => { if (sc !== null && w * h !== sc.qnum) { return false; } let x = (c.bx - 1) / 2 + w - 1, y = (c.by - 1) / 2 + h - 1; let f = (a, b) => a < 0 || b < 0 ? [] : s[a][b]; if (f(y, x).length - f(y, x - w).length - f(y - h, x).length + f(y - h, x - w).length !== 1) { return false; } sc = f(y, x).find(c => !f(y, x - w).includes(c) && !f(y - h, x).includes(c)); return sc.qnum === CQNUM.quesmark || w * h === sc.qnum; } }); } function SquareJamAssist() { RectRegion_Border({ isSizeAble: (w, h, sc, c) => { if (w !== h) { return false; } if (sc !== null && w !== sc.qnum) { return false; } for (let i = 0; i < w; i++) { for (let j = 0; j < h; j++) { if (offset(c, i, j).qnum > 0 && sc === null) { sc = offset(c, i, j); } if (offset(c, i, j).qnum > 0 && offset(c, i, j).qnum !== sc.qnum) { return false; } } } return sc === null || w === sc.qnum; } }); NoCrossingBorder(); } function TasquareAssist() { RectRegion_Cell({ isShaded: isBlack, isUnshaded: c => isGreen(c) || c.qnum !== CQNUM.none, add_shaded: add_black, add_unshaded: add_dot, isSizeAble: (w, h, sc, c) => w === h, }); CellConnected({ isShaded: c => c.qsub === CQSUB.dot || c.qnum !== CQNUM.none, isUnshaded: isBlack, 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 isSquareAble = function (c1, c2) { if (c1.isnull || c2.isnull) { return false; } let [x1, x2] = [c1.bx, c2.bx].sort((x, y) => x - y); let [y1, y2] = [c1.by, c2.by].sort((x, y) => x - y); for (let i = x1; i <= x2; i += 2) { for (let j = y1; j <= y2; j += 2) { if (isntBlack(board.getc(i, j))) { return false; } } } for (let i = x1; i <= x2; i += 2) { if (isBlack(board.getc(i, y1 - 2))) { return false; } if (isBlack(board.getc(i, y2 + 2))) { return false; } } for (let j = y1; j <= y2; j += 2) { if (isBlack(board.getc(x1 - 2, j))) { return false; } if (isBlack(board.getc(x2 + 2, j))) { return false; } } return true; } let isntBlack = c => c.isnull || c.qsub === CQSUB.dot || c.qnum !== CQNUM.none; forEachCell(cell => { if (cell.qnum === CQNUM.none) { return; } add_dot(cell); let templist = adjlist(cell.adjacent); if (templist.filter(c => !isntBlack(c)).length === 1) { templist.forEach(c => add_black(c, true)); } if (cell.qnum === CQNUM.quesmark) { return; } let bclist = Array.from(board.cell), dclist = Array.from(board.cell); let getRectCell = (c1, c2) => { if (c1.isnull || c2.isnull) { return []; } let list = []; let [x1, x2] = [c1.bx, c2.bx].sort((x, y) => x - y); let [y1, y2] = [c1.by, c2.by].sort((x, y) => x - y); for (let i = x1; i <= x2; i += 2) { for (let j = y1; j <= y2; j += 2) { list.push(board.getc(i, j)); } } return list; }; // bruteforce for each clue for (let n1 = 0; n1 <= Math.sqrt(cell.qnum); n1++) { if (isBlack(offset(cell, n1 + 1, 0, 0))) { continue; } if (n1 > 0 && isntBlack(offset(cell, n1, 0, 0))) { break; } for (let s1 = 0; s1 <= Math.max(0, n1 - 1); s1++) { if (n1 > 0 && !isSquareAble(offset(cell, 1, s1 - n1 + 1, 0), offset(cell, n1, s1, 0))) { continue; } for (let n2 = 0; n2 <= Math.sqrt(cell.qnum - n1 ** 2); n2++) { if (isBlack(offset(cell, n2 + 1, 0, 1))) { continue; } if (n2 > 0 && isntBlack(offset(cell, n2, 0, 1))) { break; } if (n2 > 0 && s1 - n1 + 1 < 0) { break; } for (let s2 = 0; s2 <= Math.max(0, n2 - 1); s2++) { if (n2 > 0 && !isSquareAble(offset(cell, 1, s2 - n2 + 1, 1), offset(cell, n2, s2, 1))) { continue; } if (s2 > 0 && n1 > 0) { break; } for (let n3 = 0; n3 <= Math.sqrt(cell.qnum - n1 ** 2 - n2 ** 2); n3++) { if (isBlack(offset(cell, n3 + 1, 0, 2))) { continue; } if (n3 > 0 && isntBlack(offset(cell, n3, 0, 2))) { break; } if (n3 > 0 && s2 - n2 + 1 < 0) { break; } for (let s3 = 0; s3 <= Math.max(0, n3 - 1); s3++) { if (n3 > 0 && !isSquareAble(offset(cell, 1, s3 - n3 + 1, 2), offset(cell, n3, s3, 2))) { continue; } if (s3 > 0 && n2 > 0) { break; } let n4 = Math.sqrt(cell.qnum - n1 ** 2 - n2 ** 2 - n3 ** 2); if (n4 % 1 !== 0) { continue; } if (isBlack(offset(cell, n4 + 1, 0, 3))) { continue; } if (n4 > 0 && isntBlack(offset(cell, n4, 0, 3))) { continue; } if (s1 > 0 && n4 > 0) { continue; } if (n4 > 0 && s3 - n3 + 1 < 0) { continue; } for (let s4 = 0; s4 <= Math.max(0, n4 - 1); s4++) { if (s4 > 0 && n3 > 0) { break; } if (n4 > 0 && !isSquareAble(offset(cell, 1, s4 - n4 + 1, 3), offset(cell, n4, s4, 3))) { continue; } if (n1 > 0 && s4 - n4 + 1 < 0) { continue; } let cset = new Set([...(n1 > 0 ? getRectCell(offset(cell, 1, s1 - n1 + 1, 0), offset(cell, n1, s1, 0)) : []), ...(n2 > 0 ? getRectCell(offset(cell, 1, s2 - n2 + 1, 1), offset(cell, n2, s2, 1)) : []), ...(n3 > 0 ? getRectCell(offset(cell, 1, s3 - n3 + 1, 2), offset(cell, n3, s3, 2)) : []), ...(n4 > 0 ? getRectCell(offset(cell, 1, s4 - n4 + 1, 3), offset(cell, n4, s4, 3)) : [])]); bclist = bclist.filter(c => cset.has(c)); dclist = dclist.filter(c => !cset.has(c) && adjlist(c.adjacent).some(cc => cset.has(cc) || cc === cell)); } } } } } } } bclist.forEach(c => add_black(c)); dclist.forEach(c => add_dot(c)); let blist = []; let dfs = function (c) { if (blist.includes(c) || c.isnull || !isBlack(c)) { 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.wall; CluePerRegion({ isShaded: c => { for (let i = -.5; i <= .5; i += .5) { for (let j = -.5; j <= .5; j += .5) { if (isDot(offset(c, i, j))) { return true; } } } return false; }, isOthers: isEmpty, isUnshaded: c => !isEmpty(c), isNotPassable: (c, nb, nc) => !isEmpty(c) || isSide(nb) || !isEmpty(nc), BridgeType: "link", }); 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(); // get dot obj let bfs_id = function (clist, n) { while (clist.length > 0) { let c = clist.pop(); let x = dotmap.get(n).bx; let y = dotmap.get(n).by; if (!isEmpty(c) || id.has(c)) { continue; } 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 id_choice = new Map(); let dfs_idc = function (c, n) { let x = dotmap.get(n).bx; let y = dotmap.get(n).by; let oc = board.getc(2 * x - c.bx, 2 * y - c.by); if (!isEmpty(c) || id.has(c) && id.get(c) !== n) { return; } if (!isEmpty(oc) || id.has(oc) && id.get(oc) !== n) { return; } if (id_choice.has(c) && id_choice.get(c).includes(n)) { return; } if (id_choice.has(oc) && id_choice.get(oc).includes(n)) { return; } if (!id_choice.has(c)) { id_choice.set(c, []); } if (!id_choice.has(oc)) { id_choice.set(oc, []); } id_choice.set(c, id_choice.get(c).concat([n])); id_choice.set(oc, id_choice.get(oc).concat([n])); fourside((nb, nc) => { if (nb.qans) { return; } dfs_idc(nc, n); }, c.adjborder, c.adjacent); }; // 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_id(clist, n); } } } // assign cells to possible dots n = 0; 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++; 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)); } dfs_idc(clist[0], n); } } } // check not assigned cells forEachCell(cell => { 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)) { return; } if (id_choice.has(cell) && id_choice.get(cell).length === 1) { bfs_id([cell], id_choice.get(cell)[0]); } }); document.querySelector('#btncolor').click(); } function NorinuriAssist() { forEachCell(c => { if (c.qnum !== CQNUM.none) add_green(c); }) BlackDomino(); SizeRegion_Cell({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: c => add_black(c, true), }); } function NorinoriAssist() { BlackDomino(); forEachRoom(room => { let list = Array.from(room.clist).filter(c => !isDot(c)); // finish region if (list.filter(c => isBlack(c)).length === 2) { list.forEach(c => add_dot(c)); } if (list.length === 2) { list.forEach(c => add_black(c)); } if (list.filter(c => isBlack(c)).length === 1) { list.forEach(c => { if (isBlack(c) || c.qsub === CQSUB.dot) { return; } if (!adjlist(c.adjacent).some(nc => !nc.isnull && (c.room !== nc.room && !isDot(nc) || c.room === nc.room && isBlack(nc)))) { add_dot(c); } }); } if (list.filter(c => !isDot(c)).length === 3) { list = list.filter(c => !isDot(c)); let fn = function (c1, c2, c3) { if (!adjlist(c1.adjacent).includes(c2)) { return; } if (adjlist(c1.adjacent).some(c => isBlack(c) && c !== c2) || adjlist(c2.adjacent).some(c => isBlack(c) && c !== c1)) { add_black(c3); } if (adjlist(c1.adjacent).includes(c3)) { for (let d = 0; d < 4; d++) { let l = [offset(c1, 0, 1, d), offset(c1, 1, 0, d)]; if (l.includes(c2) && l.includes(c3)) { add_dot(offset(c1, 1, 1, d)); } } } } fn(list[0], list[1], list[2]); fn(list[1], list[2], list[0]); fn(list[2], list[0], list[1]); } }); forEachCell(cell => { let c2x2 = [[0, 0], [0, 1], [1, 0], [1, 1]].map(([x, y]) => offset(cell, x, y)); if (c2x2.some(c => c.isnull)) { return; } let clist = Array.from(new Set(c2x2.flatMap(c => Array.from(c.room.clist)))).filter(c => !isDot(c) || c2x2.includes(c)); if (clist.length === 6 && clist.some(c => c.room !== clist[0].room)) { clist.forEach(c => c2x2.includes(c) ? null : add_black(c)); } }); } function AllorNothingAssist() { let add_color = function (c, color) { if (c.isnull || c.qsub !== CQSUB.none) { return; } if (step && flg) { return; } flg = true; 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, }); CellConnected_InRegion({ isShaded: c => c.qsub === CQSUB.yellow, isUnshaded: c => c.qsub === CQSUB.gray, add_shaded: add_yellow, add_unshaded: add_gray, ByLine: true, }); RoomPassOnce({ MaybeNotPassed: true }); forEachRoom(room => { let list = Array.from(room.clist); 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(); forEachCell(cell => { let fn = function (c1, c2, c3) { if (c1.isnull || c2.isnull || c3.isnull) { return; } if (isBlack(c1) && isBlack(c2) && isBlack(c3)) { add_green(cell); } if (isGreen(c1) && isGreen(c2) && isGreen(c3)) { 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),); } }); forEachRoom(room => { let qnum = room.top.qnum; if (qnum === CQNUM.none || qnum === CQNUM.quesmark) { return; } let list = Array.from(room.clist); if (list.filter(c => isBlack(c)).length === qnum) { list.forEach(c => add_green(c)); } if (qnum - list.filter(c => isBlack(c)).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: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, }); forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_green(cell); } }); } function HitoriAssist() { GreenConnected(); BlackNotAdjacent(); let uniq = new Map(); forEachCell(cell => uniq.set(cell, true)); let fn = function (a) { let vis = new Map(); for (let cell of a) { if (cell.qnum === CQNUM.none) continue; if (isGreen(cell)) 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 || isBlack(cell)) 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 || isBlack(cell)) 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); } forEachCell(cell => { if (uniq.get(cell)) add_green(cell); }); } function CanalViewAssist() { CellConnected({ isShaded: isBlack, isUnshaded: c => c.qsub === CQSUB.dot || c.qnum !== CQNUM.none, add_shaded: add_black, add_unshaded: add_dot, }); SightNumber({ isShaded: isBlack, isUnshaded: c => c.qsub === CQSUB.dot || c.qnum !== CQNUM.none, add_shaded: add_black, add_unshaded: add_dot, }); No2x2Cell({ isShaded: isBlack, add_unshaded: add_dot, }); } function CaveAssist() { GreenConnected(); CellConnected({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, OutsideAsShaded: true, }); SightNumber({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, }); NoCheckerCell({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, }); forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_green(cell); } }); } function TapaLikeLoopAssist() { SingleLoopInCell(); let check = function (c, s) { let qnums = c.qnums; if (s === "111111110000") { return qnums.length === 1 && (qnums[0] === 8 || qnums[0] === CQNUM.quesmark); } if (s === "000000000000") { return qnums.length === 1 && qnums[0] === 0; } for (let i = 0; i < 4; i++) { if (s[i + 8] === '1' && (s[(i * 2 + 1) % 8] === '1' || s[(i * 2 + 2) % 8] === '1')) { return false; } } let l = [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]].map(([x, y]) => offset(c, x, y)); if ([[[0, -1.5]], [[1, -1.5], [1.5, -1]], [[1.5, 0]], [[1.5, 1], [1, 1.5]], [[0, 1.5]], [[-1, 1.5], [-1.5, 1]], [[-1.5, 0]], [[-1.5, -1], [-1, -1.5]]].some((e, i) => { if (l[i].qsub !== CQSUB.circle) { return false; } if (i % 2 === 1 && [s[i], s[(i + 1) % 8], s[(i + 1) / 2 + 7]].every(n => n === '0')) { return true; } e = e.map(([x, y]) => isntLine(offset(c, x, y)) ? '0' : '1'); return [...e, s[i], s[(i + 1) % 8]].filter(n => n === '1').length < 2; })) { return false; } l = [[[0, -1.5]], [[1, -1.5], [1.5, -1]], [[1.5, 0]], [[1.5, 1], [1, 1.5]], [[0, 1.5]], [[-1, 1.5], [-1.5, 1]], [[-1.5, 0]], [[-1.5, -1], [-1, -1.5]]].map(e => { e = e.map(([x, y]) => offset(c, x, y)); e = e.map(b => isLine(b) ? 1 : isntLine(b) ? 0 : -1); if (e.includes(-1)) return -1; if (e.length === 1) return e[0]; return e[0] ^ e[1]; }); if (l.some((t, i) => { if (t === 0 && s[i] !== s[(i + 1) % 8]) { return true; } if (t === 1 && s[i] === s[(i + 1) % 8]) { return true; } return false; })) { return false; } while (s[0] !== '0') { s = s.slice(1, 8) + s[0] + s.slice(8); } s = s.slice(0, 8).split('0').filter(s => s.length > 0).map(s => s.length + 1).concat( s.slice(8).split('').flatMap(c => c === '1' ? 1 : [])); if (s.length !== qnums.length) { return false; } for (let i = 0; i < qnums.length; i++) { if (qnums[i] === CQNUM.quesmark) { continue; } if (!s.includes(qnums[i])) { return false; } s.splice(s.indexOf(qnums[i]), 1); } return s.length === qnums.filter(n => n === CQNUM.quesmark).length; }; forEachCell(cell => { if (cell.lcnt > 0) { add_Ocell(cell); } if (adjlist(cell.adjborder).every(b => isntLine(b))) { add_Xcell(cell); } if (cell.qsub === CQSUB.cross) { fourside(add_cross, cell.adjborder); } if (cell.qnums.length === 0) { return; } fourside(add_cross, cell.adjborder); let list = [[-.5, -1], [.5, -1], [1, -.5], [1, .5], [.5, 1], [-.5, 1], [-1, .5], [-1, -.5]].map(([x, y]) => offset(cell, x, y)); let dlist = [0, 3, 2, 1].map(d => [[1, -.5], [.5, -1], [1, -1.5], [1.5, - 1]].map(([x, y]) => offset(cell, x, y, d))); if (cell.qnums[0] === 0) { list.forEach(b => add_cross(b)); } let mask = parseInt([...list.map(b => !b.isnull && !isCross(b) && !isLine(b) ? "1" : "0"), ...dlist.map(([b1, b2, b3, b4]) => !isLine(b1) && !isLine(b2) && !isntLine(b3) && !isntLine(b4) ? "1" : "0") ].join(""), 2); let blk = parseInt(([...list.map(b => isLine(b) ? "1" : "0"), ...dlist.map(([b1, b2, b3, b4]) => isLine(b3) && isLine(b4) ? "1" : "0")]).join(""), 2); let setb = 0b111111111111, setd = 0b000000000000, n = 0; let setp = 0b11111111, setq = 0b11111111, seto = 0b11111111; for (let j = mask; j >= 0; j--) { j &= mask; if (check(cell, (j | blk).toString(2).padStart(12, '0'))) { n++; setb &= (j | blk); setd |= (j | blk); let t = (j | blk) >> 4; t = t ^ (t << 1 | t >> 7); setp &= t; t = ~t; if ((j | blk) & 0b000000001000) { t &= 0b10111111; } if ((j | blk) & 0b000000000100) { t &= 0b11101111; } if ((j | blk) & 0b000000000010) { t &= 0b11111011; } if ((j | blk) & 0b000000000001) { t &= 0b11111110; } setq &= t; t = (j | blk) >> 4; t = t | (t << 1 | t >> 7); if ((j | blk) & 0b000000001000) { t |= 0b01000000; } if ((j | blk) & 0b000000000100) { t |= 0b00010000; } if ((j | blk) & 0b000000000010) { t |= 0b00000100; } if ((j | blk) & 0b000000000001) { t |= 0b00000001; } seto &= t; } } if (n === 0) { add_black(cell); return; } setb = setb.toString(2).padStart(12, '0'); setd = setd.toString(2).padStart(12, '0'); setp = setp.toString(2).padStart(8, '0'); setq = setq.toString(2).padStart(8, '0'); seto = seto.toString(2).padStart(8, '0'); for (let j = 0; j < 8; j++) { if (setb[j] === '1') { add_line(list[j]); } if (setd[j] === '0') { add_cross(list[j]); } } for (let j = 8; j < 12; j++) { if (setb[j] === '1') { let [b1, b2, b3, b4] = dlist[j - 8]; add_cross(b1); add_cross(b2); add_line(b3); add_line(b4); } if (setd[j] === '0') { let [b1, b2, b3, b4] = dlist[j - 8]; if (isLine(b3)) { add_cross(b4); } if (isLine(b4)) { add_cross(b3); } if (isntLine(b1) && isntLine(b2)) { add_cross(b3); add_cross(b4); } } } [[[0, -1.5]], [[1, -1.5], [1.5, -1]], [[1.5, 0]], [[1.5, 1], [1, 1.5]], [[0, 1.5]], [[-1, 1.5], [-1.5, 1]], [[-1.5, 0]], [[-1.5, -1], [-1, -1.5]]].map((e, i) => { e = e.map(([x, y]) => offset(cell, x, y)); if (setp[i] === '1' && e.filter(b => !isntLine(b)).length === 1) { e.forEach(b => add_line(b)); } if (setq[i] === '1') { e.forEach(b => add_cross(b)); } }); [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]].forEach(([x, y], i) => { let c = offset(cell, x, y); if (seto[i] === '1') { add_Ocell(c); } }) }); } function TapaAssist() { No2x2Cell({ isShaded: isBlack, add_unshaded: add_dot, }); CellConnected({ isShaded: isBlack, isUnshaded: c => c.qsub === CQSUB.dot || c.qnums.length > 0, add_shaded: add_black, add_unshaded: add_dot, isNotPassable: (c, nb, nc) => { let d = [0, 1, 2, 3].find(d => offset(c, 1, 0, d) === nc); if (d === undefined) { return true; } if ([[0, -1], [0, 1], [1, -1], [1, 1]].some(([x, y]) => { let cc = offset(c, x, y, d); if (cc.qnums.length === 4) { return true; } if (cc.qnums.length > 0 && cc.qnums.every(n => n === 1)) { return true; } return false; })) { return true; } return false; } }); let check = function (qnums, s) { if (s === "11111111") { return qnums.length === 1 && (qnums[0] === 8 || qnums[0] === CQNUM.quesmark); } if (s === "00000000") { return qnums.length === 1 && qnums[0] === 0; } 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 !== qnums.length) { return false; } for (let i = 0; i < qnums.length; i++) { if (qnums[i] === CQNUM.quesmark) { continue; } if (!s.includes(qnums[i])) { return false; } 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; }; forEachCell(cell => { if (cell.qnums.length === 0) { return; } let list = [[-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0]].map(([x, y]) => offset(cell, x, y)); let mask = parseInt(list.map(c => isEmpty(c) ? "1" : "0").join(""), 2); let blk = parseInt(list.map(c => isBlack(c) ? "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); return; } 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 = true; c.setQans(CQANS.black); c.draw(); }; let add_white = function (c) { if (c.isnull || c.qans !== CQANS.none) { return; } if (step && flg) { return; } flg = true; c.setQans(CQANS.white); c.draw(); }; forEachCell(cell => { if (cell.qnum !== CQNUM.none && cell.ques === 1) { add_black(cell); } if (cell.qnum !== CQNUM.none && cell.ques === 0) { add_white(cell); } }); SizeRegion_Cell({ isShaded: isBlack, 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: isBlack, add_shaded: add_white, add_unshaded: add_black, NoUnshadedNum: false, }); } function SlalomAssist() { // TODO: an easier way to check gate number const stc = board.getc(board.startpos.bx, board.startpos.by); const gates = board.gatemgr.components.length; let add_order = function (c, l) { if (c === undefined || c.isnull || c.ques === 1) { return; } l = unique(l); l = l.filter(n => n > 0 && n <= gates + 1); if (c.qsub !== 0) { l = l.filter(n => c.qsub.includes(n)); } if (c.qsub !== 0 && l.length === c.qsub.length) { return; } if (l.length === 0) { return; } if (step && flg) { return; } flg2 = true; c.setQsub(l); c.draw(); }; 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 genlist = c => { if (c.adjborder === undefined) { return []; } return [[c.adjborder.top, BQSUB.arrow_up, BQSUB.arrow_dn], [c.adjborder.bottom, BQSUB.arrow_dn, BQSUB.arrow_up], [c.adjborder.left, BQSUB.arrow_lt, BQSUB.arrow_rt], [c.adjborder.right, BQSUB.arrow_rt, BQSUB.arrow_lt]] }; add_order(stc, [gates + 1]); SingleLoopInCell({ isPassable: c => c.ques !== 1, isPass: c => c === stc, Directed: true, }); let gaten = new Set(board.gatemgr.components.flatMap(gate => Array.from(gate.clist).flatMap(c => { if (c.qsub === 0 || c.qsub.length > 1) return []; return c.qsub; }))); gaten = new Array(gates).fill(0).map((_, i) => i + 1).filter(n => !gaten.has(n)); for (let i = 0; i < board.gatemgr.components.length; i++) { let clist = Array.from(board.gatemgr.components[i].clist); clist = clist.sort((c1, c2) => c1.id - c2.id); let c1 = clist[0], c2 = clist[clist.length - 1]; if ([CRQSUB.in, CRQSUB.out].includes(offset(c2, .5, .5).qsub)) { add_inout(offset(c1, -.5, -.5), offset(c2, .5, .5).qsub ^ 1); } if ([CRQSUB.in, CRQSUB.out].includes(offset(c1, -.5, -.5).qsub)) { add_inout(offset(c2, .5, .5), offset(c1, -.5, -.5).qsub ^ 1); } clist.forEach(c => { if (c.qsub === 0 || c.qsub.length > 1) { add_order(c, gaten); } }); } forEachCell(cell => { 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 => !isCross(c.adjborder.left)).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 => !isCross(c.adjborder.top)).length === 1) { list.forEach(c => add_line(c.adjborder.top)); } } if (cell.gate !== null && cell.gate.number !== -1) { add_order(cell, [cell.gate.number]); } adjlist(cell.adjborder).forEach((nb, d) => { let clist = cellTrail(cell, d); let nc = clist[clist.length - 1]; let dn = clist.slice(1, -1).filter(c => c === stc || c.gate !== null).length; let bl = [cell, nc].some(c => c !== stc && c.gate === null); if (cell.qsub !== 0 && nc.qsub !== 0) { if (cell.qsub.every(n1 => nc.qsub.every(n2 => Math.abs(n1 - n2) > (bl ? .5 : 1) + dn && Math.abs(n1 - n2) < gates + (bl ? .5 : 0) - dn))) { add_cross(nb); } } if (cell.qsub !== 0 && nc.qsub !== 0 && cell.qsub.length === 1 && nc.qsub.length === 1 && isLine(nb)) { let l = genlist(cell).filter(([b, arr_o, arr_i]) => b === nb); let t = Math.abs(cell.qsub[0] - nc.qsub[0]) < gates + (bl ? .5 : 0); if (cell.qsub[0] < nc.qsub[0]) { add_arrow(nb, l[0][t ? 1 : 2]); } if (cell.qsub[0] > nc.qsub[0]) { add_arrow(nb, l[0][t ? 2 : 1]); } } }); if (cell.qsub !== 0 && cell.qsub.length === 1 && cell.lcnt > 0) { let cset = new Set(); let dfs = function (c, d, t = 0) { if (cset.has(c) || c !== cell && c.qsub !== 0 && c.qsub.length === 1) { return; } cset.add(c); if (c.gate !== null && c !== cell) { d += .5; } let cl = [(cell === stc ? 0 : cell.qsub[0]) + d, cell.qsub[0] - d]; if (t === 0) { add_order(c, cl); } if (t === 1) { add_order(c, [cl[0]]); } if (t === 2) { add_order(c, [cl[1]]); } adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!isLine(nb)) { return; } let l = genlist(c).filter(([b, arr_o, arr_i]) => b === nb); let nt = t; if (l[0][1] === nb.qsub) { nt = 1; } if (l[0][2] === nb.qsub) { nt = 2; } dfs(nc, d + (c.gate !== null || c === stc ? .5 : 0), nt); }); } dfs(cell, 0); } adjlist(cell.adjborder, cell.adjacent).forEach(([nb, nc]) => { if (cell.qsub === 0 || nc.isnull || nc.qsub === 0) { return; } if (Math.abs(cell.qsub - nc.qsub) > 1 && Math.abs(cell.qsub - nc.qsub) < gates) { add_cross(nb); } if (Math.abs(cell.qsub - nc.qsub) === 1 && cell.qsub % 1 === .5) { add_cross(nb); } }); }); } function StarbattleAssist() { // TODO: check if there are exactly n dot/star in region/row/column let isCircle = b => !b.isnull && b.qsub === 1; let isStar = isBlack; let starcount = board.starCount.count; let add_star = add_black; let add_cir = function (b) { if (b === undefined || b.isnull || b.line || b.qsub !== BQSUB.none) { return; } if (step && flg) { return; } if (b.bx % 2 === 0 && b.by % 2 === 0) { if (adjlist(b.adjborder).some(b => isCircle(b))) { return; } } if (b.bx === board.minbx) { b = offset(b, +.5, 0); } if (b.bx === board.maxbx) { b = offset(b, -.5, 0); } if (b.by === board.minby) { b = offset(b, 0, +.5); } if (b.by === board.maxby) { b = offset(b, 0, -.5); } if (b.bx % 2 === 1 && b.by % 2 === 1) { add_star(b); return; } b.setQsub(1); b.draw(); flg ||= b.qsub === 1; }; if (board.rows === starcount * 4) { let ct = board.getobj(board.cols, board.rows); for (let i = 0; i < starcount; i++) { for (let j = 0; j < starcount; j++) { for (let d = 0; d < 4; d++) { add_cir(offset(ct, 2 * i + 1, 2 * j + 1, d)); add_dot(offset(ct, 2 * i + 0.5, 2 * j + 0.5, d)); add_dot(offset(ct, 2 * i + 1.5, 2 * j + 1.5, d)); } } } } // try covering area with 2*2 let tryCovering = function (clist, n = starcount) { clist = clist.filter(c => !isDot(c)); if (clist.filter(c => c.qsub !== CQSUB.dot).length <= n * 4) { let fg = false; let dfs = function (list = [], dlist = []) { if (list.filter(c => clist.includes(c)).length + (n - dlist.length) * 4 < clist.length) { return; } if (dlist.length === n) { dlist.forEach(b => add_cir(b)); list.forEach(c => { if (!clist.includes(c)) { add_dot(c); } }); fg = true; } if (fg) { return; } let c = clist.find(cc => !list.includes(cc)); if (c === undefined) { return; } for (let d = 0; d < 4; d++) { let l = [c, offset(c, 1, 0, d), offset(c, 0, 1, d), offset(c, 1, 1, d)].filter(cc => !c.isnull); if (l.some(cc => list.includes(cc))) { continue; } dfs([...list, ...l], [...dlist, offset(c, .5, .5, d)]); if (fg) { return; } } }; dfs(); } }; for (let i = 0; i < board.rows - 1; i++) { let hclist = [], vclist = []; for (let j = 0; j < board.cols; j++) { hclist.push(board.getc(j * 2 + 1, i * 2 + 1)); hclist.push(board.getc(j * 2 + 1, i * 2 + 3)); vclist.push(board.getc(i * 2 + 1, j * 2 + 1)); vclist.push(board.getc(i * 2 + 3, j * 2 + 1)); } tryCovering(hclist, starcount * 2); tryCovering(vclist, starcount * 2); } forEachRoom(room => { let clist = Array.from(room.clist).filter(c => !isDot(c)); // finish room if (clist.filter(c => c.qans === CQANS.star).length === starcount) { clist.forEach(c => add_dot(c)); } if (clist.filter(c => c.qsub !== CQSUB.dot).length === starcount) { clist.forEach(c => add_star(c)); } tryCovering(clist); }); 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 * j + 1, 2 * i + 1)); vcellList.push(board.getc(2 * i + 1, 2 * j + 1)); } hcellList = hcellList.filter(c => !isDot(c)); vcellList = vcellList.filter(c => !isDot(c)); // finish row/col if (hcellList.filter(c => isStar(c) || !isStar(c) && !isStar(c.adjacent.right) && isCircle(c.adjborder.right) && !isCircle(c.adjborder.left)).length === starcount) { hcellList.forEach(c => !isCircle(c.adjborder.left) && !isCircle(c.adjborder.right) && add_dot(c)); } if (vcellList.filter(c => isStar(c) || !isStar(c) && !isStar(c.adjacent.bottom) && isCircle(c.adjborder.bottom) && !isCircle(c.adjborder.top)).length === starcount) { vcellList.forEach(c => !isCircle(c.adjborder.top) && !isCircle(c.adjborder.bottom) && add_dot(c)); } for (let j = 0; j < hcellList.length; j++) { if (hcellList[j].adjacent.right === hcellList[j + 1]) { hcellList[j] = [hcellList[j], hcellList[j + 1]]; hcellList.splice(j + 1, 1); } else { hcellList[j] = [hcellList[j]]; } } if (hcellList.length === starcount) { hcellList.forEach(l => { if (l.length === 1) { add_star(l[0]); } if (l.length === 2) { add_cir(l[0].adjborder.right); } }); } for (let j = 0; j < vcellList.length; j++) { if (vcellList[j].adjacent.bottom === vcellList[j + 1]) { vcellList[j] = [vcellList[j], vcellList[j + 1]]; vcellList.splice(j + 1, 1); } else { vcellList[j] = [vcellList[j]]; } } if (vcellList.length === starcount) { vcellList.forEach(l => { if (l.length === 1) { add_star(l[0]); } if (l.length === 2) { add_cir(l[0].adjborder.bottom); } }); } } forEachCell(cell => { 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)); } } } }); // adjust circle 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)); if (offset(cross, 0, -.5, d).qsub === 1) { 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 < 2; 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 forEachBorder(border => { if (border.sidecell.some(c => c.qnum !== CQNUM.none)) { add_inout(border.sidecross[0], border.sidecross[1].qsub); add_inout(border.sidecross[1], border.sidecross[0].qsub); } }); forEachCell(cell => { if (isClue(cell)) { // 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 ? CRQSUB.out : CRQSUB.in)); } } // finish clue if (isNum(cell)) { let d = qdirRemap(cell.qdir); let borderlist = []; let pcell = offset(cell, 1, 0, d); let qnum = cell.qnum; while (!pcell.isnull && (pcell.qnum < 0 || pcell.qdir !== cell.qdir)) { let b = offset(pcell, .5, 0, d); if (!b.isnull && b.sidecell[0].qnum === CQNUM.none && b.sidecell[1].qnum === CQNUM.none) { borderlist.push(b); } pcell = offset(pcell, 1, 0, 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)); } } } }); } 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 isBorderBlack = c => c.isnull || isBlack(c); 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); forEachCell(cell => { let blackcnt = adjlist(cell.adjacent).filter(c => isBorderBlack(c)).length; let dotcnt = adjlist(cell.adjacent).filter(c => isDot(c)).length; // no clue pattern // add dot if (isEmpty(cell)) { for (let d = 0; d < 4; d++) { // cannot place black with 2x2 black rule // : █ // █_█ : _█ -> A is dot // A█ ; A█ 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); } // : █ : : █ // █_█ : _█ : █_█ : _█ -> A is dot // A_█ : A_█ : A_ : A_ // ; ; █ ; █ 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); } // ██ // █__ -> A is dot // A█ 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)) && isBorderBlack(offset(cell, 1, -2, 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)) && isBorderBlack(offset(cell, -1, -2, d))) { 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: isBlack, 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: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, OneNumPerRegion: false, NoUnshadedNum: false, }); SizeRegion_Cell({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, OneNumPerRegion: false, NoUnshadedNum: false, }); RectRegion_Cell({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, isSizeAble: (w, h, sc, c) => { for (let i = 0; i < w; i++) { for (let j = 0; j < h; j++) { if (offset(c, i, j).qnum > 0 && offset(c, i, j).qnum !== w * h) { return false; } } } return true; }, }); forEachCell(cell => { if (cell.qnum === 1 || cell.qnum === 2) { add_black(cell); } // non-rect if (isGreen(cell)) { let templist = [offset(cell, 1, 0), offset(cell, 0, 1), offset(cell, -1, 0), offset(cell, 0, -1)]; templist = templist.filter(c => !c.isnull && !isBlack(c)); 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 && !isBlack(c) && c !== cell); if (templist2.length === 1) { add_green(templist2[0]); } } } }); } function CreekAssist() { CellConnected({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, isNotPassable: (c, nb, nc) => {// there isn't board.border so here is a workaround for (let d = 0; d < 4; d++) { if (offset(c, 1, 0, d) === nc) { if (offset(c, .5, -.5, d).qnum + [offset(c, 0, -1, d), offset(c, 1, -1, d)].filter(c => isntBlack(c)).length >= 3) { return true; } if (offset(c, .5, +.5, d).qnum + [offset(c, 0, +1, d), offset(c, 1, +1, d)].filter(c => isntBlack(c)).length >= 3) { return true; } } } }, }); let dotcnt = 0; forEachCell(cell => { dotcnt += cell.qsub === CQSUB.dot; }); for (let i = 0; i < board.cross.length; i++) { let cross = board.cross[i]; let list = [board.getc(cross.bx - 1, cross.by - 1), board.getc(cross.bx - 1, cross.by + 1), board.getc(cross.bx + 1, cross.by - 1), board.getc(cross.bx + 1, cross.by + 1)]; list = list.filter(c => !c.isnull); if (cross.qnum >= 0) { if (list.filter(c => isBlack(c)).length === cross.qnum) { list.forEach(c => add_dot(c)); } if (list.filter(c => c.qsub !== CQSUB.dot).length === cross.qnum) { list.forEach(c => add_black(c)); } } for (let d = 0; d < 4; d++) { let ncross = offset(cross, 1, 0, d); // ++++ // +AB+ // ++++ if (cross.qnum >= 0 && ncross.qnum >= 0) { let n1 = cross.qnum, n2 = ncross.qnum; let r = [[offset(cross, -.5, -.5, d), offset(cross, -.5, +.5, d)], [offset(cross, +.5, -.5, d), offset(cross, +.5, +.5, d)], [offset(cross, 1.5, -.5, d), offset(cross, 1.5, +.5, d)]]; let fn = l => [0, 1, 2].filter(n => n >= l.filter(c => isBlack(c)).length && n <= l.filter(c => !isntBlack(c)).length) let cand = getComb(r.map(l => fn(l))); cand = cand.filter(([a1, a2, a3]) => a1 + a2 === n1 && a2 + a3 === n2); for (let i = 0; i <= 2; i++) { if (cand.length > 0 && cand.every(l => l[i] === cand[0][i])) { NShadeInClist({ clist: r[i], n: cand[0][i] }); } } } // // + + + + + + + + // // · █ // // + 1 3 + -> + 1 3 + // // · █ // // + + + + + + + + // if (cross.qnum === 1 && offset(cross, 1, 0, d).qnum === 3) { // add_dot(offset(cross, -.5, -.5, d)); // add_dot(offset(cross, -.5, +.5, d)); // add_black(offset(cross, 1.5, -.5, d)); // add_black(offset(cross, 1.5, +.5, d)); // } // // + + + + + + + + // // · · · // // + 1 2 + -> + 1 2 + // // · █ // // + + + + + + + + // if (cross.qnum === 1 && offset(cross, 1, 0, d).qnum === 2 && // (isDot(offset(cross, 1.5, -.5, d)) || isDot(offset(cross, 1.5, +.5, d)))) { // add_dot(offset(cross, -.5, -.5, d)); // add_dot(offset(cross, -.5, +.5, d)); // add_black(offset(cross, 1.5, -.5, d)); // add_black(offset(cross, 1.5, +.5, d)); // } // // + + + + + + + + // // · · · // // + 1 1 + -> + 1 1 + // // · · · // // + + + + + + + + // if (cross.qnum === 1 && offset(cross, 1, 0, d).qnum === 1 && // (isDot(offset(cross, 1.5, -.5, d)) && isDot(offset(cross, 1.5, +.5, d)))) { // add_dot(offset(cross, -.5, -.5, d)); // add_dot(offset(cross, -.5, +.5, d)); // } // + + + + + + + + + + // █ █ // + 3 2 3 + -> + 3 2 3 + // █ █ // + + + + + + + + + + if (cross.qnum === 3 && offset(cross, 1, 0, d).qnum === 2 && offset(cross, 2, 0, d).qnum === 3) { add_black(offset(cross, -.5, -.5, d)); add_black(offset(cross, -.5, +.5, d)); add_black(offset(cross, 2.5, -.5, d)); add_black(offset(cross, 2.5, +.5, d)); } // + + + + + + + + + + // · · // + 1 2 1 + -> + 1 2 1 + // · · // + + + + + + + + + + if (cross.qnum === 1 && offset(cross, 1, 0, d).qnum === 2 && offset(cross, 2, 0, d).qnum === 1) { add_dot(offset(cross, -.5, -.5, d)); add_dot(offset(cross, -.5, +.5, d)); add_dot(offset(cross, 2.5, -.5, d)); add_dot(offset(cross, 2.5, +.5, d)); } // + + + + + + + + // // + 3 + + + 3 + + // -> █ // + + 3 + + + 3 + // // + + + + + + + + if (cross.qnum === 3 && offset(cross, 1, 1, d).qnum === 3 && dotcnt > 0) { add_black(offset(cross, .5, .5, d)); } } } } 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; } // record the qnum count/sum in (0,0) to (a,b) let scnt = Array.from(new Array(board.rows + 1), () => new Array(board.cols + 1).fill(0)); let ncnt = Array.from(new Array(board.rows + 1), () => new Array(board.cols + 1).fill(0)); for (let i = 0; i <= board.rows; i++) { for (let j = 0; j <= board.cols; j++) { let f = (arr, a, b) => a < 0 || b < 0 ? 0 : arr[a][b]; scnt[i][j] = Math.max(0, board.getobj(2 * j, 2 * i).qnum); scnt[i][j] += f(scnt, i - 1, j) + f(scnt, i, j - 1) - f(scnt, i - 1, j - 1); ncnt[i][j] = board.getobj(2 * j, 2 * i).qnum >= 0 ? 1 : 0; ncnt[i][j] += f(ncnt, i - 1, j) + f(ncnt, i, j - 1) - f(ncnt, i - 1, j - 1); } } // check the clue for the four corners of the corresponding rectangle let getClue = function (cr1, cr2) { if (cr1.isnull || cr2.isnull) { return 0; } let [x1, x2] = [cr1.bx / 2, cr2.bx / 2].sort((x, y) => x - y); let [y1, y2] = [cr1.by / 2, cr2.by / 2].sort((x, y) => x - y); let f = (arr, a, b) => a < 0 || b < 0 ? 0 : arr[a][b]; let res = f(scnt, y2, x2) - f(scnt, y1 - 1, x2) - f(scnt, y2, x1 - 1) + f(scnt, y1 - 1, x1 - 1); res -= (Math.min(x2 + 1, board.cols) - Math.max(x1 - 1, 0)) * (y2 - y1); res -= (Math.min(y2 + 1, board.rows) - Math.max(y1 - 1, 0)) * (x2 - x1); return [res, (x2 - x1 + 1) * (y2 - y1 + 1) - (f(ncnt, y2, x2) - f(ncnt, y1 - 1, x2) - f(ncnt, y2, x1 - 1) + f(ncnt, y1 - 1, x1 - 1))]; } forEachCross(cross => { // finish clue // clue can be grouped in a rectangle and look at only four corners for (let dx = 0; getClue(cross, offset(cross, dx, 0))[1] <= 1; dx++) { for (let dy = 0; getClue(cross, offset(cross, dx, dy))[1] <= 1; dy++) { let [qnum, lckn] = getClue(cross, offset(cross, dx, dy)); // extreme value for one missing clue if (lckn > 0 && qnum !== -4 * lckn && qnum !== 4) { continue; } if (qnum < 0) { qnum = 0; } let adjcellList = [[offset(cross, -.5, -.5), CQANS.rslash, CQANS.lslash], [offset(cross, -.5, dy + .5), CQANS.lslash, CQANS.rslash], [offset(cross, dx + .5, -.5), CQANS.lslash, CQANS.rslash], [offset(cross, dx + .5, dy + .5), CQANS.rslash, CQANS.lslash]]; adjcellList = adjcellList.filter(c => !c[0].isnull); if (adjcellList.filter(c => c[0].qans === c[1]).length === qnum) { adjcellList.forEach(c => add_slash(c[0], c[2])); } if (adjcellList.filter(c => c[0].qans !== c[2]).length === qnum) { adjcellList.forEach(c => add_slash(c[0], c[1])); } } } for (let d = 0; d < 4; d++) { // + + + + + + + + // // + 1 + + + 1 + + // -> ╱ // + + 1 + + + 1 + // // + + + + + + + + if (cross.qnum === 1 && isNotSide(cross)) { let cross2 = offset(cross, 1, 1, d); if (cross2.qnum === 1 && isNotSide(cross2)) { add_slash(offset(cross, .5, .5, d), CQANS.lslash + d); } } // no loop for 2 or 3 if (offset(cross, .5, .5, d).qans === CQANS.none && offset(cross, 1, -1, d).path !== null && offset(cross, 1, -1, d).path === offset(cross, 1, 1, d).path) { if (cross.qnum === 3 || cross.qnum === 2 && (offset(cross, -.5, -.5, d).qans === (d % 2 === 0 ? CQANS.lslash : CQANS.rslash) || offset(cross, -.5, +.5, d).qans === (d % 2 === 1 ? CQANS.lslash : CQANS.rslash))) { add_slash(offset(cross, -.5, -.5, d), CQANS.rslash + d); add_slash(offset(cross, -.5, +.5, d), CQANS.lslash + d); } } if (offset(cross, -.5, .5, d).qans === CQANS.none && offset(cross, 1, -1, d).path !== null && offset(cross, 1, -1, d).path === offset(cross, -1, 1, d).path) { if (cross.qnum === 3 || cross.qnum === 2 && (offset(cross, -.5, -.5, d).qans === (d % 2 === 0 ? CQANS.lslash : CQANS.rslash) || offset(cross, +.5, +.5, d).qans === (d % 2 === 0 ? CQANS.lslash : CQANS.rslash))) { add_slash(offset(cross, -.5, -.5, d), CQANS.rslash + d); add_slash(offset(cross, +.5, +.5, d), CQANS.rslash + d); } } } }); // no loop // + + + + + + // / \ / \ // + + + -> + + + // / / / // + + + + + + forEachCell(cell => { if (cell.qans !== CQANS.none) { return; } for (let d = 0; d < 2; d++) { let cross1 = offset(cell, -.5, -.5, d), cross2 = offset(cell, .5, .5, d); if (cross1.path !== null && cross1.path === cross2.path) { add_slash(cell, CQANS.lslash + d); } } }); } function NuribouAssist() { StripRegion_cell({ isShaded: isBlack, add_unshaded: add_green, }); SizeRegion_Cell({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: c => add_black(c, true), }); let clen = new Map(); forEachCell(cell => { if (!isBlack(cell) || adjlist(cell.adjacent).filter(c => isBlack(c)).length > 1) { return; } let clist = [cell]; let d = [0, 1, 2, 3].find(d => isBlack(offset(cell, 1, 0, d))); if (d === undefined) { if (adjlist(cell.adjacent).every(nc => isntBlack(nc))) { clen.set(cell, 1); } return; } let pcell = offset(cell, 1, 0, d); while (isBlack(pcell)) { clist.push(pcell); pcell = offset(pcell, 1, 0, d); } if (isntBlack(pcell) && isntBlack(offset(cell, -1, 0, d))) { clist.forEach(c => clen.set(c, clist.length)); } }); forEachCell(cell => { if (!isBlack(cell) || adjlist(cell.adjacent).filter(c => isBlack(c)).length > 1) { return; } if (adjdiaglist(cell).some(c => clen.get(c) === 1) && adjlist(cell.adjacent).filter(c => !isntBlack(c)).length === 1) { add_black(adjlist(cell.adjacent).find(c => !isntBlack(c))); } if (adjdiaglist(cell).some(c => clen.get(c) === 2) && [0, 1, 2, 3].every(d => isntBlack(offset(cell, 1, 0, d)) || isntBlack(offset(cell, 2, 0, d)))) { adjlist(cell.adjacent).forEach(c => add_green(c)); } let d = [0, 1, 2, 3].find(d => isBlack(offset(cell, 1, 0, d))); if (d === undefined) { return; } let l = 1; while (isBlack(offset(cell, l, 0, d))) { l++; } if (adjdiaglist(cell).some(c => clen.get(c) === l)) { add_black(offset(cell, l, 0, d)); } if (adjdiaglist(cell).some(c => clen.get(c) === l + 1) && isntBlack(offset(cell, l + 1, 0, d))) { add_green(offset(cell, l, 0, d)); } }); } function NurikabeAssist() { CellConnected({ isShaded: isBlack, isUnshaded: c => c.qsub === CQSUB.dot || c.qnum !== CQNUM.none, add_shaded: c => add_black(c, true), add_unshaded: add_dot, }); No2x2Cell({ isShaded: isBlack, add_unshaded: add_dot, }); SizeRegion_Cell({ isShaded: c => c.qsub === CQSUB.dot || c.qnum !== CQNUM.none, isUnshaded: isBlack, add_shaded: add_dot, add_unshaded: c => add_black(c, true), }); } function GuideArrowAssist() { BlackNotAdjacent(); GreenConnected(); GreenNoLoopInCell(); let goalcell = board.getc(board.goalpos.bx, board.goalpos.by); forEachCell(cell => { if (cell === goalcell) { add_green(cell); return; } if (cell.qnum !== CQNUM.none) { add_green(cell); if (cell.qnum !== CQNUM.quesmark) { let d = qdirRemap(cell.qnum); add_green(offset(cell, 1, 0, d)); } return; } }); // 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 = offset(c, 1, 0, d1); if (c1 === undefined || c1.isnull || !isGreen(c1)) { continue; } dfs(c1, (d1 + 2) % 4); } }; dfs(goalcell, -1); forEachCell(cell => { if (cell.qnum === CQNUM.none || cell.qnum === CQNUM.quesmark) { return; } dfs(cell, qdirRemap(cell.qnum)); }); forEachCell(cell => { if (cell.qsub !== CQSUB.none || cell.qans !== CQANS.none) { return; } let cnt = 0; fourside(c => { if (isGreen(c) && vis.has(c)) { cnt++; } }, cell.adjacent); if (cnt >= 2) add_black(cell); }); } // single out { let vis = new Map(); vis.set(goalcell, -1); forEachCell(cell => { 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 = offset(cell, 1, 0, d1); if (c1 === undefined || c1.isnull || isBlack(c1)) { continue; } cnt++; dd = d1; } if (cnt === 1) { return dd; } return -1; } return qdirRemap(cell.qnum); })(); if (d === -1) { return; } while (true) { if (vis.has(cell)) { break; } vis.set(cell, d); cell = offset(cell, 1, 0, d); add_green(cell); let cnt = 0; let dd = -1; for (let d1 = 0; d1 < 4; d1++) { let c1 = offset(cell, 1, 0, d1); if (c1 === undefined || c1.isnull || isBlack(c1)) { 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); } } } forEachCell(cell => { if (cell.qnum !== CQNUM.none) { add_color(cell, cell.qnum); } // ○ ● ○●● // ○ ○ -> ○ ○ 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 PaintareaAssist() { No2x2Black(); No2x2Green(); CellConnected({ isShaded: isBlack, isUnshaded: isGreen, add_shaded: add_black, add_unshaded: add_green, isLinked: (c, nb, nc) => c.room === nc.room, }); forEachCell(cell => { if (cell.qnum >= 0 && adjlist(cell.adjacent).filter(c => isBlack(c)).length === cell.qnum) { adjlist(cell.adjacent).forEach(c => add_green(c)); } if (cell.qnum >= 0 && adjlist(cell.adjacent).filter(c => !c.isnull && !isGreen(c)).length === cell.qnum) { adjlist(cell.adjacent).forEach(c => add_black(c)); } // no 2*2 let list = [cell, offset(cell, 1, 0), offset(cell, 0, 1), offset(cell, 1, 1)]; if (!list.some(c => c.isnull) && !list.some(c => isGreen(c))) { let templist2 = list.filter(c => !c.qans); if (templist2.length > 0 && !templist2.some(c => c.room !== templist2[0].room)) { add_green(templist2[0]); } } if (!list.some(c => c.qans)) { let templist2 = list.filter(c => !isGreen(c)); if (templist2.length > 0 && !templist2.some(c => c.room !== templist2[0].room)) { add_black(templist2[0]); } } }); forEachRoom(room => { let cellList = Array.from(room.clist); if (cellList.some(c => isGreen(c))) { cellList.forEach(c => add_green(c)); return; } if (cellList.some(c => isBlack(c))) { cellList.forEach(c => add_black(c)); return; } }); } function ParquetAssist() { CellNoLoop({ isShaded: isBlack, isUnshaded: isDot, add_shaded: add_black, add_unshaded: add_dot, }); CellConnected({ isShaded: isBlack, isUnshaded: isDot, add_shaded: add_black, add_unshaded: add_dot, isLinked: (c, nb, nc) => c.room === nc.room, }); forEachRoom(room => { let clist = Array.from(room.clist); if (clist.some(c => isBlack(c))) { clist.forEach(c => add_black(c)); } if (clist.some(c => isDot(c))) { clist.forEach(c => add_dot(c)); } }); for (let i = 0; i < board.spblockgraph.components.length; i++) { let spblock = board.spblockgraph.components[i]; let rlist = spblock.tiles; let fn = room => Array.from(room.clist).some(c => isBlack(c)) ? "Black" : Array.from(room.clist).some(c => isGreen(c)) ? "Green" : "Empty"; if (rlist.some(r => fn(r) === "Black")) { rlist.filter(r => fn(r) === "Empty").forEach(r => Array.from(r.clist).forEach(c => add_dot(c))); } if (rlist.filter(r => fn(r) !== "Green").length === 1) { rlist.filter(r => fn(r) !== "Green").forEach(r => Array.from(r.clist).forEach(c => add_black(c))); } } forEachCell(cell => { let templist = [cell, offset(cell, 1, 0), offset(cell, 0, 1), offset(cell, 1, 1)]; if (templist.every(c => !c.isnull && !isGreen(c))) { let templist2 = templist.filter(c => !isBlack(c)); if (templist2.length > 0 && templist2.every(c => c.room === templist2[0].room)) { add_green(templist2[0]); } } }); } function NuriMazeAssist() { let startcell = board.getc(board.startpos.bx, board.startpos.by); let goalcell = board.getc(board.goalpos.bx, board.goalpos.by); No2x2Black(); No2x2Green(); CellConnected({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, isLinked: (c, nb, nc) => c.room === nc.room, }); CellConnected({ isShaded: c => { return c === startcell || c === goalcell || c.ques === CQUES.cir; }, isUnshaded: c => isBlack(c) || c.ques === CQUES.tri, add_shaded: add_green, add_unshaded: () => { }, isLinked: (c, nb, nc) => c.room === nc.room, }); CellConnected({ isShaded: c => { if (c === startcell || c === goalcell) { return c.lcnt === 0; } return c.lcnt == 1; }, isUnshaded: c => { if (c === startcell || c === goalcell) { return c.lcnt === 1; } return c.lcnt === 2 || isBlack(c) || c.ques === CQUES.tri; }, add_shaded: add_green, add_unshaded: () => { }, isLinked: (c, nb, nc) => c.room === nc.room, cantDivideShade: (s, o) => s % 2 === 1, OnlyOneConnected: false, UnshadeEmpty: false, }); let circnt = 0; forEachCell(cell => { circnt += cell.ques === CQUES.cir; }); forEachRoom(room => { let cellList = Array.from(room.clist); if (cellList.some(c => isGreen(c) || c.ques === CQUES.cir || c.ques === CQUES.tri || c.lcnt > 0) || room === startcell.room || room === goalcell.room) { cellList.forEach(c => add_green(c)); return; } if (cellList.some(c => isBlack(c))) { cellList.forEach(c => add_black(c)); return; } 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 => isGreen(c)); if (templist.length < 2) { return; } // no loop let templist2 = templist.map(c => { let dfslist = []; let dfs = function (c) { if (c.isnull || !isGreen(c) || 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)); return; } // not enough cir if (circnt1 + circnt2 < circnt) { cellList.forEach(c => add_black(c)); return; } // no branch for line templist2 = templist.map(c => { let res = 0; let dfslist = []; let dfs = function (c) { if (c.isnull || !isGreen(c) || 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)); return; } }); forEachCell(cell => { 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 && !isBlack(c)); 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 => isBlack(c)).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 => isGreen(c))) { 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 => !isGreen(c)); if (templist2.length > 0 && !templist2.some(c => c.room !== templist2[0].room)) { add_black(templist2[0]); } } } }); // line forEachCell(cell => { if (isBlack(cell) || cell.ques === CQUES.tri) { fourside(add_cross, cell.adjborder); } if (!isBlack(cell)) { let emptycnt = 0; let linecnt = 0; fourside((c, b) => { if (!c.isnull && !isCross(b)) { emptycnt++; } linecnt += b.line; }, cell.adjacent, cell.adjborder); if (linecnt > 0) { add_green(cell); } // no branch if (linecnt === 2 || linecnt === 1 && (cell === startcell || cell === goalcell)) { fourside(add_cross, cell.adjborder); } // no deadend if (emptycnt === 1) { if (cell !== startcell && cell !== goalcell) { fourside(add_cross, cell.adjborder); } else { fourside((c, b) => { if (!c.isnull && !isCross(b)) { add_line(b); } }, cell.adjacent, cell.adjborder); } } // 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); } }, cell.adjacent, cell.adjborder); } // 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 || !isGreen(c) || 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(); let n = 0; let ord = new Map(); let siz = new Map(); let qn = new Map(); forEachCell(cell => (cell.qnum !== CQNUM.none ? add_black(cell) : undefined)); forEachCell(cell => { if (isBlack(cell) && !ord.has(cell)) { n++; siz.set(n, 0); qn.set(n, -1); let cl = []; let dfs = function (c) { if (!isBlack(c) || ord.has(c)) { return; } ord.set(c, n); cl.push(c); siz.set(n, siz.get(n) + 1); if (isNum(c)) { qn.set(n, c.qnum); } dfs(offset(c, -1, -1)); dfs(offset(c, -1, +1)); dfs(offset(c, +1, -1)); dfs(offset(c, +1, +1)); }; dfs(cell); let ol = cl.flatMap(c => adjdiaglist(c)); ol = ol.filter(c => !c.isnull && !isBlack(c) && !isGreen(c)); if (cl.length < qn.get(n) && ol.length === 1) { add_black(ol[0]); ord.set(ol[0], n); } } }); forEachCell(cell => { if (!isBlack(cell) && !isGreen(cell)) { let l = adjdiaglist(cell); l = l.filter(c => isBlack(c)); l = Array.from(new Set(l.map(c => ord.get(c)))); if (l.length > 0 && l.some(i => qn.get(i) !== -1)) { if (new Set(l.map(i => qn.get(i)).filter(i => i !== -1)).size > 1) { add_green(cell); } if (l.map(i => qn.get(i)).find(i => i !== -1) < l.map(i => siz.get(i)).reduce((a, b) => a + b) + 1) { add_green(cell); } } } }); } function IcelomAssist() { 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 ncnt = 0; forEachCell(cell => isClue(cell) ? ncnt++ : undefined); let getTrailDir = function (t) { // get direction of a trial. 0: forward, 1: backward, 2: unknown, -1: broken let qn = t.flatMap(c => isClue(c) ? c.qnum : []); if (inb.sidecell.includes(t[0])) { qn = [0].concat(qn); } if (outb.sidecell.includes(t[0])) { qn = [ncnt + 1].concat(qn); } if (inb.sidecell.includes(t[t.length - 1])) { qn.push(0); } if (outb.sidecell.includes(t[t.length - 1])) { qn.push(ncnt + 1); } let f = function (l) { l = l.flatMap((n, i) => n === CQNUM.quesmark ? [] : n - i); if (l.length === 0) { return true; } return unique(l).length === 1; }; if (f(qn) && f([...qn].reverse())) { return 2; } if (f(qn)) { return 0; } if (f([...qn].reverse())) { return 1; } return -1; }; let inb = board.getb(board.arrowin.bx, board.arrowin.by); let outb = board.getb(board.arrowout.bx, board.arrowout.by); add_line(inb); add_line(outb); SingleLoopInCell({ isPass: c => !isIce(c), Directed: true }); // add cross outside except IN and OUT forEachBorder(border => { if (!border.inside && border !== inb && border !== outb) { add_cross(border); } if (border.qdir !== QDIR.none) { add_arrow(border, border.qdir + 10); // from qdir to bqsub add_line(border); } if (!isLine(border) && !isCross(border)) { let t = border.sidecell.map(c => { let d = [0, 1, 2, 3].find(d => offset(c, .5, 0, d) === border); let tr = cellTrail(c, d, true); tr.shift(); return tr; }); t = [...t[0].reverse(), ...t[1]]; if (getTrailDir(t) === -1) { add_cross(border); } } }); forEachCell(cell => { if (isClue(cell) && cell.lcnt === 2) { fourside(add_cross, cell.adjborder); } if (isClue(cell) && adjlist(cell.adjborder).filter(b => !isntLine(b)).length === 2) { fourside(add_line, cell.adjborder); } if (cell.lcnt !== 1 || isIce(cell)) { return; } for (let d = 0; d < 4; d++) { if (!isLine(offset(cell, .5, 0, d))) { let ncell = offset(cell, 1, 0, d); while (!ncell.isnull && isIce(ncell)) { ncell = offset(ncell, 1, 0, d); } if (ncell.isnull || ncell.lcnt !== 1) { continue; } if (board.linegraph.components.length > 2 && (cell.path === inb.path && ncell.path === outb.path || cell.path === outb.path && ncell.path === inb.path)) { add_cross(offset(cell, .5, 0, d)); } } else if (offset(cell, .5, 0, d).qsub === BQSUB.none) { let dl = [[cell.adjborder.right, BQSUB.arrow_rt, BQSUB.arrow_lt], [cell.adjborder.top, BQSUB.arrow_up, BQSUB.arrow_dn], [cell.adjborder.left, BQSUB.arrow_lt, BQSUB.arrow_rt], [cell.adjborder.bottom, BQSUB.arrow_dn, BQSUB.arrow_up],]; let dir = getTrailDir(cellTrail(cell, d, true)); if (dir === -1 || dir === 2) { continue; } add_arrow(offset(cell, .5, 0, d), dl[d][dir + 1]); } } }); } function IcebarnAssist() { 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 inb = board.getb(board.arrowin.bx, board.arrowin.by); let outb = board.getb(board.arrowout.bx, board.arrowout.by); add_line(inb); add_line(outb); SingleLoopInCell({ Directed: true }); // add cross outside except IN and OUT forEachBorder(border => { if (!border.inside && border !== inb && border !== outb) { add_cross(border); } if (border.qdir != QDIR.none) { add_arrow(border, border.qdir + 10); // from qdir to bqsub add_line(border); } }); let iceSet = new Set(); forEachCell(cell => { // pass all ice if (isIce(cell) && !iceSet.has(cell)) { let list = [], blist = []; let dfs = function (c) { if (c.isnull || !isIce(c) || iceSet.has(c)) { return; } iceSet.add(c); list.push(c); fourside(b => blist.push(b), c.adjborder); fourside(dfs, c.adjacent); } dfs(cell); blist = blist.filter(b => b.sidecell.some(c => isIce(c)) && b.sidecell.some(c => !c.isnull && !isIce(c))); blist = blist.filter(b => !isCross(b)); if (blist.length === 2) { blist.forEach(b => add_line(b)); } } if (cell.lcnt === 1 && !isIce(cell)) { for (let d = 0; d < 4; d++) { let ncell = offset(cell, 1, 0, d); while (!ncell.isnull && isIce(ncell)) { ncell = offset(ncell, 1, 0, d); } if (ncell.isnull || ncell.lcnt !== 1 || offset(ncell, -.5, 0, d).line) { continue; } if (board.linegraph.components.length > 2 && (cell.path === inb.path && ncell.path === outb.path || cell.path === outb.path && ncell.path === inb.path)) { add_cross(offset(cell, .5, 0, d)); } } } }); } function InverseLitsoAssist() { BlackConnected(); CellConnected({ isShaded: isGreen, isUnshaded: isBlack, add_shaded: add_green, add_unshaded: add_black, isNotPassable: (c, nb, nc) => nb.ques, OnlyOneConnected: false, UnshadeEmpty: false, }); No2x2Black(); const LITSO = [ [[0, 0], [1, 0], [2, 0], [0, 1]],// L [[0, 0], [1, 0], [2, 0], [2, 1]], [[0, 0], [0, 1], [1, 1], [2, 1]], [[0, 0], [0, 1], [-1, 1], [-2, 1]], [[0, 0], [1, 0], [0, 1], [0, 2]], [[0, 0], [1, 0], [1, 1], [1, 2]], [[0, 0], [0, 1], [0, 2], [1, 2]], [[0, 0], [0, 1], [0, 2], [-1, 2]], [[0, 0], [1, 0], [2, 0], [3, 0]],// I [[0, 0], [0, 1], [0, 2], [0, 3]], [[0, 0], [1, 0], [2, 0], [1, 1]],// T [[0, 0], [1, 0], [2, 0], [1, -1]], [[0, 0], [0, 1], [0, 2], [1, 1]], [[0, 0], [0, 1], [0, 2], [-1, 1]], [[0, 0], [1, 0], [1, 1], [2, 1]],// S [[0, 0], [1, 0], [1, -1], [2, -1]], [[0, 0], [0, 1], [1, 1], [1, 2]], [[0, 0], [0, 1], [-1, 1], [-1, 2]], [[0, 0], [1, 0], [0, 1], [1, 1]],// O ]; const CANDMAX = 50; forEachRoom(room => { let clist = Array.from(room.clist).filter(c => !isBlack(c)); let bclist = Array.from(room.clist).filter(c => !isBlack(c) && isGreen(c)); let clistSet = new Set(clist); let cand = []; for (let j = 0; j < clist.length; j++) { let list = LITSO; list = list.map(l => l.map(([dx, dy]) => offset(clist[j], dx, dy))); list = list.filter(l => !l.some(c => !clistSet.has(c))); if (bclist.length > 0) { list = list.filter(l => bclist.every(c => l.includes(c))); } list = list.filter(l => { let nlist = []; l.forEach(c => { fourside(nc => { if (isGreen(nc) && nc.room !== room) { nlist.push(nc); } }, c.adjacent); }); // no 2*2 let no2x2 = true; clist.forEach(c => { if (no2x2 === false) { return; } for (let d = 0; d < 4; d++) { if ([c, offset(c, 1, 0, d), offset(c, 0, 1, d), offset(c, 1, 1, d)].every(cc => isBlack(cc) || cc.room === room && !l.includes(cc))) { no2x2 = false; return; } } }); if (no2x2 === false) { return false; } // no same shape let cshape = getShape(l); nlist = nlist.map(c => { let clist = Array.from(c.room.clist).filter(c => isGreen(c)); if (clist.length > 4) return null; return getShape(clist); }); return !nlist.includes(cshape); }); cand.push(...list); if (cand.length > CANDMAX) { break; } } if (cand.length > CANDMAX) { return; } for (let j = 0; j < clist.length; j++) { if (cand.every(l => l.includes(clist[j]))) { add_dot(clist[j]); } if (cand.every(l => !l.includes(clist[j]))) { add_black(clist[j]); } } }); } function LitsAssist() { BlackConnected(); BlackConnected_InRegion(); No2x2Black(); let cset = new Set(); forEachCell(cell => { if (cset.has(cell) || isDot(cell)) { return; } let l = []; let dfs = function (c) { if (c.isnull || cset.has(c) || isDot(c) || c.room !== cell.room) { return; } cset.add(c); l.push(c); fourside(dfs, c.adjacent); }; dfs(cell); if (l.length < 4) { l.forEach(c => add_dot(c)); } }); const LITS = [ [[0, 0], [1, 0], [2, 0], [0, 1]],// L [[0, 0], [1, 0], [2, 0], [2, 1]], [[0, 0], [0, 1], [1, 1], [2, 1]], [[0, 0], [0, 1], [-1, 1], [-2, 1]], [[0, 0], [1, 0], [0, 1], [0, 2]], [[0, 0], [1, 0], [1, 1], [1, 2]], [[0, 0], [0, 1], [0, 2], [1, 2]], [[0, 0], [0, 1], [0, 2], [-1, 2]], [[0, 0], [1, 0], [2, 0], [3, 0]],// I [[0, 0], [0, 1], [0, 2], [0, 3]], [[0, 0], [1, 0], [2, 0], [1, 1]],// T [[0, 0], [1, 0], [2, 0], [1, -1]], [[0, 0], [0, 1], [0, 2], [1, 1]], [[0, 0], [0, 1], [0, 2], [-1, 1]], [[0, 0], [1, 0], [1, 1], [2, 1]],// S [[0, 0], [1, 0], [1, -1], [2, -1]], [[0, 0], [0, 1], [1, 1], [1, 2]], [[0, 0], [0, 1], [-1, 1], [-1, 2]], ]; const CANDMAX = 50; forEachRoom(room => { let clist = Array.from(room.clist).filter(c => !isGreen(c)); let bclist = Array.from(room.clist).filter(c => !isGreen(c) && isBlack(c)); let clistSet = new Set(clist); let cand = []; for (let j = 0; j < clist.length; j++) { let list = LITS; list = list.map(l => l.map(([dx, dy]) => offset(clist[j], dx, dy))); list = list.filter(l => !l.some(c => !clistSet.has(c))); if (bclist.length > 0) { list = list.filter(l => bclist.every(c => l.includes(c))); } list = list.filter(l => l.every(c => !adjlist(c.adjborder, c.adjacent).some(([nb, nc]) => nb.ques === 1 && l.includes(nc)))); list = list.filter(l => { let nlist = [], con = false; l.forEach(c => { fourside(nc => { if (isBlack(nc) && nc.room !== room) { nlist.push(nc); } if (!nc.isnull && !isGreen(nc) && nc.room !== room) { con = true; } }, c.adjacent); }); // connectivity if (!con && board.roommgr.components.length > 1) { return false; } // no 2*2 let no2x2 = true; l.forEach(c => { if (no2x2 === false) { return; } for (let d = 0; d < 4; d++) { if ([c, offset(c, 1, 0, d), offset(c, 0, 1, d), offset(c, 1, 1, d)].every(cc => nlist.includes(cc) || l.includes(cc))) { no2x2 = false; return; } } }); if (no2x2 === false) { return false; } // no same shape let cshape = getShape(l); nlist = nlist.map(c => { let clist = Array.from(c.room.clist).filter(c => isBlack(c)); return getShape(clist); }); return !nlist.includes(cshape); }); cand.push(...list); if (cand.length > CANDMAX) { break; } } if (cand.length > CANDMAX) { return; } for (let j = 0; j < clist.length; j++) { if (cand.every(l => l.includes(clist[j]))) { add_black(clist[j]); } if (cand.every(l => !l.includes(clist[j]))) { add_dot(clist[j]); } } }); } 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 = cellList.filter(c => isBlack(c)).length; let emptycnt = cellList.filter(c => !isBlack(c) && c.qsub !== CQSUB.dot).length; if (blackcnt === 0 && emptycnt === 1) { cellList.forEach(c => add_black(c)); } if (blackcnt === 1) { cellList.forEach(c => add_green(c)); } } forEachCell(cell => { for (let d = 0; d < 4; d++) { let fn = function (list) { if (!list.some(c => c.isnull) && list.filter(c => isBlack(c)).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 => !isBlack(c)); 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 => isBlack(c))) { 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]); } } if (offset(cell, 1.5, 0, d).qnum === 1 && offset(cell, 3.5, 0, d).qnum === 1) { add_green(cell); } if (offset(cell, 1.5, 0, d).qnum === 1 && offset(cell, -1.5, 0, d).qnum === 1) { add_green(cell); } } }); } function ShakashakaAssist() { let isEmpty = c => !c.isnull && !isClue(c); let add_tri = function (c, ndir) { // 0 = ◣, 1 = ◢, 2 = ◥, 3 = ◤ if (c === undefined || c.isnull || isClue(c) || isDot(c) || c.qans !== CQANS.none) { return; } if (step && flg) { return; } flg = true; ndir = (ndir % 4 + 4) % 4; c.setQans(ndir + 2); c.draw(); }; // see https://scrapbox.io/wandsbox/%E3%82%B7%E3%83%A3%E3%82%AB%E3%82%B7%E3%83%A3%E3%82%AB%E3%81%AE%E6%A0%BC%E5%AD%90%E7%82%B9%E3%81%AB%E7%9D%80%E7%9B%AE%E3%81%99%E3%82%8B // in this link the cross is just black in the upper link https://scrapbox.io/wandsbox/%E3%82%B7%E3%83%A3%E3%82%AB%E3%82%B7%E3%83%A3%E3%82%AB%E8%A3%9C%E5%8A%A9%E8%A8%98%E5%8F%B7%C3%97%E3%81%AE%E6%8F%90%E6%A1%88 let isCrEmpty = cr => !cr.isnull && cr.qsub === 0; let isCrBlack = cr => !cr.isnull && cr.qsub === 2; let isCrWhite = cr => !cr.isnull && cr.qsub === 1; let isDiffCr = (cr1, cr2) => isCrBlack(cr1) && isCrWhite(cr2) || isCrWhite(cr1) && isCrBlack(cr2); let add_crblack = function (cr) { if (cr === undefined || cr === null || cr.isnull || !isCrEmpty(cr)) { return; } flg2 = true; cr.setQsub(2); cr.draw(); } let add_crwhite = function (cr) { if (cr === undefined || cr === null || cr.isnull || !isCrEmpty(cr)) { return; } flg2 = true; cr.setQsub(1); cr.draw(); } if (board.border.length === 0) { // re-add border board.hasborder = 1; board.initGroup("border", board.rows, board.cols); board.setposAll(); board.rebuildInfo(); } forEachCell(cell => { let adjcross = [[+.5, -.5], [-.5, -.5], [-.5, +.5], [+.5, +.5]].map(([x, y]) => offset(cell, x, y)); // cell to cross if (cell.qnum !== CQNUM.none) { adjcross.forEach(cr => add_crblack(cr)); } if (cell.qans !== CQANS.none) { // 2 = ◣, 3 = ◢, 4 = ◥, 5 = ◤ add_crwhite(adjcross[cell.qans - 2]); adjcross.forEach(cr => add_crblack(cr)); } if (isDot(cell)) { for (let d = 0; d < 4; d++) { if (isCrBlack(offset(cell, -.5, -.5, d))) { add_crblack(offset(cell, .5, .5, d)); } if (isCrWhite(offset(cell, -.5, -.5, d))) { add_crwhite(offset(cell, .5, .5, d)); } } } // number clue if (isNum(cell)) { NShadeInClist({ isShaded: c => { if (c.qans !== CQANS.none) { return true; } if (c.isnull || isClue(c)) { return false; } let d = [0, 1, 2, 3].find(d => offset(c, 1, 0, d) === cell); if (isCrWhite(offset(c, -.5, -.5, d)) || isCrWhite(offset(c, -.5, +.5, d)) || isLink(offset(c, -.5, 0, d))) { return true; } if ([[0, 0], [0, 1], [1, 1]].every(([x, y]) => !isClue(offset(c, x, y, d))) && [-.5, +.5, 1.5].every(x => isCrBlack(offset(c, x, 1.5, d)))) { return true; } if ([[0, 0], [0, 1], [1, 1]].every(([x, y]) => !isClue(offset(c, x, -y, d))) && [-.5, +.5, 1.5].every(x => isCrBlack(offset(c, x, -1.5, d)))) { return true; } return false; }, isUnshaded: c => isDot(c) || isClue(c), add_shaded: c => { let d = [0, 1, 2, 3].find(d => offset(c, -1, 0, d) === cell); if (isCrBlack(offset(c, .5, -.5, d))) { add_crwhite(offset(c, .5, +.5, d)); } if (isCrBlack(offset(c, .5, +.5, d))) { add_crwhite(offset(c, .5, -.5, d)); } add_link(offset(c, .5, 0, d)); }, add_unshaded: add_dot, clist: adjlist(cell.adjacent), n: cell.qnum, }); } // cross to cell if (adjcross.every(cr => isCrBlack(cr))) { add_dot(cell); } if (adjcross.filter(cr => isCrWhite(cr)).length === 2) { add_dot(cell); } if (adjcross.filter(cr => isCrWhite(cr)).length === 1 && adjcross.filter(cr => isCrBlack(cr)).length === 3) { add_tri(cell, [0, 1, 2, 3].find(n => isCrWhite(adjcross[n]))); } // pattern for (let d = 0; d < 4; d++) { // 1 1 if (cell.qnum === 1 && offset(cell, 2, 0, d).qnum === 1 && isEmpty(offset(cell, 1, 0, d)) && isEmpty(offset(cell, 1, -1, d)) && isEmpty(offset(cell, 1, +1, d)) && isEmpty(offset(cell, 0, -1, d)) && isEmpty(offset(cell, 0, +1, d)) && isEmpty(offset(cell, 2, -1, d)) && isEmpty(offset(cell, 2, +1, d))) { add_dot(offset(cell, -1, 0, d)); add_dot(offset(cell, 1, 0, d)); add_dot(offset(cell, 3, 0, d)); add_crblack(offset(cell, -.5, -2, d)); add_crblack(offset(cell, -.5, +2, d)); add_crblack(offset(cell, 2.5, -2, d)); add_crblack(offset(cell, 2.5, +2, d)); } // black cross & 1 if (cell.qnum === 1 && isCrBlack(offset(cell, -1.5, -1.5, d)) && !isClue(offset(cell, -1, -1, d)) && !isClue(offset(cell, -1, 0, d)) && !isClue(offset(cell, 0, -1, d))) { add_dot(offset(cell, 1, 0, d)); add_dot(offset(cell, 0, 1, d)); } // ××○ ××○ // × ×○ -> ×○×○ // ×× ×× if ([[0, 0], [1, 0], [0, 1], [1, 1]].every(([x, y]) => !isClue(offset(cell, x, y, d))) && [[0, 0], [1, 0], [0, 1], [2, 1], [0, 2], [1, 2]].every(([x, y]) => isCrBlack(offset(cell, x - .5, y - .5, d))) && isCrWhite(offset(cell, 2.5, .5, d)) && (isCrWhite(offset(cell, 1.5, -.5, d)) || isCrWhite(offset(cell, 1.5, 1.5, d)))) { add_crwhite(offset(cell, .5, .5, d)); } // ×× ××× // ×× ○ -> ×××○ // ×× ××× if ([[0, 0], [1, 0], [0, 1], [1, 1]].every(([x, y]) => !isClue(offset(cell, x, y, d))) && [[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2]].every(([x, y]) => isCrBlack(offset(cell, x - .5, y - .5, d))) && isCrWhite(offset(cell, 2.5, .5, d))) { add_crblack(offset(cell, 1.5, -.5, d)); add_crblack(offset(cell, 1.5, 1.5, d)); } // ××× ××× // ×× -> ×× // ×× ×× × // ×× ×× if ([...adj8list(cell), cell].every(c => !isClue(c)) && [[1, 0], [2, 0], [3, 0], [0, 1], [1, 1], [0, 2], [1, 2], [0, 3], [1, 3]].every(([x, y]) => isCrBlack(offset(cell, x - 1.5, y - 1.5, d)))) { add_crblack(offset(cell, 1.5, .5, d)); } if ([...adj8list(cell), cell].every(c => !isClue(c)) && [[1, 0], [2, 0], [3, 0], [0, 1], [1, 1], [0, 2], [1, 2], [0, 3], [1, 3]].every(([x, y]) => isCrBlack(offset(cell, y - 1.5, x - 1.5, d)))) { add_crblack(offset(cell, .5, 1.5, d)); } } }); forEachCross(cross => { let adjcross = [[-1, 0], [0, -1], [1, 0], [0, 1]].map(([x, y]) => offset(cross, x, y)); // white is not adjacent if (isCrWhite(cross)) { adjcross.forEach(cr => add_crblack(cr)); } if (isEdge(cross)) { add_crblack(cross); return; } // 3 white & 1 black is forbidden if (adjcross.filter(cr => isCrWhite(cr)).length === 3) { adjcross.forEach(cr => add_crwhite(cr)); } if (adjcross.filter(cr => isCrWhite(cr)).length === 2 && adjcross.filter(cr => isCrBlack(cr)).length === 1) { adjcross.forEach(cr => add_crblack(cr)); } for (let d = 0; d < 4; d++) { let cl = [[+.5, -.5], [-.5, -.5], [-.5, +.5], [+.5, +.5]].map(([x, y]) => offset(cross, x, y)); let clqn = cl.filter(c => c.qnum !== CQNUM.none).length; // 3*3 black but 1 white at corner is forbidden let crl = [[-1, -1], [-1, 0], [0, -1]].map(([x, y]) => offset(cross, x, y, d)); if (clqn === 0 && crl.every(cr => isCrBlack(cr)) && (isCrBlack(cross) || isDiffCr(offset(cross, -1, 1, d), offset(cross, 0, 2, d)) || isDiffCr(offset(cross, 1, -1, d), offset(cross, 2, 0, d)) || isCrBlack(offset(cross, -1, 1, d)) && isCrWhite(offset(cross, 2, 2, d)) && isCrWhite(offset(cross, 1, 3, d)) || isCrBlack(offset(cross, 1, -1, d)) && isCrWhite(offset(cross, 2, 2, d)) && isCrWhite(offset(cross, 3, 1, d))) && (isCrBlack(offset(cross, -1, 1, d)) || isCrBlack(cross) && isCrWhite(offset(cross, 2, 2, d)) && isCrWhite(offset(cross, 1, 3, d))) && (isCrBlack(offset(cross, 1, -1, d)) || isCrBlack(cross) && isCrWhite(offset(cross, 2, 2, d)) && isCrWhite(offset(cross, 3, 1, d)))) { add_crblack(offset(cross, 1, 1, d)); } crl = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [1, -1]].map(([x, y]) => offset(cross, x, y, d)); if (clqn === 0 && crl.every(cr => isCrBlack(cr)) && (isCrWhite(offset(cross, 0, 2, d)) || isCrWhite(offset(cross, 2, 0, d)))) { add_crblack(offset(cross, 1, 1, d)); } crl = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [1, -1], [1, 0], [1, 1]].map(([x, y]) => offset(cross, x, y, d)); if (clqn === 0 && crl.filter(cr => isCrWhite(cr)).length === 1 && crl.filter(cr => isCrBlack(cr)).length === 7 && [0, 2, 6, 8].some(n => isCrWhite(crl[n]))) { crl.forEach(cr => add_crwhite(cr)); } crl = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [1, -1]].map(([x, y]) => offset(cross, x, y, d)); if (clqn === 0 && crl.filter(cr => isCrWhite(cr)).length === 1 && crl.filter(cr => isCrBlack(cr)).length === 6 && [0, 2, 6].some(n => isCrWhite(crl[n]))) { add_link(offset(cross, 1, .5, d)); } crl = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [1, -1], [1, 0]].map(([x, y]) => offset(cross, x, y, d)); if (clqn === 0 && crl.filter(cr => isCrWhite(cr)).length === 1 && crl.filter(cr => isCrBlack(cr)).length === 6 && [0, 2, 5].some(n => isCrWhite(crl[n]))) { add_link(offset(cross, .5, 1, d)); } // 3*3 black but there is one black cell crl = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [1, -1], [1, 0], [1, 1]].map(([x, y]) => offset(cross, x, y, d)); if (clqn === 1 && crl.filter(cr => isCrBlack(cr)).length === 8) { crl.forEach(cr => add_crwhite(cr)); } if (isCrWhite(offset(cross, 1, -1, d)) && isCrWhite(offset(cross, 2, 0, d)) && isDiffCr((offset(cross, -1, 1, d)), offset(cross, 0, 2, d))) { add_crblack(cross); add_crblack(offset(cross, 1, 1, d)); } crl = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [1, -1]].map(([x, y]) => offset(cross, x, y, d)); if (clqn === 1 && crl.every(cr => isCrBlack(cr))) { add_link(offset(cross, 1, .5, d)); } crl = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [1, -1], [1, 0]].map(([x, y]) => offset(cross, x, y, d)); if (clqn === 1 && crl.every(cr => isCrBlack(cr))) { add_link(offset(cross, .5, 1, d)); } if (isCrBlack(cross) && isLink(offset(cross, .5, 0, d))) { add_crwhite(offset(cross, 1, 0, d)); } if (isLink(offset(cross, .5, 0, d)) && isLink(offset(cross, .5, 1, d))) { add_link(offset(cross, 0, .5, d)); add_link(offset(cross, 1, .5, d)); } if (isLink(offset(cross, 1.5, 0, d)) && isLink(offset(cross, 1.5, 1, d)) && isCrBlack(offset(cross, 1, -1, d))) { add_crblack(cross); } if (isLink(offset(cross, 1.5, 0, d)) && isLink(offset(cross, 1.5, -1, d)) && isCrBlack(offset(cross, 1, 1, d))) { add_crblack(cross); } if (isNum(offset(cross, 1.5, 1.5, d)) && isCrBlack(offset(cross, 2, 0, d)) && isCrBlack(offset(cross, 0, 2, d)) && [[offset(cross, 3, 1, d), offset(cross, 3, 2, d)], [offset(cross, 1, 3, d), offset(cross, 2, 3, d)]].filter(l => l.some(cr => !cr.isnull && !isCrBlack(cr))).length < offset(cross, 1.5, 1.5, d).qnum) { add_crblack(cross); } if ((isLink(offset(cross, .5, -1, d)) || isLink(offset(cross, 1, -.5, d))) && (isLink(offset(cross, .5, +1, d)) || isLink(offset(cross, 1, +.5, d))) && isCrBlack(offset(cross, 2, 0, d))) { add_crblack(cross); } if ((isLink(offset(cross, .5, -1, d)) || isLink(offset(cross, 1, -.5, d))) && (isLink(offset(cross, 2, -.5, d)) || isLink(offset(cross, 1.5, 0, d))) && isCrBlack(offset(cross, 1, 1, d))) { add_crblack(cross); } if ((isLink(offset(cross, .5, +1, d)) || isLink(offset(cross, 1, +.5, d))) && (isLink(offset(cross, 2, +.5, d)) || isLink(offset(cross, 1.5, 0, d))) && isCrBlack(offset(cross, 1, -1, d))) { add_crblack(cross); } } }); } function AkichiwakeAssist() { GreenConnected(); BlackNotAdjacent(); NoFacingDoor(); forEachRoom(room => { let qnum = room.top.qnum; let clist = Array.from(room.clist); if (qnum !== CQNUM.none && qnum !== CQNUM.quesmark) { clist.forEach(sc => { if (isBlack(sc)) { return; } let l = []; let dfs = function (c) { if (l.includes(c) || c.room !== room || c !== sc && !isGreen(c)) { return; } l.push(c); adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => !isBound(nb) ? dfs(nc) : undefined); }; dfs(sc); if (l.length === qnum && isGreen(sc)) { l.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!nc.isnull && !isBound(nb) && nc.room === room) { add_black(nc); } })); } if (l.length > qnum && !isGreen(sc)) { add_black(sc); } if (l.length === qnum && !isGreen(sc)) { let nl = []; l.forEach(c => adjlist(c.adjborder, c.adjacent).forEach(([nb, nc]) => { if (!nc.isnull && !isBound(nb) && nc.room === room && !l.includes(nc) && !nl.includes(nc)) { nl.push(nc); } })); if (nl.some(c => adjlist(c.adjacent).some(nc => nl.includes(nc)))) { add_black(sc); } } }); if (qnum === 0) { add_black(clist[0]); } if (qnum === clist.filter(c => !isBlack(c)).length) { clist.forEach(c => add_green(c)); } } }); } function AyeheyaAssist() { forEachRoom(room => { 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 d = 0; d < 2; d++) { if ([offset(c, 0, 1, d), offset(c, 0, -1, d)].every(c => c.isnull || isBlack(c)) && [offset(c, 1, 0, d), offset(c, -1, 0, d)].every(c => c.room === room)) { add_green(offset(c, 1, 0, d)); add_green(offset(c, -1, 0, d)); } } } if (rows % 2 === 0 && cols % 2 === 0) { for (let d = 0; d < 4; d++) { let c = offset(board.getobj(tx / 2, ty / 2), .5, .5, d); if ([offset(c, 1, 0, d), offset(c, 0, 1, d)].every(c => c.isnull || isBlack(c)) && [offset(c, -1, 0, d), offset(c, 0, -1, d)].every(c => c.room === room)) { add_green(offset(c, -1, 0, d)); add_green(offset(c, 0, -1, d)); } } } Array.from(room.clist).forEach(cell => { if (isGreen(cell)) { add_green(board.getc(tx - cell.bx, ty - cell.by)); } if (isBlack(cell)) { add_black(board.getc(tx - cell.bx, ty - cell.by)); } }); }); HeyawakeAssist(true); } function HeyablockAssist() { GreenConnected(); BlackNotAdjacent_OverBorder(); NoFacingDoor(); BlackConnected_InRegion(true); forEachRoom(room => NShadeInClist({ n: room.top.qnum, clist: Array.from(room.clist), AtLeastOne: room.top.qnum === CQNUM.none })); } function HeyawakeAssist(isSym = false) { if (document.querySelector('#penaltyText') === null) { // penalty information let penaltyText = []; { let penalty = board.cols * board.rows + 1 + board.cols % 2 + board.rows % 2; let shadecnt = Array.from(board.roommgr.components).reduce((tot, room) => room.top.qnum >= 0 ? tot + room.top.qnum : tot, 0); if ((penalty - shadecnt * 3) <= 5) { penaltyText.push( "Whole Board: " + "Penalty: " + penalty + "; " + "Max Shade: " + (Math.floor(penalty / 3)) + "; " + "Min Shade: " + shadecnt + "; " + "Spare Penalty: " + (penalty - shadecnt * 3)); } } forEachRoom(room => { if (room.top.qnum < 8) { return; } let clist = Array.from(room.clist); let penalty = new Set(clist.flatMap(c => [0, 1, 2, 3].map(d => offset(c, .5, .5, d)).filter(cr => !isEdge(cr)))).size; let fn = f => Array.from(board.cell).filter(c => f(c)).map(c => c.room === room ? '#' : '_').join('').replaceAll('##', '$').replaceAll('_', '').length; penalty += fn(c => c.bx === board.minbx + 1); penalty += fn(c => c.by === board.minby + 1); penalty += fn(c => c.bx === board.maxbx - 1); penalty += fn(c => c.by === board.maxby - 1); let shadecnt = room.top.qnum; if ((penalty - shadecnt * 3) <= 5) { penaltyText.push( "No." + board.roommgr.components.indexOf(room) + " Room: " + "Penalty: " + penalty + "; " + "Shade: " + shadecnt + "; " + "Spare Penalty: " + (penalty - shadecnt * 3)); } }); document.querySelector('#quesboard').insertAdjacentHTML('beforebegin', '<div id="penaltyText">' + penaltyText.join('<br>') + '</div>'); } GreenConnected(); BlackNotAdjacent(); NoFacingDoor(); const MAXSIT = 200000; const MAXAREA = 50; forEachCell(cell => { for (let d = 0; d < 4; d++) { if (isBound(offset(cell, 1.5, 0, d)) && isBound(offset(cell, 2.5, 0, d)) && (offset(cell, 1, +1, d).isnull || isBound(offset(cell, .5, +1, d)) && isBound(offset(cell, 1.5, +1, d))) && (offset(cell, 1, -1, d).isnull || isBound(offset(cell, .5, -1, d)) && isBound(offset(cell, 1.5, -1, d)))) { if (isBlack(cell)) { add_black(offset(cell, 3, 0, d)); } if (isGreen(offset(cell, 3, 0, d))) { add_green(cell); } } if (isBlack(offset(cell, 2, 0, d)) && (offset(cell, 1, -1, d).isnull || isBlack(offset(cell, 1, -1, d)) || isBound(offset(cell, .5, -1, d)) && isBound(offset(cell, 1.5, -1, d))) && (offset(cell, 1, +1, d).isnull || isBlack(offset(cell, 1, +1, d)) || isBound(offset(cell, .5, +1, d)) && isBound(offset(cell, 1.5, +1, d)))) { add_green(cell); } } }); forEachRoom(room => { let qnum = room.top.qnum; if (qnum === CQNUM.none || qnum === CQNUM.quesmark) { return; } let clist = Array.from(room.clist).sort((a, b) => a.id - b.id); // (4^n-1)/3 black cells in (2^n-1)^2 square if (Math.sqrt(clist.length) % 1 === 0) { let n = Math.sqrt(clist.length); let tc = room.top; if ((n & (n + 1)) === 0 && qnum * 3 === (n + 1) ** 2 - 1 && clist.every(c => c.bx >= tc.bx && c.bx < tc.bx + n * 2 && c.by >= tc.by && c.by < tc.by + n * 2)) { clist.forEach(c => { let i = (c.bx - tc.bx) / 2 + 1, j = (c.by - tc.by) / 2 + 1; if ((~i & (i - 1)) === (~j & (j - 1))) { add_black(c); } else { add_green(c); } }); } } let surlist = []; let sitcnt = 0; let cst = new Map(); let apl = new Map(); clist.forEach(cell => { cst.set(cell, (isBlack(cell) ? "BLK" : (isGreen(cell) ? "GRN" : "UNK"))); apl.set(cell, (isBlack(cell) ? "BLK" : (isGreen(cell) ? "GRN" : "UNK"))); }); if (qnum === clist.filter(c => isBlack(c)).length) { clist.forEach(c => add_green(c)); return; } // randomly chosen approximate formula if (clist.filter(c => c.qans === CQANS.none && c.qsub === CQSUB.none).length > MAXAREA && (qnum - clist.filter(c => isBlack(c)).length + 1) < clist.filter(c => c.qans === CQANS.none && c.qsub === CQSUB.none).length) { return; } if ((qnum - clist.filter(c => isBlack(c)).length) * 2 + 5 < clist.filter(c => c.qans === CQANS.none && c.qsub === CQSUB.none).length) { return; } clist.forEach(c => { adjlist(c.adjacent).forEach(c => { if (c.isnull || c.room === room || surlist.includes(c)) { return; } if (isGreen(c) || isBlack(c)) { return; } surlist.push(c); apl.set(c, "GRN"); }); }); let dfs = function (i, blkcnt) { if (sitcnt > MAXSIT) { return; } if (i === clist.length) { if (blkcnt !== qnum) { return; } let templist = []; let templist2 = []; if (clist.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 (isBlack(c)) { 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 < clist.length) { return true; } return false; })) { return; }; clist.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(clist[i]) !== "UNK") { dfs(i + 1, blkcnt); return; } sitcnt++; let templist = [offset(clist[i], -1, 0), offset(clist[i], 0, -1), offset(clist[i], 1, 0), offset(clist[i], 0, 1)]; if (blkcnt < qnum && !templist.some(c => isBlack(c) || cst.has(c) && cst.get(c) === "BLK") && (!isSym || cst.get(clist[clist.length - 1 - i]) !== "GRN")) { cst.set(clist[i], "BLK"); dfs(i + 1, blkcnt + 1); cst.set(clist[i], "UNK"); } if (!isSym || cst.get(clist[clist.length - 1 - i]) !== "BLK") { cst.set(clist[i], "GRN"); dfs(i + 1, blkcnt); } cst.set(clist[i], "UNK"); }; dfs(0, clist.filter(c => isBlack(c)).length); if (sitcnt > MAXSIT) { return; } clist.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); }; forEachCell(cell => { 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 let emptycellList = []; if (cell.qnum === CQNUM.none && !cell.qlight) { if (cell.qsub !== CQSUB.dot) { emptycellList.push(cell); } for (let d = 0; d < 4; d++) { let pcell = offset(cell, 1, 0, d); while (!pcell.isnull && pcell.qnum === CQNUM.none) { emptycellList.push(pcell); pcell = offset(pcell, 1, 0, d); } } emptycellList = emptycellList.filter(c => c.qsub !== CQSUB.dot); if (emptycellList.length === 1) { add_light(emptycellList[0]); } } // only two cells can lit up this cell if (cell.qsub === CQSUB.dot && !cell.qlight && emptycellList.length === 2) { let [ec1, ec2] = emptycellList; if (ec1.bx !== ec2.bx && ec1.by !== ec2.by) { if (ec1.bx === cell.bx) { [ec1, ec2] = [ec2, ec1]; } let oc = board.getc(ec1.bx, ec2.by); let f = true; for (let i = cell.bx; i !== ec1.bx; i += (ec1.bx > cell.bx ? 1 : -1)) { f &= board.getc(i, ec2.by).qnum === CQNUM.none; } for (let i = cell.by; i !== ec2.by; i += (ec2.by > cell.by ? 1 : -1)) { f &= board.getc(ec1.bx, i).qnum === CQNUM.none; } if (f && oc.qnum === CQNUM.none) { add_dot(oc); } } } fourside(c => { if (!c.isnull && c.qnum === CQNUM.none && c.qsub !== CQSUB.dot && c.qans !== CQANS.light) { emptycnt++; } lightcnt += (c.qans === CQANS.light); }, cell.adjacent); if (cell.qnum >= 0) { // finished clue if (cell.qnum === lightcnt) { fourside(add_dot, cell.adjacent); } // finish clue if (cell.qnum === emptycnt + lightcnt) { fourside(add_light, cell.adjacent); } // 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)); } } } } for (let d = 0; d < 4; d++) { // // 22 => ●22● // if (cell.qnum === 2 && offset(cell, 1, 0, d).qnum === 2) { add_light(offset(cell, -1, 0, d)); add_light(offset(cell, 2, 0, d)); } // // 21· => ●21· // if (cell.qnum === 2 && offset(cell, 1, 0, d).qnum === 1 && isNotLight(offset(cell, 2, 0, d))) { add_light(offset(cell, -1, 0, d)); } // ● // 3 => ●3 // 1 1· // · if (cell.qnum === 3 && 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 => 2 // 1 1 // if (cell.qnum === 2 && offset(cell, 1, 1, d).qnum === 1) { add_dot(offset(cell, -1, -1, d)); } // ● // ·2 => ·2 // 1 1· // · if (cell.qnum === 2 && offset(cell, 1, 1, d).qnum === 1 && (isNotLight(offset(cell, -1, 0, d)) || isNotLight(offset(cell, 0, -1, d)))) { 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)); } // · // 1 => ·1 // 1· 1· // · · if (cell.qnum === 1 && 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, }); let isBlack = c => !c.isnull && c.qnum === CQNUM.bcir; let isWhite = c => !c.isnull && c.qnum === CQNUM.wcir; let isPathable = b => !b.isnull && !isCross(b); forEachCell(cell => { for (let d = 0; d < 4; d++) { if (isWhite(cell) && offset(cell, .5, .5, d).qsub !== CRQSUB.none) { add_inout(offset(cell, -.5, -.5, d), offset(cell, .5, .5, d).qsub ^ 1); } if (isBlack(cell) && isWhite(offset(cell, -1, -1, d)) && isWhite(offset(cell, 1, 1, d)) && offset(cell, .5, .5, d).qsub !== CRQSUB.none) { add_inout(offset(cell, -.5, -.5, d), offset(cell, .5, .5, d).qsub); } if (isBlack(cell) && isWhite(offset(cell, -1, -1, d)) && isWhite(offset(cell, 1, 1, d)) && offset(cell, -.5, .5, d).qsub !== CRQSUB.none) { add_inout(offset(cell, .5, -.5, d), offset(cell, -.5, .5, d).qsub ^ 1); } // +×+ +×+ // ● ● -> ● ● // + + +×+ if (isBlack(offset(cell, -1, 0, d)) && isBlack(offset(cell, 1, 0, d)) && !isPathable(offset(cell, 0, -.5, d))) { add_cross(offset(cell, 0, .5, d)); } // + + +×+ // ━○ -> ━○━ // + + +×+ if (isWhite(cell) && (offset(cell, -.5, 0, d).line || !isPathable(offset(cell, 0, -.5, d)))) { add_line(offset(cell, -.5, 0, d)); add_line(offset(cell, +.5, 0, d)); add_cross(offset(cell, 0, -.5, d)); add_cross(offset(cell, 0, +.5, d)); } // + + + + + + + + // ━━━○━╸ -> ━━━○━╸× // + + + + + + + + if (isWhite(cell) && offset(cell, -.5, 0, d).line && offset(cell, -1.5, 0, d).line) { add_cross(offset(cell, 1.5, 0, d)); } // + + + + + +┃+ + // ━╸ ○ ○ -> ━╸×○×○ // + + + + + +┃+ + if (isWhite(cell) && (offset(cell, -1.5, 0, d).line || isWhite(offset(cell, -1, 0, d)) || ((isntLine(offset(cell, -1, -.5, d)) || isBlack(offset(cell, -1, -1, d))) && (isntLine(offset(cell, -1, +.5, d)) || isBlack(offset(cell, -1, +1, d))))) && (offset(cell, +1.5, 0, d).line || isWhite(offset(cell, +1, 0, d)) || ((isntLine(offset(cell, +1, -.5, d)) || isBlack(offset(cell, +1, -1, d))) && (isntLine(offset(cell, +1, +.5, d)) || isBlack(offset(cell, +1, +1, d)))))) { add_cross(offset(cell, -.5, 0, d)); add_cross(offset(cell, +.5, 0, d)); add_line(offset(cell, 0, -.5, d)); add_line(offset(cell, 0, +.5, d)); } // + + + : + + + : + +┃+ : + + + : + + + + + + // ━● : ●× : ● ╹ : ● ● : ● × -> ━●× // + + + ; + + + ; + + + ; + + + ; + + + + + + if (isBlack(cell) && (offset(cell, -.5, 0, d).line || !isPathable(offset(cell, .5, 0, d)) || offset(cell, 1, -.5, d).line || offset(cell, 1, .5, d).line || isBlack(offset(cell, 1, 0, d)) || !isPathable(offset(cell, 1.5, 0, d)))) { add_cross(offset(cell, .5, 0, d)); add_line(offset(cell, -.5, 0, d)); } // + + + + + + // ●━╸ -> ●━━━ // + + + + + + if (isBlack(cell) && offset(cell, .5, 0, d).line) { add_line(offset(cell, 1.5, 0, d)); } // + + + + + + + + + + // ● ○ ○ -> ━● ○ ○ // + + + + + + + + + + if (isBlack(cell) && isWhite(offset(cell, 2, 0, d)) && isWhite(offset(cell, 3, 0, d))) { add_line(offset(cell, -.5, 0, d)); } // + + + + + + + + // ○ ○ ○ ○ // + + + + -> + + + + // ● ● // + + + + + +┃+ + if (isBlack(cell) && isWhite(offset(cell, -1, -1, d)) && isWhite(offset(cell, 1, -1, d))) { add_line(offset(cell, 0, .5, d)); } // connectivity and black if (isBlack(cell) && cell.path !== null && cell.path === offset(cell, 2, 0, d).path && offset(cell, 1, 0, d).path === null && board.linegraph.components.length > 1) { add_line(offset(cell, -.5, 0, d)); } // connectivity and white if (isWhite(cell) && offset(cell, -1, 0, d).path !== null && offset(cell, -1, 0, d).path === offset(cell, +1, 0, d).path && board.linegraph.components.length > 1) { add_line(offset(cell, 0, +.5, d)); add_line(offset(cell, 0, -.5, d)); } } }); } function SimpleloopAssist() { SingleLoopInCell({ isPassable: c => c.ques !== CQUES.wall, isPass: c => c.ques !== CQUES.wall, }); } function KoburinAssist() { 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 && !isBlack(c); let isEmpty = c => !c.isnull && c.qnum === CQNUM.none && !isBlack(c) && c.qsub !== CQSUB.dot && c.lcnt === 0; const koburin_minesweeper = ui.puzzle.config.list.koburin_minesweeper.val; forEachCell(cell => { // check clue if (cell.qnum >= 0) { let list = adjlist(cell.adjacent); if (koburin_minesweeper) { list = adj8list(cell); } if (list.filter(c => isBlack(c)).length === cell.qnum) { list.forEach(c => add_dot(c)); } if (list.filter(c => !c.isnull && c.qnum === CQNUM.none && c.qsub !== CQSUB.dot).length === cell.qnum) { list.forEach(c => add_black(c, true)); } } // add cross if (cell.qnum !== CQNUM.none && !koburin_minesweeper) { fourside(add_cross, cell.adjborder); for (let d = 0; d < 4; d++) { // · · // 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)); } // █ // 3 -> █3 // █ █ if (cell.qnum === 3 && offset(cell, 1, 1, d).qnum === CQNUM.none && (isntLine(offset(cell, 1.5, 1, d)) || (c => c.isnull || isBlack(c) || c.qnum !== CQNUM.none)(offset(cell, 2, 1, d)))) { add_black(offset(cell, -1, 0, d), true); add_black(offset(cell, 0, -1, d), true); } if (cell.qnum === 3 && offset(cell, 1, -1, d).qnum === CQNUM.none && (isntLine(offset(cell, 1.5, -1, d)) || (c => c.isnull || isBlack(c) || c.qnum !== CQNUM.none)(offset(cell, 2, -1, d)))) { add_black(offset(cell, -1, 0, d), true); add_black(offset(cell, 0, 1, d), true); } // · // 2 -> 2 // █ █ if (cell.qnum === 2 && offset(cell, 1, 1, d).qnum === CQNUM.none && (isntLine(offset(cell, 1.5, 1, d)) || (c => c.isnull || isBlack(c) || c.qnum !== CQNUM.none)(offset(cell, 2, 1, d)))) { add_dot(offset(cell, -1, -1, d)); } if (cell.qnum === 2 && offset(cell, 1, -1, d).qnum === CQNUM.none && (isntLine(offset(cell, 1.5, -1, d)) || (c => c.isnull || isBlack(c) || c.qnum !== CQNUM.none)(offset(cell, 2, -1, d)))) { add_dot(offset(cell, -1, 1, d)); } } return; } if (isClue(cell)) { return; } // add dot around black if (isBlack(cell)) { fourside(add_cross, cell.adjborder); fourside(add_dot, cell.adjacent); return; } let emptycnt = 0; let linecnt = 0; fourside((b, c) => { if (isPathable(c) && !isCross(b)) { emptycnt++; } linecnt += b.line; }, cell.adjborder, cell.adjacent); // no branch if (linecnt === 2) { fourside(add_cross, cell.adjborder); } // no deadend if (emptycnt <= 1) { add_black(cell, true); fourside(add_cross, cell.adjborder); fourside(add_dot, cell.adjacent); } // 2 degree cell no deadend if (emptycnt === 2) { fourside((b, c) => { if (!isPathable(c) || isCross(b)) { return; } add_dot(c); }, cell.adjborder, cell.adjacent); } }); } function YajisanKazusanAssist() { BlackNotAdjacent(); GreenConnected(); forEachCell(cell => { // check clue if (!isBlack(cell) && cell.qnum >= 0 && cell.qdir !== QDIR.none) { let d = qdirRemap(cell.qdir); let qnum = cell.qnum; let list = []; let dn = 0, lc = null; while (!offset(cell, dn + 1, 0, d).isnull) { dn++; let c = offset(cell, dn, 0, d); if (isBlack(c)) { qnum--; } if (isBlack(c) || isGreen(c)) { continue; } if (lc === null) { lc = c; continue; } if (lc === offset(c, -1, 0, d)) { list.push([lc, c]); lc = null; } else { list.push([lc]); lc = c; } } if (lc !== null) { list.push([lc]); } if (list.length < qnum || qnum < 0) { add_black(cell); } if (qnum === 0) { add_green(offset(cell, 1, 0, d)); } if (list.length === qnum && isGreen(cell)) { list.forEach(p => { if (p.length === 1) { add_black(p[0]); } }); } if (qnum === 0 && isGreen(cell)) { list.forEach(p => p.forEach(c => add_green(c))); } } }); } function RegionalYajilinAssist() { SingleLoopInCell({ isPassable: c => !c.isnull && !isBlack(c), isPass: c => c.qsub === CQSUB.dot, add_notpass: c => add_black(c, true), add_pass: c => add_dot(c, false), }); let isPassable = c => !c.isnull && !isBlack(c); let isEmpty = c => !c.isnull && !isBlack(c) && c.qsub !== CQSUB.dot && c.lcnt === 0; forEachRoom(room => { let n = room.top.qnum; if (n < 0) { return; } let clist = Array.from(room.clist); if (clist.filter(c => !isDot(c) && c.lcnt === 0).length === n) { clist.filter(c => !isDot(c) && c.lcnt === 0).forEach(c => add_black(c)); } if (clist.filter(c => isBlack(c)).length === n) { clist.filter(c => !isBlack(c)).forEach(c => add_dot(c, false)); } }); forEachCell(cell => { // add dot around black if (isBlack(cell)) { fourside(add_cross, cell.adjborder); fourside(c => add_dot(c, false), cell.adjacent); return; } let emptycnt = 0; let linecnt = 0; fourside((nb, nc) => { if (isPassable(nc) && !isCross(nb)) { emptycnt++; } linecnt += nb.line; }, cell.adjborder, cell.adjacent); // no branch if (linecnt === 2) { fourside(add_cross, cell.adjborder); } // no deadend if (emptycnt <= 1) { add_black(cell); fourside(add_cross, cell.adjborder); fourside(c => add_dot(c, false), cell.adjacent); } // 2 degree cell no deadend if (emptycnt === 2) { fourside((nb, nc) => { if (!isPassable(nc) || isCross(nb)) { return; } add_dot(nc, false); }, cell.adjborder, cell.adjacent); } // prevent multiple loops let list = adjlist(cell.adjborder, cell.adjacent); list = list.filter(([b, c]) => isPassable(c) && !isCross(b)); if (list.length === 0 || list[0][1].path !== undefined && list[0][1].path !== null && list.every(([b, c]) => c.path === list[0][1].path) && board.linegraph.components.length > 1) { add_black(cell); } for (let d = 0; d < 4; d++) { if (!isPassable(offset(cell, -1, 0, d))) { continue; } if (!isPassable(offset(cell, 0, -1, d))) { continue; } if (isPassable(offset(cell, -2, 0, d)) && offset(cell, -1.5, 0, d).qsub !== BQSUB.cross) { continue; } if (isPassable(offset(cell, 0, -2, d)) && offset(cell, 0, -1.5, d).qsub !== BQSUB.cross) { continue; } if (!offset(cell, -1, -1.5, d).line && !offset(cell, -1.5, -1, d).line) { continue; } add_dot(cell, false); } }); } function YajilinAssist() { SingleLoopInCell({ isPassable: c => !c.isnull && c.qnum === CQNUM.none && !isBlack(c), isPass: c => c.qsub === CQSUB.dot, add_notpass: c => add_black(c, true), add_pass: add_dot, }); let isPassable = c => !c.isnull && c.qnum === CQNUM.none && !isBlack(c); let isEmpty = c => !c.isnull && c.qnum === CQNUM.none && !isBlack(c) && c.qsub !== CQSUB.dot && c.lcnt === 0; forEachCell(cell => { // check clue if (cell.qnum >= 0 && cell.qdir !== QDIR.none) { let d = qdirRemap(cell.qdir); let qnum = cell.qnum; let list = []; let dn = 0, lc = []; while ((c => !c.isnull && (c.qnum < 0 || c.qdir !== cell.qdir))(offset(cell, dn + 1, 0, d))) { dn++; let c = offset(cell, dn, 0, d); if (isBlack(c)) { qnum--; } if (!isEmpty(c)) { continue; } if (lc.length === 0) { lc = [c]; continue; } if (lc.length === 1 && lc[0] === offset(c, -1, 0, d)) { lc.push(c); } else if ((lc.length === 2 && lc[1] === offset(c, -1, 0, d) && lc[0] === offset(c, -2, 0, d) || lc.length === 1 && lc[0] === offset(c, -2, 0, d) && offset(c, -1, 0, d).qnum === CQNUM.none) && ([[-1, +.5], [-1, -.5]].some(([x, y]) => isntLine(offset(c, x, y, d))) || [[0, -1.5], [+.5, -1]].some(([x, y]) => isntLine(offset(c, x, y, d))) && [[-2, -1.5], [-2.5, -1]].some(([x, y]) => isntLine(offset(c, x, y, d))) && offset(c, 0, -1, d).qnum === CQNUM.none && offset(c, -2, -1, d).qnum === CQNUM.none || [[0, +1.5], [+.5, +1]].some(([x, y]) => isntLine(offset(c, x, y, d))) && [[-2, +1.5], [-2.5, +1]].some(([x, y]) => isntLine(offset(c, x, y, d))) && offset(c, 0, +1, d).qnum === CQNUM.none && offset(c, -2, +1, d).qnum === CQNUM.none)) { lc.push(c); } else { list.push(lc); lc = [c]; } } if (lc.length !== 0) { list.push(lc); } if ((c => c.qnum >= 0 && c.qdir === cell.qdir)(offset(cell, dn + 1, 0, d))) { qnum -= offset(cell, dn + 1, 0, d).qnum; } if (list.length === qnum) { list.forEach(p => { if (p.length === 1) { add_black(p[0]); } if (p.length === 2 && offset(p[0], 1, 0, d) === p[1]) { add_cross(offset(p[0], .5, 0, d)); if (!isClue(offset(p[0], 0, -1, d)) && [[-.5, -1], [0, -1.5]].some(([x, y]) => isntLine(offset(p[0], x, y, d)))) { add_dot(offset(p[0], 1, -1, d)); } if (!isClue(offset(p[0], 0, +1, d)) && [[-.5, +1], [0, +1.5]].some(([x, y]) => isntLine(offset(p[0], x, y, d)))) { add_dot(offset(p[0], 1, +1, d)); } if (!isClue(offset(p[0], 1, -1, d)) && [[1.5, -1], [1, -1.5]].some(([x, y]) => isntLine(offset(p[0], x, y, d)))) { add_dot(offset(p[0], 0, -1, d)); } if (!isClue(offset(p[0], 1, +1, d)) && [[1.5, +1], [1, +1.5]].some(([x, y]) => isntLine(offset(p[0], x, y, d)))) { add_dot(offset(p[0], 0, +1, d)); } } if (p.length === 2 && offset(p[0], 2, 0, d) === p[1]) { if (isntLine(offset(p[0], 1, -.5, d))) { add_line(offset(p[0], 1, +.5, d)); } if (isntLine(offset(p[0], 1, +.5, d))) { add_line(offset(p[0], 1, -.5, d)); } } if (p.length === 3) { if (isntLine(offset(p[0], 1, -.5, d))) { add_dot(offset(p[0], 1, +1, d)); } if (isntLine(offset(p[0], 1, +.5, d))) { add_dot(offset(p[0], 1, -1, d)); } } }); } if (qnum === 0) { list.forEach(p => p.forEach(c => add_dot(c))); } } // add cross if (cell.qnum !== CQNUM.none) { fourside(add_cross, cell.adjborder); return; } // add dot around black if (isBlack(cell)) { fourside(add_cross, cell.adjborder); fourside(add_dot, cell.adjacent); return; } let emptycnt = 0; let linecnt = 0; fourside((nb, nc) => { if (isPassable(nc) && !isCross(nb)) { emptycnt++; } linecnt += nb.line; }, cell.adjborder, cell.adjacent); // no branch if (linecnt === 2) { fourside(add_cross, cell.adjborder); } // no deadend if (emptycnt <= 1) { add_black(cell); fourside(add_cross, cell.adjborder); fourside(add_dot, cell.adjacent); } // 2 degree cell no deadend if (emptycnt === 2) { fourside((nb, nc) => { if (!isPassable(nc) || isCross(nb)) { return; } add_dot(nc); }, cell.adjborder, cell.adjacent); } // prevent multiple loops let list = adjlist(cell.adjborder, cell.adjacent); list = list.filter(([b, c]) => isPassable(c) && !isCross(b)); if (list.length === 0 || list[0][1].path !== undefined && list[0][1].path !== null && list.every(([b, c]) => c.path === list[0][1].path) && board.linegraph.components.length > 1) { add_black(cell); } for (let d = 0; d < 4; d++) { if (isPassable(offset(cell, -1, 0, d)) && isPassable(offset(cell, 0, -1, d)) && (isntLine(offset(cell, -1.5, 0, d)) || isntLine(offset(cell, -1, .5, d))) && (isntLine(offset(cell, 0, -1.5, d)) || isntLine(offset(cell, .5, -1, d))) && (isLine(offset(cell, -1, -1.5, d)) || isLine(offset(cell, -1.5, -1, d)))) { add_dot(cell); } } }); } function VertexSlitherlinkAssist() { SingleLoopInBorder({ useCrossQsub: false }); let isCrEmpty = cr => !cr.isnull && cr.qsub === 0; let isCrCrs = cr => !cr.isnull && cr.qsub === 2; let isCrDot = cr => !cr.isnull && cr.qsub === 1; let add_crcrs = function (cr) { if (cr === undefined || cr === null || cr.isnull || !isCrEmpty(cr)) { return; } flg = true; cr.setQsub(2); cr.draw(); } let add_crdot = function (cr) { if (cr === undefined || cr === null || cr.isnull || !isCrEmpty(cr)) { return; } flg = true; cr.setQsub(1); cr.draw(); } forEachCell(cell => { NShadeInClist({ isShaded: isCrDot, isUnshaded: isCrCrs, add_shaded: add_crdot, add_unshaded: add_crcrs, n: cell.qnum, clist: [0, 1, 2, 3].map(d => offset(cell, .5, .5, d)), }); for (let d = 0; d < 4; d++) { if (cell.qnum === 1 && (isntLine(offset(cell, 1, .5, d)) || isntLine(offset(cell, .5, 1, d)))) { add_crcrs(offset(cell, .5, .5, d)); } if (cell.qnum === 2 && (isntLine(offset(cell, 1, .5, d)) && isntLine(offset(cell, .5, 1, d)))) { add_crcrs(offset(cell, .5, .5, d)); } } }); forEachCross(cross => { let lcnt = cross.lcnt; if (lcnt > 0) { add_crdot(cross); } if (isCrCrs(cross)) { adjlist(cross.adjborder).forEach(b => add_crcrs(b)); } if (adjlist(cross.adjborder).every(b => isntLine(b))) { add_crcrs(cross); } if (isCrDot(cross)) { NShadeInClist({ isShaded: isLine, isUnshaded: isCross, add_shaded: add_line, add_unshaded: add_cross, n: 2, clist: adjlist(cross.adjborder), }); } }); } function LitherslinkAssist() { forEachCross(cross => { let qsub; if (cross.qsub === 0 || JSON.parse(cross.qsub).length === 0) { qsub = []; let list = adjlist(cross.adjborder); for (let i = 1; i < 16; i++) { if ([0b0011, 0b0101, 0b1001, 0b0110, 0b1010, 0b1100].includes(i)) { continue; } let t = []; for (let j = 0; j < 4; j++) { if (i & (1 << j)) { t.push(list[j].id); } } if (!t.some(n => n === null)) { qsub.push(t); } } } else { qsub = JSON.parse(cross.qsub); } qsub = qsub.filter(s => s.every(i => !isCross(board.border[i]))); fourside(b => { if (b.line) { qsub = qsub.filter(s => s.includes(b.id)); } if (qsub.every(s => s.includes(b.id))) { add_line(b); } if (qsub.every(s => !s.includes(b.id))) { add_cross(b); } }, cross.adjborder); cross.setQsub(JSON.stringify(qsub)); }); forEachCell(cell => { NShadeInClist({ isShaded: isLine, isUnshaded: isCross, add_shaded: add_line, add_unshaded: add_cross, n: cell.qnum, clist: adjlist(cell.adjborder), }); // deduce single clue. it's disabled here because it's overpowered. if (cell.qnum >= 0 && false) { let list = [offset(cell, -.5, -.5), offset(cell, .5, -.5), offset(cell, -.5, .5), offset(cell, .5, .5)]; let sum = list.map(cr => JSON.parse(cr.qsub).length).reduce((a, b) => a + b, 0); let comblist = []; JSON.parse(list[0].qsub).forEach(q0 => { if (adjlist(cell.adjborder).filter(b => [].concat(q0).includes(b.id)).length > cell.qnum) { return; } if (2 - adjlist(cell.adjborder).filter(b => [].concat(q0).includes(b.id)).length > 4 - cell.qnum) { return; } JSON.parse(list[1].qsub).forEach(q1 => { if (adjlist(cell.adjborder).filter(b => [].concat(q0, q1).includes(b.id)).length > cell.qnum) { return; } if (3 - adjlist(cell.adjborder).filter(b => [].concat(q0, q1).includes(b.id)).length > 4 - cell.qnum) { return; } if (q0.includes(offset(cell, 0, -.5).id) ^ q1.includes(offset(cell, 0, -.5).id)) { return; } JSON.parse(list[2].qsub).forEach(q2 => { if (q0.includes(offset(cell, -.5, 0).id) ^ q2.includes(offset(cell, -.5, 0).id)) { return; } JSON.parse(list[3].qsub).forEach(q3 => { if (q2.includes(offset(cell, 0, .5).id) ^ q3.includes(offset(cell, 0, .5).id)) { return; } if (q1.includes(offset(cell, .5, 0).id) ^ q3.includes(offset(cell, .5, 0).id)) { return; } if (adjlist(cell.adjborder).filter(b => [].concat(q0, q1, q2, q3).includes(b.id)).length !== cell.qnum) { return; } comblist.push([q0, q1, q2, q3]); }); }); }); }); list.forEach((cr, i) => { cr.setQsub(JSON.stringify(JSON.parse(cr.qsub).filter(s => comblist.some(comb => JSON.stringify(comb[i]) === JSON.stringify(s))))); }); if (list.map(cr => JSON.parse(cr.qsub).length).reduce((a, b) => a + b, 0) < sum) { flg2 = true; } } for (let d = 0; d < 4; d++) { let b1 = offset(cell, 1, .5, d); let b2 = offset(cell, .5, 1, d); if (cell.qnum === 1 && ([b1, b2].every(b => isLine(b)) || [b1, b2].every(b => isntLine(b)))) { add_cross(offset(cell, -.5, 0, d)); add_cross(offset(cell, 0, -.5, d)); } if (cell.qnum === 1 && (isLine(b1) && isntLine(b2) || isLine(b2) && isntLine(b1))) { add_cross(offset(cell, .5, 0, d)); add_cross(offset(cell, 0, .5, d)); } if (cell.qnum === 3 && [b1, b2].every(b => isntLine(b))) { add_line(offset(cell, -.5, 0, d)); add_line(offset(cell, 0, -.5, d)); } if (cell.qnum === 3 && (isLine(b1) && isntLine(b2) || isLine(b2) && isntLine(b1))) { add_line(offset(cell, .5, 0, d)); add_line(offset(cell, 0, .5, d)); } if (cell.qnum === 3 && isLine(offset(cell, -.5, 0, d)) && isLine(offset(cell, 0, -.5, d)) && offset(cell, 1, 1, d).qnum === 3) { add_line(b1); add_line(b2); } if (cell.qnum === 3 && offset(cell, 1, 0, d).qnum === 3) { add_line(offset(cell, .5, 0, d)); } } }); forEachCross(cross => { let lcnt = cross.lcnt; let ccnt = adjlist(cross.adjborder).filter(b => isntLine(b)).length; if (lcnt + ccnt === 3 && [0, 2].includes(lcnt)) { adjlist(cross.adjborder).forEach(b => add_line(b)); } if (lcnt + ccnt === 3 && [0, 2].includes(lcnt + 1)) { adjlist(cross.adjborder).forEach(b => add_cross(b)); } }); let DSU = new Map(); // Disjoint Set Union let DSUfind = function (n) { if (DSU.get(n) !== n) { DSU.set(n, DSUfind(DSU.get(n))); } return DSU.get(n); }; forEachCross(cross => { DSU.set(cross, cross); for (let d = 0; d < 4; d++) { let ncross = offset(cross, 1, 0, d); if (ncross.isnull || !DSU.has(ncross)) { continue; } if (isLine(offset(cross, .5, 0, d)) || [offset(cross, .5, -.5, d), offset(cross, .5, .5, d)].some(c => c.qnum === 3)) { DSU.set(DSUfind(cross), DSUfind(ncross)); } } }); forEachBorder(border => { let [cr1, cr2] = border.sidecross; if (!isLine(border) && cr1.path !== null && cr1.path === cr2.path) { add_cross(border); } if (!isLine(border) && DSUfind(cr1) === DSUfind(cr2) && !border.sidecell.some(c => c.qnum === 3)) { add_cross(border); } }); } function SlitherlinkAssist() { SingleLoopInBorder(); 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_green = function (c) { add_bg_color(c, CQSUB.green); } let add_yellow = function (c) { add_bg_color(c, CQSUB.yellow); } let isYellow = c => c.isnull || c.qsub === CQSUB.yellow; CellConnected({ isShaded: c => isGreen(c) && c.qnum !== 3, isUnshaded: c => isYellow(c) || c.qsub === CQSUB.none && c.qnum === 3, add_shaded: add_green, add_unshaded: add_yellow, isLinked: (c, nb, nc) => isCross(nb), isNotPassable: (c, nb, nc) => nb.line, }); CellConnected({ isShaded: c => isYellow(c) && c.qnum !== 3, isUnshaded: c => isGreen(c) || c.qsub === CQSUB.none && c.qnum === 3, add_shaded: add_yellow, add_unshaded: add_green, isLinked: (c, nb, nc) => isCross(nb), isNotPassable: (c, nb, nc) => nb.line, OutsideAsShaded: true, }); // counting this due to some small loop jokes let twocnt = 0; let threecnt = 0; forEachCell(cell => { twocnt += cell.qnum === 2; threecnt += cell.qnum === 3; }); forEachCell(cell => { let blist = adjlist(cell.adjborder); if (blist.filter(b => b.line).length === cell.qnum) { blist.forEach(b => add_cross(b)); } if (blist.filter(b => !isCross(b)).length === cell.qnum) { blist.forEach(b => add_line(b)); } // deduce single clue if (cell.qnum >= 0) { let list = [offset(cell, -.5, -.5), offset(cell, .5, -.5), offset(cell, -.5, .5), offset(cell, .5, .5)]; let sum = list.map(cr => JSON.parse(cr.qsub).length).reduce((a, b) => a + b, 0); let comblist = []; JSON.parse(list[0].qsub).forEach(q0 => { if (adjlist(cell.adjborder).filter(b => [].concat(q0).includes(b.id)).length > cell.qnum) { return; } if (2 - adjlist(cell.adjborder).filter(b => [].concat(q0).includes(b.id)).length > 4 - cell.qnum) { return; } JSON.parse(list[1].qsub).forEach(q1 => { if (adjlist(cell.adjborder).filter(b => [].concat(q0, q1).includes(b.id)).length > cell.qnum) { return; } if (3 - adjlist(cell.adjborder).filter(b => [].concat(q0, q1).includes(b.id)).length > 4 - cell.qnum) { return; } if (q0.includes(offset(cell, 0, -.5).id) ^ q1.includes(offset(cell, 0, -.5).id)) { return; } JSON.parse(list[2].qsub).forEach(q2 => { if (q0.includes(offset(cell, -.5, 0).id) ^ q2.includes(offset(cell, -.5, 0).id)) { return; } JSON.parse(list[3].qsub).forEach(q3 => { if (q2.includes(offset(cell, 0, .5).id) ^ q3.includes(offset(cell, 0, .5).id)) { return; } if (q1.includes(offset(cell, .5, 0).id) ^ q3.includes(offset(cell, .5, 0).id)) { return; } if (adjlist(cell.adjborder).filter(b => [].concat(q0, q1, q2, q3).includes(b.id)).length !== cell.qnum) { return; } comblist.push([q0, q1, q2, q3]); }); }); }); }); list.forEach((cr, i) => { cr.setQsub(JSON.stringify(JSON.parse(cr.qsub).filter(s => comblist.some(comb => JSON.stringify(comb[i]) === JSON.stringify(s))))); }); if (list.map(cr => JSON.parse(cr.qsub).length).reduce((a, b) => a + b, 0) < sum) { flg2 = true; } } for (let d = 0; d < 4; d++) { // × // · · · ╻ ╻ ╻ // 3 3 -> ┃3┃3┃ // · · · ╹ ╹ ╹ // × 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)); let fn = function (c1, c2) { if (isYellow(c1)) { add_bg_color(c2, CQSUB.green); } if (isGreen(c1)) { add_bg_color(c2, CQSUB.yellow); } }; fn(offset(cell, 0, 1, d), offset(cell, 0, -1, d)); fn(offset(cell, 0, -1, d), offset(cell, 0, 1, d)); } // × // · · · · · ╻ // ×2 3 -> ×2 3┃ // · · · · · ╹ // × if (cell.qnum === 2 && offset(cell, 1, 0, d).qnum === 3 && offset(cell, -.5, 0, d).qsub === BQSUB.cross) { add_line(offset(cell, 1.5, 0, d)); add_cross(offset(cell, .5, -1, d)); add_cross(offset(cell, .5, 1, d)); } } }); // deduce color forEachCell(cell => { let innercnt = adjlist(cell.adjacent).filter(c => isGreen(c)).length; let outercnt = adjlist(cell.adjacent).filter(c => isYellow(c)).length; // number and color deduce if (cell.qnum >= 0) { if (cell.qnum < innercnt || 4 - cell.qnum < outercnt) { add_green(cell); } if (cell.qnum < outercnt || 4 - cell.qnum < innercnt) { add_yellow(cell); } if (isGreen(cell) && cell.qnum === outercnt) { fourside(add_green, cell.adjacent); } if (isYellow(cell) && cell.qnum === innercnt) { fourside(add_yellow, cell.adjacent); } if (isYellow(cell) && cell.qnum === 4 - outercnt) { fourside(add_green, cell.adjacent); } if (isGreen(cell) && cell.qnum === 4 - innercnt) { fourside(add_yellow, cell.adjacent); } if (cell.qnum === 2 && outercnt === 2) { fourside(add_green, cell.adjacent); } if (cell.qnum === 2 && innercnt === 2) { fourside(add_yellow, cell.adjacent); } // 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); } } }, cell.adjacent, cell.adjborder); } // same diagonal color as 3 if (cell.qnum === 3 && cell.qsub !== CQSUB.none) { for (let d = 0; d < 4; d++) { if (!offset(cell, 1, 0, d).isnull && !offset(cell, 0, 1, d).isnull && offset(cell, 1, 1, d).qsub === cell.qsub) { add_line(offset(cell, -.5, 0, d)); add_line(offset(cell, 0, -.5, d)); } } } if (cell.qnum === 2) { // × // ×· · // 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 || isCross(b1))) { continue; } if (!(b2.isnull || isCross(b2))) { continue; } let c1 = offset(cell, 1, 0, d); let c2 = offset(cell, 0, 1, d); // A=B add_bg_color(c1, (c2.isnull ? CQSUB.yellow : c2.qsub)); add_bg_color(c2, (c1.isnull ? CQSUB.yellow : c1.qsub)); } } } }); }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址