您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Solver for Single Trees puzzle
// ==UserScript== // @name Single Trees Solver // @namespace Violentmonkey Scripts // @match https://www.playqueensgame.com/* // @grant none // @version 1.0 // @author Zach Kosove // @license MIT // @description Solver for Single Trees puzzle // ==/UserScript== (function () { async function main() { console.clear(); // --- Initialize --- function initBoards() { const cells = document.querySelectorAll('.grid .aspect-square'); const N = Math.sqrt(cells.length); if (!Number.isInteger(N)) throw new Error(`Board is not square: ${cells.length} cells`); // allocate flat buffers const boardBuf = new Array(N * N).fill(null); const colorBuf = new Int16Array(N * N).fill(-1); // wrap them into N row views so you can still use [r][c] const board = Array.from({ length: N }, (_, r) => boardBuf.slice(r * N, (r + 1) * N)); const colors = Array.from({ length: N }, (_, r) => colorBuf.subarray(r * N, (r + 1) * N)); const map = Object.create(null); let count = 0; cells.forEach(cell => { colors[+cell.dataset.row][+cell.dataset.col] = map[cell.style.backgroundColor] ??= count++; }); console.table(colors.map(row => [...row])); return { N, colors, board }; } const { N, colors, board } = initBoards(); // --- Constraints --- function canPlaceDash(r, c, board) { return board[r][c] === null; } function canPlaceT(r, c, board) { for (let dr = -1; dr <= 1; dr++) { for (let dc = -1; dc <= 1; dc++) { const nr = r + dr, nc = c + dc; if (nr >= 0 && nr < N && nc >= 0 && nc < N && board[nr][nc] === 'T') return false; } } return true; } // --- Place T / Dash --- function applyBatteryRule(next) { const out = next.map(r => r.slice()); const bitAt = Uint32Array.from({ length: N }, (_, i) => 1 << i); const posMap = new Int16Array(N); const seen = new Uint32Array(N); let epoch = 1; const ctz = x => 31 - Math.clz32(x & -x); const popc = x => { let c = 0; while (x) { x &= x - 1; c++; } return c; }; let changed; do { changed = false; for (let pass = 0; pass < 2; pass++) { const spans = new Int32Array(N); const colorList = []; // build spans for (let r = 0; r < N; r++) { for (let c = 0; c < N; c++) { if (out[r][c] !== null) continue; const idx = pass === 0 ? r : c; spans[colors[r][c]] |= bitAt[idx]; } } // collect active colors for (let color = 0; color < N; color++) { if (spans[color] && seen[color] !== epoch) { posMap[color] = colorList.length; seen[color] = epoch; colorList.push(color); } } epoch++; const total = colorList.length; if (total < 2) continue; // iterate subsets of size 2..MAX_K let mask = total - 3 >> 31; let MAX_K = (3 & ~mask) | (total & mask); for (let k = 2; k <= MAX_K; k++) { let subset = (1 << k) - 1; const limit = 1 << total; while (subset < limit) { // union mask let mask = 0; for (let i = 0; i < total; i++) { if (subset & (1 << i)) mask |= spans[colorList[i]]; } if (popc(mask) === k) { // block outside subset let bits = mask; while (bits) { const idx = ctz(bits); bits &= bits - 1; for (let j = 0; j < N; j++) { const r = pass === 0 ? idx : j; const c = pass === 0 ? j : idx; const clr = colors[r][c]; const pos = posMap[clr]; if (pos >= 0 && (subset & (1 << pos))) continue; if (out[r][c] === null) { out[r][c] = "-"; changed = true; } } } } // Gosper’s hack const c = subset & -subset; const r = subset + c; subset = (((r ^ subset) >>> 2) / c) | r; } } } } while (changed); return out; } function placeDash(r, c, board) { if (!canPlaceDash(r, c, board)) return false; const next = board.map(row => row.slice()); next[r][c] = '-'; return next; } function placeT(r, c, board) { if (!canPlaceT(r, c, board)) return placeDash(r, c, board); var next = board.map(row => row.slice()); next[r][c] = 'T'; // 1. block all other cells in the same row and column for (let i = 0; i < N; i++) { if (next[r][i] === null) next[r][i] = '-'; if (next[i][c] === null) next[i][c] = '-'; } // 2. block diagonals (no-touch rule) if (r > 0 && c > 0 && next[r - 1][c - 1] === null) next[r - 1][c - 1] = '-'; if (r > 0 && c < N - 1 && next[r - 1][c + 1] === null) next[r - 1][c + 1] = '-'; if (r < N - 1 && c > 0 && next[r + 1][c - 1] === null) next[r + 1][c - 1] = '-'; if (r < N - 1 && c < N - 1 && next[r + 1][c + 1] === null) next[r + 1][c + 1] = '-'; // 3. block other cells in the same region/color const color = colors[r][c]; for (let i = 0; i < N; i++) { for (let j = 0; j < N; j++) { if (next[i][j] === null && colors[i][j] === color) next[i][j] = '-'; } } // 4. apply battery rule next = applyBatteryRule(next); // 4. color validation + forced placement const rowNulls = new Int16Array(N); const colNulls = new Int16Array(N); const colorNulls = new Int16Array(N); const colorHasT = new Uint8Array(N); const lastRow = new Int16Array(N * 2); const lastCol = new Int16Array(N * 2); const lastColor = new Int16Array(N * 2); // pass 1: count nulls and detect T’s for (let i = 0; i < N; i++) { const rowN = next[i], rowC = colors[i]; for (let j = 0; j < N; j++) { const v = rowN[j], clr = rowC[j]; if (v === 'T') { colorHasT[clr] = 1; continue; } if (v !== null) continue; // row rowNulls[i]++; lastRow[i<<1] = i; lastRow[(i<<1)+1] = j; // col colNulls[j]++; lastCol[j<<1] = i; lastCol[(j<<1)+1] = j; // color colorNulls[clr]++; lastColor[clr<<1] = i; lastColor[(clr<<1)+1] = j; } } // pass 2: forced placements for (let i = 0; i < N; i++) { if (colorNulls[i] === 0 && colorHasT[i] === 0) return false; if (colorNulls[i] === 1) return placeT(lastColor[i <<1], lastColor[(i <<1 ) + 1], next); if (rowNulls[i] === 1) return placeT(lastRow[i <<1], lastRow[(i << 1) + 1], next); if (colNulls[i] === 1) return placeT(lastCol[i <<1], lastCol[(i << 1) + 1], next); } return next; } // --- Validation / Analysis --- let evaluations = 0; function analyze(board) { evaluations++; const rowT = new Int8Array(N), rowE = new Int8Array(N), colT = new Int8Array(N), colE = new Int8Array(N), tByColor = new Int8Array(N), eByColor = new Int8Array(N); // count let empties = false; for (let r = 0; r < N; r++) { for (let c = 0; c < N; c++) { const v = board[r][c], color = colors[r][c]; if (v === "T") (rowT[r]++, colT[c]++, tByColor[color]++); else if (v == null) (rowE[r]++, colE[c]++, eByColor[color]++, empties = true); } } // solved? if (!empties) { for (let i = 0; i < N; i++) { if (rowT[i] !== 1 || colT[i] !== 1 || tByColor[i] !== 1) break; if (i === N - 1) return true; } } // candidate let best = null, bScore = 1e9; for (let r = 0; r < N; r++) { for (let c = 0; c < N; c++) { if (board[r][c] === null && canPlaceT(r, c, board)) { const score = eByColor[colors[r][c]]; if (score < bScore) best = { r, c }, bScore = score; } } } return best; } // --- Search --- function search(board) { console.table(board); const pick = analyze(board); if (pick === null) return null; if (pick === true) return board; let res = null; let next = placeT(pick.r, pick.c, board); if (next && (res = search(next))) return res; next = placeDash(pick.r, pick.c, board); if (next && (res = search(next))) return res; return null; } function solve(board) { board = applyBatteryRule(board); const solution = search(board); if (!solution) throw new Error("no solution found"); console.table(solution); console.log("Evaluations:", evaluations); return solution; } let solution = solve(board); // --- Input Solution --- async function clickTCells(boardOrPromise) { const b = await boardOrPromise; const els = document.querySelectorAll(".grid [data-row][data-col]"); let i = 0; for (let r = 0; r < N; r++) { const row = b[r]; for (let c = 0; c < N; c++, i++) { if (row[c] === "T") els[i].click(); } } } clickTCells(solution); } document.addEventListener("keydown", e => e.key === "`" && main()); })();