js练习-手写数独

手写数独-步骤解析

1. 生成默认表盘

// 生成9*9宫格
function createBoard() {
  return JSON.parse(JSON.stringify(new Array(9).fill(new Array(9).fill("."))));
}

2. 生成终解

  1. 预填部分值,降低生成难度:随机生成 1、3、9 区域内容
    1~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();
      }
    }
  });
}
  1. 回溯算法,生成剩余未填项。
// 校验
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. “挖洞”

  1. 初级算法
// 抠除算法-初级(全盘随机)
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]] = "";
    }
  }
}
  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]] = "";
    }
  }
}
  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]] = "";
    }
  }
}
  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. 绑定单元格事件

    • (1)“选中未填格”
      • 高亮相关 20 格,并区别高亮选中格
      • 启用数字键盘、提示按钮
    • (2)“选中已填格”
      • 高亮相关 20 格,高亮其他相同数字格
      • 禁用用数字键盘、提示按钮
    • 根据当前单元格 class,修改擦除按钮禁用状态
  2. 数字键盘输入事件

    • 未填格输入正确值时,
      • 切换为(2)状态
    • 未填格输入错误值时,
      • 切换为(1)状态
      • 高亮错误样式、启用擦除按钮
  3. 提示按钮

    • 填写正确值
    • 切换为(2)状态
    • 清空错误样式、禁用擦除按钮
  4. 擦除按钮

    • 清空选中单元格与样式

5. 注意事项

  • 存在三个全局变量
    • board:整体 value 表盘
    • selectedCell:选中的未填格 id
    • tips:存储“挖洞”挖掉的位置,用于提示操作
  • 较完整的数独游戏还缺少下面几个功能:
    • 撤销功能
    • 笔记功能
    • 错误限制次数
    • 提示限制次数
    • 记录游戏时间

游戏入口 数独 by 小明(wap 版) 数独 by 小明(pc 版)


参考文档


  转载请注明: XMwarrior js练习-手写数独

 本篇
js练习-手写数独 js练习-手写数独
手写数独-步骤解析1. 生成默认表盘// 生成9*9宫格 function createBoard() { return JSON.parse(JSON.stringify(new Array(9).fill(new Arra
2022-05-26
下一篇 
前端性能优化-内存泄漏问题分析 前端性能优化-内存泄漏问题分析
排查内存泄漏方法(Chrome) 利用 Chrome 调试工具的内存快照: F12 => 内存 => 拍摄堆快照 => 摘要、比较、控制、统计信息 重点检查 VueComponent
  目录