手写数独-步骤解析
1. 生成默认表盘
// 生成9*9宫格
function createBoard() {
return JSON.parse(JSON.stringify(new Array(9).fill(new Array(9).fill("."))));
}
2. 生成终解
- 预填部分值,降低生成难度:随机生成 1、3、9 区域内容
// 生成长度为9的1~9随机数组
function randomArr9() {
return ["1", "2", "3", "4", "5", "6", "7", "8", "9"].sort(() => Math.random() - 0.5);
}
// 先设置三个3*3数独值
function firstSetValue() {
const firstArea = [0, 3, 6];
firstArea.forEach(function (item) {
const randomArr = randomArr9();
for (let i = 0; i < 3; i++) {
for (let r = 0; r < 3; r++) {
board[item + i][item + r] = randomArr.pop();
}
}
});
}
- 回溯算法,生成剩余未填项。
// 校验
function verify(row, col, val) {
// 校验横竖轴
for (let i = 0; i < 9; i++) {
if (board[i][col] === val || board[row][i] === val) {
return false;
}
}
// 校验3*3宫格
const rect9RowIndex = row - (row % 3);
const rect9ColIndex = col - (col % 3);
for (let i = 0; i < 3; i++) {
for (let r = 0; r < 3; r++) {
const currntVal = board[rect9RowIndex + i][rect9ColIndex + r];
if (currntVal === val) return false;
}
}
return true;
}
// 填充数独
function fillBoard(current) {
const row = (current / 9) | 0;
const col = current % 9;
if (current === 81) return true;
if (board[row][col] !== ".") return fillBoard(current + 1);
for (let item = 1; item < 10; item++) {
if (verify(row, col, item + "")) {
board[row][col] = item + "";
if (fillBoard(current + 1)) return true;
board[row][col] = ".";
}
}
}
3. “挖洞”
- 初级算法
// 抠除算法-初级(全盘随机)
function cutOut0() {
const index = 0;
const boardArray = new Array(81).fill("0");
const randomBoard = board
.map((item, i) => item.map((num, j) => "" + i + j))
.flat()
.sort(() => Math.random() - 0.5);
for (let i = 0; i < randomBoard.length; i++) {
const element = randomBoard[i].split("");
const value = board[element[0]][element[1]];
const others = "123456789".replace(value, "").split("");
const canClear = others.every((num) => !verify(element[0], element[1], num));
if (canClear) {
cacheTip(element, board[element[0]][element[1]]);
board[element[0]][element[1]] = "";
}
}
}
- 中级算法
// 抠除算法-中级(间隔)
function cutOut1() {
const index = 0;
const boardArray = new Array(81).fill("0");
const randomBoard = board.map((item, i) => item.map((num, j) => "" + i + j)).flat();
const spacingBoard = randomBoard
.filter((item) => {
if (Number(item[0] % 2)) {
return Number(item) % 2;
} else {
return !(Number(item) % 2);
}
})
.sort((prev, next) => {
const isSameRow = prev[0] === next[0];
if (isSameRow && Number(prev[0] % 2) && Number(next[0] % 2)) {
return -1;
} else {
return 1;
}
});
const others = randomBoard.filter((item) => {
if (Number(item[0] % 2)) {
return !Number(item) % 2;
} else {
return Number(item) % 2;
}
});
const concatArray = spacingBoard.concat(others);
for (let i = 0; i < concatArray.length; i++) {
const element = concatArray[i].split("");
const value = board[element[0]][element[1]];
const others = "123456789".replace(value, "").split("");
const canClear = others.every((num) => !verify(element[0], element[1], num));
if (canClear) {
cacheTip(element, board[element[0]][element[1]]);
board[element[0]][element[1]] = "";
}
}
}
- 高级算法
// 抠除算法-高级(蛇形)
function cutOut2() {
const index = 0;
const boardArray = new Array(81).fill("0");
const randomBoard = board.map((item, i) => item.map((num, j) => "" + i + j)).flat();
randomBoard.sort((prev, next) => {
const isSameRow = prev[0] === next[0];
if (isSameRow && Number(prev[0] % 2) && Number(next[0] % 2)) {
return -1;
} else {
return 1;
}
});
for (let i = 0; i < randomBoard.length; i++) {
const element = randomBoard[i].split("");
const value = board[element[0]][element[1]];
const others = "123456789".replace(value, "").split("");
const canClear = others.every((num) => !verify(element[0], element[1], num));
if (canClear) {
cacheTip(element, board[element[0]][element[1]]);
board[element[0]][element[1]] = "";
}
}
}
- 骨灰级算法
// 抠除算法-骨灰级(从上到下,从左到右)
function cutOut3() {
const index = 0;
const boardArray = new Array(81).fill("0");
const randomBoard = board.map((item, i) => item.map((num, j) => "" + i + j)).flat();
for (let i = 0; i < randomBoard.length; i++) {
const element = randomBoard[i].split("");
const value = board[element[0]][element[1]];
const others = "123456789".replace(value, "").split("");
const canClear = others.every((num) => !verify(element[0], element[1], num));
if (canClear) {
cacheTip(element, board[element[0]][element[1]]);
board[element[0]][element[1]] = "";
}
}
}
4. 添加操作
绑定单元格事件
- (1)“选中未填格”
- 高亮相关 20 格,并区别高亮选中格
- 启用数字键盘、提示按钮
- (2)“选中已填格”
- 高亮相关 20 格,高亮其他相同数字格
- 禁用用数字键盘、提示按钮
- 根据当前单元格 class,修改擦除按钮禁用状态
- (1)“选中未填格”
数字键盘输入事件
- 未填格输入正确值时,
- 切换为(2)状态
- 未填格输入错误值时,
- 切换为(1)状态
- 高亮错误样式、启用擦除按钮
- 未填格输入正确值时,
提示按钮
- 填写正确值
- 切换为(2)状态
- 清空错误样式、禁用擦除按钮
擦除按钮
- 清空选中单元格与样式
5. 注意事项
- 存在三个全局变量
- board:整体 value 表盘
- selectedCell:选中的未填格 id
- tips:存储“挖洞”挖掉的位置,用于提示操作
- 较完整的数独游戏还缺少下面几个功能:
- 撤销功能
- 笔记功能
- 错误限制次数
- 提示限制次数
- 记录游戏时间
游戏入口 数独 by 小明(wap 版) 数独 by 小明(pc 版)
参考文档