每日杀手数独自动高亮
// ==UserScript==
// @name 每日杀手数独自动高亮
// @version 0.3.0
// @description Daily killer Sudoku AutoHighlight.(含指定高亮及冲突高亮)
// @author DreamNya
// @match https://www.dailykillersudoku.com/puzzle/*
// @grant none
// @license MIT
// @run-at document-end
// @namespace https://greasyfork.org/users/809466
// ==/UserScript==
// localStorage
const getKey = function (key, defaultValue) {
const value = JSON.parse(localStorage.getItem('AutoHighlight-' + key));
return value ?? defaultValue;
};
const setKey = function (key, value) {
localStorage.setItem('AutoHighlight-' + key, JSON.stringify(value));
return value;
};
// 等待元素加载
await new Promise((resolve) => {
$(puzzleBoard).ready(function () {
resolve();
});
});
// 关联格与宫
const blocks = Array(3)
.fill()
.map((i) =>
Array(3)
.fill()
.map((n) => [])
);
const boardCells = DKS.puzzle.board._cells.flat();
boardCells.forEach((cell) => {
const row = Math.floor(cell.row / 3);
const column = Math.floor(cell.column / 3);
blocks[row][column].push(cell);
});
blocks.flat().forEach((block) => {
block.forEach((cell) => {
cell.block = block;
});
});
// 自动高亮(指定)
const AutoHighlight = {};
window.AutoHighlight = AutoHighlight;
const config = {};
AutoHighlight.config = config;
config.num = getKey('num', true);
config.subnum = getKey('subnum', true);
config.errorLint = getKey('errorLint', true);
AutoHighlight.num = 0;
// 切换设置
AutoHighlight.toggleConfig = function (item, element) {
config[item] = setKey(item, !config[item]);
$(element).attr('data-icon', config[item] ? 'toggle-on' : 'toggle-off');
if (item == 'errorLint') {
!config.errorLint && AutoErrorLint.clearErrorLint();
config.errorLint && AutoErrorLint.errorLint();
}
};
// 清除指定高亮
AutoHighlight.reset = function () {
AutoHighlight.highlight(0);
};
// 指定高亮
AutoHighlight.highlight = function (num) {
DKS.puzzle.board.clearHighlightedCells();
if (AutoHighlight.num == num) {
num = 0;
}
AutoHighlight.num = num;
if (num == 0) {
return;
}
const cells = boardCells.reduce((arr, cell) => {
let flag = false;
if (config.num) {
if (cell.value == num) {
flag = true;
}
}
if (!flag && config.subnum) {
if (cell.pencilMarks.includes(num)) {
flag = true;
}
}
if (flag) {
arr.push(cell);
}
return arr;
}, []);
DKS.puzzle.board.highlightedCells.push(...cells);
DKS.puzzle.board._drawHighlightedCells();
};
// 自动高亮(冲突)
const AutoErrorLint = {};
window.AutoErrorLint = AutoErrorLint;
AutoErrorLint.errorCells = new Set();
AutoErrorLint.clearErrorLint = function () {
AutoErrorLint.errorCells.forEach((cell) => cell.removeErrorTint());
AutoErrorLint.errorCells.clear();
};
// 检查实数冲突
AutoErrorLint.checkValue = function ({ row, column, value }, cells) {
if (value == 0) {
return false;
}
let flag = false;
cells.forEach((cell) => {
if (cell.row == row && cell.column == column) {
return;
}
const conflict = cell.value == value || cell.pencilMarks.includes(value);
if (conflict) {
cell.addErrorTint();
AutoErrorLint.errorCells.add(cell);
flag = true;
}
});
return flag;
};
// 检查候选数冲突
AutoErrorLint.checkMarks = function ({ row, column, pencilMarks }, cells) {
if (pencilMarks.length == 0) {
return false;
}
let flag = false;
cells.forEach((cell) => {
if (cell.row == row && cell.column == column) {
return;
}
const conflict = pencilMarks.includes(cell.value); // || pencilMarks.some((mark) => cell.pencilMarks.includes(mark));
if (conflict) {
cell.addErrorTint();
AutoErrorLint.errorCells.add(cell);
flag = true;
}
});
return flag;
};
// 检查格
AutoErrorLint.checkCells = function (cell, cells) {
return AutoErrorLint.checkValue(cell, cells) || AutoErrorLint.checkMarks(cell, cells);
};
// 检查宫
AutoErrorLint.checkBlock = function (cell) {
const { block } = cell;
return AutoErrorLint.checkCells(cell, block);
};
// 检查笼
AutoErrorLint.checkCage = function (cell) {
const cells = cell.cage.cells;
return AutoErrorLint.checkCells(cell, cells);
};
// 检查行
AutoErrorLint.checkRow = function (cell) {
const { row } = cell;
const cells = DKS.puzzle.board._cells[row];
return AutoErrorLint.checkCells(cell, cells);
};
// 检查列
AutoErrorLint.checkColumn = function (cell) {
const { column } = cell;
const cells = DKS.puzzle.board._cells.map((_cells) => _cells[column]);
return AutoErrorLint.checkCells(cell, cells);
};
// 冲突高亮
AutoErrorLint.errorLint = function () {
AutoErrorLint.clearErrorLint();
//DKS.puzzle.board.highlightedCells
boardCells.forEach((cell) => {
const cage = AutoErrorLint.checkCage(cell);
const block = AutoErrorLint.checkBlock(cell);
const column = AutoErrorLint.checkColumn(cell);
const row = AutoErrorLint.checkRow(cell);
if (cage || block || column || row) {
cell.addErrorTint();
AutoErrorLint.errorCells.add(cell);
}
});
};
config.errorLint && AutoErrorLint.errorLint();
document.addEventListener('keyup', function () {
config.errorLint && AutoErrorLint.errorLint();
});
const off =
'M384 64H192C85.961 64 0 149.961 0 256s85.961 192 192 192h192c106.039 0 192-85.961 192-192S490.039 64 384 64zM64 256c0-70.741 57.249-128 128-128 70.741 0 128 57.249 128 128 0 70.741-57.249 128-128 128-70.741 0-128-57.249-128-128zm320 128h-48.905c65.217-72.858 65.236-183.12 0-256H384c70.741 0 128 57.249 128 128 0 70.74-57.249 128-128 128z';
const on =
'M384 64H192C86 64 0 150 0 256s86 192 192 192h192c106 0 192-86 192-192S490 64 384 64zm0 320c-70.8 0-128-57.3-128-128 0-70.8 57.3-128 128-128 70.8 0 128 57.3 128 128 0 70.8-57.3 128-128 128z';
$(document.body).append(
`<div
id="AutoHighlight"
class="controls-container"
style="position: fixed; z-index: 99999; right: 4%; top: calc(50vh - 180px); width: 240px; height: 365px">
<header>
<a
class="controls-header-button controls-header-button-enabled reset-button"
onclick="AutoHighlight.reset();"
><svg
class="svg-inline--fa fa-sync-alt fa-w-16"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="sync-alt"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
data-fa-i2svg="">
<path
fill="currentColor"
d="M370.72 133.28C339.458 104.008 298.888 87.962 255.848 88c-77.458.068-144.328 53.178-162.791 126.85-1.344 5.363-6.122 9.15-11.651 9.15H24.103c-7.498 0-13.194-6.807-11.807-14.176C33.933 94.924 134.813 8 256 8c66.448 0 126.791 26.136 171.315 68.685L463.03 40.97C478.149 25.851 504 36.559 504 57.941V192c0 13.255-10.745 24-24 24H345.941c-21.382 0-32.09-25.851-16.971-40.971l41.75-41.749zM32 296h134.059c21.382 0 32.09 25.851 16.971 40.971l-41.75 41.75c31.262 29.273 71.835 45.319 114.876 45.28 77.418-.07 144.315-53.144 162.787-126.849 1.344-5.363 6.122-9.15 11.651-9.15h57.304c7.498 0 13.194 6.807 11.807 14.176C478.067 417.076 377.187 504 256 504c-66.448 0-126.791-26.136-171.315-68.685L48.97 471.03C33.851 486.149 8 475.441 8 454.059V320c0-13.255 10.745-24 24-24z"></path></svg
>
<div>Reset</div></a
>
<a
class="controls-header-button controls-header-button-enabled reset-button"
onclick="AutoHighlight.colorPicker.pick();"
><svg
class="svg-inline--fa fa-eye fa-w-18"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="eye"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512"
data-fa-i2svg="">
<path
fill="currentColor"
d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"></path></svg
>
<input id="colorPicker" type="color" style="position: absolute; visibility: hidden; left: 45px;">
<div>Color</div></a
>
<h2 style="font-size: 1.3rem;">AutoHighlight</h2>
</header>
<div class="controls-inner">
<div class="calculator-header" style="display: flex; justify-content: space-around; height: 85px; flex-wrap: wrap;">
<div class="preference-toggle">
<svg
class="svg-inline--fa fa-toggle-${config.errorLint ? 'on' : 'off'} fa-w-18 fa-fw fa-2x"
onclick="AutoHighlight.toggleConfig('errorLint', this)"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="toggle-${config.errorLint ? 'on' : 'off'}"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512"
data-fa-i2svg=""
style="margin: 0">
<path
fill="currentColor"
d="${config.errorLint ? on : off}"></path></svg
><span style="vertical-align: super">冲突高亮</span>
</div>
<div style="width: 100%; display: flex; justify-content: space-around; border-top: rgb(255 255 255 / 8%) double;">
<div class="preference-toggle">
<svg
class="svg-inline--fa fa-toggle-${config.num ? 'on' : 'off'} fa-w-18 fa-fw fa-2x"
onclick="AutoHighlight.toggleConfig('num', this)"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="toggle-${config.num ? 'on' : 'off'}"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512"
data-fa-i2svg=""
style="margin: 0">
<path
fill="currentColor"
d="${config.num ? on : off}"></path></svg
><span style="vertical-align: super">实数</span>
</div>
<div class="preference-toggle">
<svg
class="svg-inline--fa fa-toggle-${config.subnum ? 'on' : 'off'} fa-w-18 fa-fw fa-2x"
onclick="AutoHighlight.toggleConfig('subnum', this)"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="toggle-${config.subnum ? 'on' : 'off'}"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512"
data-fa-i2svg=""
style="margin: 0">
<path
fill="currentColor"
d="${config.subnum ? on : off}"></path></svg
><span style="vertical-align: super">候选数</span>
</div>
</div>
</div>
<div id="calculatorContent" class="calculator-content" style="display: flex; flex-wrap: wrap; justify-content: center; align-content: flex-end; height: 210px;">
${Array(9)
.fill()
.reduce((str, _, index) => {
str += `<div class="combination-container" onclick="AutoHighlight.highlight(${
index + 1
})" style="font-size: 16.8011px; width: 33.3%; height: 33.3%">
<span>${index + 1}</span>
</div>`;
return str;
}, '')}
</div>
</div>
`
);
$('#AutoHighlight').on('click mousedown', (e) => {
e.stopPropagation();
});
$(document).on('mousedown', (e) => {
if (DKS.puzzle.board.highlightedCells.length == 0) {
AutoHighlight.num = 0;
}
});
const colorPicker = {};
AutoHighlight.colorPicker = colorPicker;
config.numColor = getKey('numColor', DKS.isDarkMode ? '#ddf' : '#363686');
colorPicker.lastTime = 0;
colorPicker.colorStyle = $(`<style>.pencil-marks,.cell-value{color:${config.numColor}!important}</style>`).appendTo(
document.body
);
colorPicker.colorPicker = document.querySelector('#colorPicker');
colorPicker.colorPicker.value = config.numColor;
$(colorPicker.colorPicker).on('input', function (e) {
const now = Date.now();
if (now - colorPicker.lastTime < 100) {
return;
}
colorPicker.lastTime = now;
config.numColor = setKey('numColor', this.value);
colorPicker.colorStyle.html(`.pencil-marks,.cell-value{color:${config.numColor}!important}`);
});
colorPicker.pick = function (e) {
colorPicker.colorPicker.showPicker();
};