console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数独核对底稿生成器</title>
<style>
body { font-family: "微软雅黑", sans-serif; display: flex; flex-direction: column; align-items: center; padding: 20px; background-color: #f4f6f9; }
.input-area { width: 700px; background: #fff; padding: 15px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); display: flex; gap: 10px; align-items: center; }
textarea { flex: 1; height: 60px; font-size: 14px; padding: 10px; border: 1px solid #ccc; border-radius: 4px; resize: none; font-family: monospace; }
button { padding: 10px 20px; font-size: 16px; background-color: #0056b3; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
button:hover { background-color: #004494; }
.container { display: flex; gap: 30px; }
.board { display: grid; grid-template-columns: repeat(9, 50px); grid-template-rows: repeat(9, 50px); border: 3px solid #333; background: #fff;}
.cell { width: 50px; height: 50px; display: flex; justify-content: center; align-items: center; font-size: 24px; font-weight: bold; border: 1px solid #ccc; box-sizing: border-box; background-color: #fff; cursor: pointer; outline: none; text-align: center; transition: background-color 0.2s; }
/* 3x3 粗边框 */
.cell:nth-child(3n) { border-right: 3px solid #333; }
.cell:nth-child(9n) { border-right: 1px solid #ccc; }
.cell:nth-child(n+19):nth-child(-n+27), .cell:nth-child(n+46):nth-child(-n+54) { border-bottom: 3px solid #333; }
.cell.initial { background-color: #e2e6ea; color: #333; pointer-events: none; }
.cell.error { color: #d9534f; background-color: #f2dede; }
.panel { width: 250px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.panel h3 { margin-top: 0; color: #333; border-bottom: 2px solid #eee; padding-bottom: 10px; }
.info-item { display: flex; justify-content: space-between; margin-bottom: 15px; font-size: 14px; color: #555; }
.info-val { font-weight: bold; color: #000; }
.status-error { color: #d9534f !important; }
</style>
</head>
<body>
<!-- 顶部输入区 -->
<div class="input-area">
<textarea id="puzzle-input" placeholder="在这里粘贴 81 个数字。例如:7 0 0 0 8 0 0 5 0 0 0 6 9 0 1... (0代表空格,允许带空格或换行)"></textarea>
<button onclick="generateNewBoard()">生成底稿</button>
</div>
<!-- 游戏区 -->
<div class="container">
<div class="board" id="sudoku-board"></div>
<div class="panel">
<h3>数据核对底稿</h3>
<div class="info-item"><span>待核对项</span><span class="info-val" id="blank-count">0</span></div>
<div class="info-item"><span>异常项 (冲突)</span><span class="info-val" id="error-count">0</span></div>
<div class="info-item"><span>核对状态</span><span class="info-val" id="status">进行中</span></div>
<div class="info-item"><span>校验规则</span><span class="info-val" style="font-size:12px; color:#888;">同行/列/宫不重复</span></div>
</div>
</div>
<script>
let blanks = 0;
const board = document.getElementById('sudoku-board');
const blankCountEl = document.getElementById('blank-count');
const errorCountEl = document.getElementById('error-count');
const statusEl = document.getElementById('status');
// 默认填入当前这道题目
document.getElementById('puzzle-input').value =
"7 0 0 0 8 0 0 5 0 " +
"0 0 6 9 0 1 0 0 0 " +
"0 0 0 0 3 0 1 0 7 " +
"6 0 0 5 0 0 8 0 0 " +
"0 0 8 0 0 0 5 0 0 " +
"0 0 9 0 0 4 0 0 3 " +
"3 0 2 0 4 0 0 0 0 " +
"0 0 0 2 0 7 4 0 0 " +
"0 7 0 0 6 0 0 0 1";
// 页面加载完成后先渲染一次
window.onload = () => generateNewBoard();
function generateNewBoard() {
// 1. 获取并清洗输入内容
let rawInput = document.getElementById('puzzle-input').value;
// 只保留数字 (过滤掉空格、逗号、换行等)
let digits = rawInput.replace(/[^0-9]/g, '');
if (digits.length !== 81) {
alert('输入错误:必须正好包含 81 个数字!当前包含 ' + digits.length + ' 个。');
return;
}
// 2. 转换成二维数组
let puzzle = [];
for (let i = 0; i < 9; i++) {
let row = [];
for (let j = 0; j < 9; j++) {
row.push(parseInt(digits[i * 9 + j]));
}
puzzle.push(row);
}
// 3. 清空旧棋盘并重新渲染
board.innerHTML = '';
blanks = 0;
for (let r = 0; r < 9; r++) {
for (let c = 0; c < 9; c++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.row = r;
cell.dataset.col = c;
cell.id = `cell-${r}-${c}`;
if (puzzle[r][c] !== 0) {
cell.textContent = puzzle[r][c];
cell.classList.add('initial');
} else {
blanks++;
cell.setAttribute('contenteditable', 'true');
}
cell.addEventListener('input', function() {
let val = this.textContent.trim();
if (val !== '' && !/^[1-9]$/.test(val)) {
this.textContent = '';
}
validateBoard();
});
cell.addEventListener('keydown', function(e) {
if (e.key === 'Enter') e.preventDefault();
});
board.appendChild(cell);
}
}
// 初始化面板
validateBoard();
}
// 核心:全盘查重逻辑 (与之前一致)
function validateBoard() {
let currentBoard = [];
for(let r=0; r<9; r++) {
let row = [];
for(let c=0; c<9; c++) {
let val = document.getElementById(`cell-${r}-${c}`).textContent;
row.push(val ? parseInt(val) : 0);
}
currentBoard.push(row);
}
document.querySelectorAll('.cell').forEach(c => c.classList.remove('error'));
let errorCells = new Set();
// 查行/列
for (let i = 0; i < 9; i++) {
let rowSeen = {}, colSeen = {};
for (let j = 0; j < 9; j++) {
let rVal = currentBoard[i][j];
if (rVal !== 0) {
if (rowSeen[rVal] !== undefined) {
errorCells.add(`${i}-${j}`); errorCells.add(`${i}-${rowSeen[rVal]}`);
} else rowSeen[rVal] = j;
}
let cVal = currentBoard[j][i];
if (cVal !== 0) {
if (colSeen[cVal] !== undefined) {
errorCells.add(`${j}-${i}`); errorCells.add(`${colSeen[cVal]}-${i}`);
} else colSeen[cVal] = j;
}
}
}
// 查 3x3 宫
for (let boxRow = 0; boxRow < 3; boxRow++) {
for (let boxCol = 0; boxCol < 3; boxCol++) {
let boxSeen = {};
for (let r = 0; r < 3; r++) {
for (let c = 0; c < 3; c++) {
let actualR = boxRow * 3 + r;
let actualC = boxCol * 3 + c;
let val = currentBoard[actualR][actualC];
if (val !== 0) {
if (boxSeen[val] !== undefined) {
errorCells.add(`${actualR}-${actualC}`); errorCells.add(`${boxSeen[val].r}-${boxSeen[val].c}`);
} else {
boxSeen[val] = {r: actualR, c: actualC};
}
}
}
}
}
}
// 标红
errorCells.forEach(coord => {
document.getElementById(`cell-${coord}`).classList.add('error');
});
updateStats(errorCells.size);
}
function updateStats(errorCount) {
let filled = 0;
document.querySelectorAll('.cell:not(.initial)').forEach(cell => {
if (cell.textContent.trim() !== '') filled++;
});
blankCountEl.textContent = blanks - filled;
errorCountEl.textContent = errorCount;
if (errorCount > 0) {
errorCountEl.classList.add('status-error');
statusEl.textContent = '异常待处理';
statusEl.classList.add('status-error');
} else {
errorCountEl.classList.remove('status-error');
statusEl.classList.remove('status-error');
if (filled === blanks) {
statusEl.textContent = '无冲突 (已填满)';
statusEl.style.color = '#28a745';
} else {
statusEl.textContent = '进行中 (暂无冲突)';
statusEl.style.color = '#000';
}
}
}
</script>
</body>
</html>