本文章是小游戏制作系列,其他系列-数独篇章
游戏思路
数独游戏的规律是,同一行,同一列,每3*3的9宫格中只能存在1-9的数字,不能重复。
步骤1:知道游戏规则之后,我们可以开始着手创建一个符合游戏规则的数独棋盘(它的本质是一个二维数组)。
步骤2:根据选择的难度随机挖空对应数目的格子,留给用户填写。简单模式挖空4格子,中等模式挖空6格子等。
步骤3:用户点击提交时判断做一个简单的输入验证(是否输入为空,是否正填写1-9范围的数字)。
步骤4:通过步骤3的初步验证之后,验证用户填写完的数独数组是否是有效的。(有两种方案,方案一是保步骤1的数独数组与现在的数独数组做对比;方案二:将现在新的组成的数独数组通过一个检验是否有效数独的方法校验),此处选择的是方案二。
步骤5:根据步骤4的校验结果,判断是否通过游戏,并进行对应的提示。通过之后得分改变。
最终游戏界面如下:
游戏代码
html代码
js代码重点方法
// 画布初始化
function init(contentId){
// 创建一个二维数据 9*9来进行画布渲染
for(let i=0;i<9;i++){
sdArr[i] = new Array(9).fill(0)
}
// 创建数独二维数组
// 数独规则,每一行,每一列数字不能重复(1-9)。每一个3*3的正方形格子中,数字不能重复(1-9)
for(let row=0;row<9;row++){
let column = 0;
let numArr = [...numArray];
let newObj = new Map();
while(column<9){
if(column == 0){
newObj = new Map();
}
let cellItem = Math.floor(Math.random()*numArr.length);
newObj[column]?'':newObj[column] = new Set();
newObj[column].add(numArr[cellItem])
if(!rule(numArr[cellItem],row,column)){
if(numArr.length == newObj[column].size){
// 表示陷入死循环中,需要重新开始计算这一对数据
numArr = [...numArray];
column=0;
sdArr[row] = new Array(9).fill(0)
}
}else{
sdArr[row][column] = numArr[cellItem];
numArr.splice(cellItem,1)
column++;
}
}
}
hollowing(contentId,sdArr);
}
/**
* @description 生成数独数组时需要判断数独的合理性,一行不能出现重复数字,一列不能出现重复数字,一个3*3方格不能出现重复的数字
*/
function rule(number,row,column){
// 一列
for(let i=0;i<9;i++){
if( sdArr[i][column] == number || sdArr[row][i] == number){
return false;
}
}
// 一个3*3
let rowStart = Math.floor(row/3)*3;
let cloumStart = Math.floor(column/3)*3;
let rowS = row%3;
if(rowS != 0){
for(let i=0;i<3;i++){
for(let j=0;j<3;j++){
if(sdArr[rowStart+i][cloumStart+j] == number){
return false
}
}
}
}
return true;
}
/**
* @description 用于检验是否是一个合法的数独数组
* */
function submitRule(Arr){
let blockSet = {};
let blockFlag =true;
for(let i=0;i<9;i++){
let columSet = new Set();
let rowIndex = Math.floor(i/3)*3;
for(let j=0;j<9;j++){
let columnIndex = Math.floor(j/3)*3;
columSet.add(Arr[i][j]);
if(i%3 == 0 && j%3 == 0){
blockSet[rowIndex+''+columnIndex] = new Set();
}
blockSet[rowIndex+''+columnIndex].add(Arr[i][j]);
}
let rowSet = new Set(Arr[i]);
if(rowSet.size<9 || columSet.size<9){
return false;
}
}
for(item in blockSet){
if(item.size<9){
blockFlag = false;
}
}
return blockFlag
}
/**
* @description 用于渲染游戏区域
* @param{contentId} 需要渲染的游戏区域的元素id
* @param{Arr} 用于渲染游戏区域的数组对象
* */
function renderGame(contentId,Arr){
const content = document.getElementById(contentId);
let str = '';
for(let i=0;i<9;i++){
str +=`<div class='canvas-row'>`
for(let j=0;j<9;j++){
if(Arr[i][j]){
str += `<span class='cell'>${Arr[i][j]}</span>`
}else{
str += `<span class='cell cell${i}${j} cell-color' contenteditable="true">${Arr[i][j]}</span>`
}
}
str +=`</div>`
}
content.innerHTML = str;
}
/**
* @description 进行游戏挖空,根据选定难度来
* */
let hollowingArr = [];
function hollowing(contentId,Arr){
let num = difficulty[difficultyType].nums;//提取难度对应的空缺位数
while(num>0){
// 取随机数
let row = Math.floor(Math.random()*9)
let colum = Math.floor(Math.random()*9)
if(hollowingArr.length == 0){
hollowingArr.push({row,colum});
Arr[row][colum] = ''
num--;
}else{
let hasRow = hollowingArr.some((item)=>{
return item.row == row && item.colum == colum
});
if(!hasRow){
hollowingArr.push({row,colum});
Arr[row][colum] = ''
num--;
}
}
}
renderGame(contentId,Arr);
}
/**
* @description 游戏可编辑块输入内容捕捉
* */
function editable(className){
// 获取所有填写的cell元素,组合数组元素
let num = difficulty[difficultyType].nums;//提取难度对应的空缺位数
let flag = true;
for(i=0;i<num;i++){
const editE = document.getElementsByClassName(className+hollowingArr[i].row+hollowingArr[i].colum)[0];
if(editE.innerText){
if(numArray.includes(editE.innerText)){
sdArr[hollowingArr[i].row][hollowingArr[i].colum] = editE.innerText;
}else{
alert("请正确填写第"+(parseInt(hollowingArr[i].row)+1)+'行,第'+(parseInt(hollowingArr[i].colum)+1)+'列内容');
flag = false;
return false;
}
}else{
alert("第"+(parseInt(hollowingArr[i].row)+1)+'行,第'+(parseInt(hollowingArr[i].colum)+1)+'列未填写');
flag = false;
return false;
}
}
return flag;
}
游戏优化
不过现在多次重新开始游戏,可能是init方法里面的算法问题,可能会存在卡顿现象。后续还需要进行算法优化
界面优化(2023-1-6):在选中用户点击可输入块进行输入时,界面上提示该列,该行,该3*3九宫格范围,让用户比较明显的知道要对比的数据。我们需要css中准备一个class类名‘cell-item’,然后在事件判断过程中,动态的添加到元素上,展示效果如图。
重点代码如下:
/**
* @description 输入过程中,样式动态修改
*/
function editCell(){
document.getElementById("canvasContent").addEventListener("click", function(e) {
// 检查事件源e.targe是否为目标
if(e.target && e.target.className.includes('cell-color')) {
let elementData = e.target.getAttribute('data');
// 获取到当前点击的行列坐标
let row = elementData[0];
let colum = elementData[2];
// 现在需要做样式渲染
let cellList = document.getElementsByClassName("cell");
for(let i=0;i<cellList.length;i++){
cellList[i].classList.remove("cell-item")
// 同行同列开始渲染颜色
if((i>=row*9 && i<(row*9+9)) || i%9 == colum){
cellList[i].classList.add("cell-item")
}
// 3*3格子开始渲染颜色
if(Math.floor(i/27) == Math.floor(row/3)){
if(Math.floor((i%9)/3)>=Math.floor(colum/3) && Math.floor((i%9)/3)<Math.floor(colum/3)+1){
cellList[i].classList.add("cell-item")
}
}
}
}
});
}