前言
借鉴了数独参考资料:JavaScript九宫格数独生成算法
在原来的基础上进行了改造,完成了逆数独~ (即我的数独我做主~)
《我的数独我做主》:是一款pc端单机html小休闲益智游戏;需要键盘和鼠标搭配,自己制作数独,自己解数独~
一、游戏介绍与规则
技术介绍
css + jq
游戏名称
《我的数独我做主》
游戏规则
随心所欲,自己的数独自己做主;想怎么分配数字就怎么分配数字。(没有限制,任由发挥,可以创建空的数独,再去自己填补完整的数独。也可以直接创建完整的数独,再点击解答~)
1.建数独
输入数字到对应位置,创建数独。
2.解数独
完成数独创建,再去解数独。
3.完成
流程完成会出现有显示创建的完成时间、解答的完成时间~
二、大体设计与代码讲解
大体设计
先“画”表格,往表格里填写数字,点击创建数独;数独判断是否成立;成立即为创建数独成功;开行进行解答数独,填写正确数独;点击完成,判断是否解对,对了即可胜利!
完整代码
/** * author:ls * email:liusaint@gmail.com * date:2016年4月9日 * * update: nfz * updateDate:2022年4月24日 */ function SD() { this.sdArr = []; //生成的数独数组 this.errorArr = []; //错误的格子。 this.blankNum = 81; //空白格子数量 this.backupSdArr = []; //数独数组备份。 this.createTime = 1076275213; // 开始创建时间 this.createEndTime = 1076275213; // 创建完成时间 } SD.prototype = { constructor: SD, // 判断是否为数独 isSudoku: function() { var backupSdArr = this.backupSdArr; // console.log(this.sdArr) var board = []; for (var i = 1; i < 10; i++) { var b = []; for (var j = 0; j < 9; j++) { if (backupSdArr[i * 10 + j + 1] == undefined) { b[j] = ""; } else { b[j] = backupSdArr[i * 10 + j + 1]; } } board[i - 1] = b; } // console.log(board) for (let i = 0; i < 9; i++) { let col = new Set() let row = new Set() for (let j = 0; j < 9; j++) { // console.log(board[i][j] ) if (board[i][j] != "") { //判断行 if (!row.has(board[i][j])) { row.add(board[i][j]) } else { return false } } if (board[j][i] != "") { //判断列 if (!col.has(board[j][i])) { col.add(board[j][i]) } else { return false } } } let block = new Set() let x = parseInt(i / 3) * 3 //关键是找到块的x,y坐标 let y = i % 3 * 3 for (let k = 0; k < 9; k++) { if (board[x][y] != "") { //判断块 if (!block.has(board[x][y])) { block.add(board[x][y]) } else { return false } } y++ if ((k + 1) % 3 == 0) { //第4个换行 x += 1 y -= 3 } } } return true; }, createSudoku: function() { // console.log(this.sdArr) this.getInputVals(); // console.log(this.backupSdArr) // console.log(this.sdArr) var isFlag = this.isSudoku(); if (!isFlag) { // 不符合数独 alert("当前数独不符合,请检查~"); return; } this.createEndTime = new Date().getTime(); var createShow = document.getElementById("createShow"); var runningShow = document.getElementById("runningShow"); createShow.style.display = "none"; runningShow.style.display = "block"; }, init: function() { this.createDoms(); this.drawCells(); this.createBlank(this.blankNum); this.createBlankCells(); this.createTime = new Date().getTime(); // 创建时间 }, reset: function() { //重置程序。 this.errorArr = []; $(".sdspan").removeClass('bg_red blankCell'); this.createSdArr(); $(".sdspan[contenteditable=true]").prop('contenteditable', false); this.drawCells(); this.createBlank(this.blankNum); this.createBlankCells(); var createShow = document.getElementById("createShow"); var runningShow = document.getElementById("runningShow"); createShow.style.display = "block"; runningShow.style.display = "none"; }, createSdArr: function() { //生成数独数组。 var that = this; try { this.sdArr = []; this.setThird(2, 2); this.setThird(5, 5); this.setThird(8, 8); var allNum = [1, 2, 3, 4, 5, 6, 7, 8, 9]; outerfor: for (var i = 1; i <= 9; i++) { innerfor: for (var j = 1; j <= 9; j++) { if (this.sdArr[parseInt(i + '' + j)]) { continue innerfor; } var XArr = this.getXArr(j, this.sdArr); var YArr = this.getYArr(i, this.sdArr); var thArr = this.getThArr(i, j, this.sdArr); var arr = getConnect(getConnect(XArr, YArr), thArr); var ableArr = arrMinus(allNum, arr); if (ableArr.length == 0) { this.createSdArr(); return; break outerfor; } var item; //如果生成的重复了就重新生成。 do { item = ableArr[getRandom(ableArr.length) - 1]; } while (($.inArray(item, arr) > -1)); this.sdArr[parseInt(i + '' + j)] = item; } } this.backupSdArr = this.sdArr.slice(); } catch (e) { //如果因为超出浏览器的栈限制出错,就重新运行。 that.createSdArr(); } }, getXArr: function(j, sdArr) { //获取所在行的值。 var arr = []; for (var a = 1; a <= 9; a++) { if (this.sdArr[parseInt(a + "" + j)]) { arr.push(sdArr[parseInt(a + "" + j)]) } } return arr; }, getYArr: function(i, sdArr) { //获取所在列的值。 var arr = []; for (var a = 1; a <= 9; a++) { if (sdArr[parseInt(i + '' + a)]) { arr.push(sdArr[parseInt(i + '' + a)]) } } return arr; }, getThArr: function(i, j, sdArr) { //获取所在三宫格的值。 var arr = []; var cenNum = this.getTh(i, j); var thIndexArr = [cenNum - 11, cenNum - 1, cenNum + 9, cenNum - 10, cenNum, cenNum + 10, cenNum - 9, cenNum + 1, cenNum + 11]; for (var a = 0; a < 9; a++) { if (sdArr[thIndexArr[a]]) { arr.push(sdArr[thIndexArr[a]]); } } return arr; }, getTh: function(i, j) { //获取所在三宫格的中间位坐标。 var cenArr = [22, 52, 82, 25, 55, 85, 28, 58, 88]; var index = (Math.ceil(j / 3) - 1) * 3 + Math.ceil(i / 3) - 1; var cenNum = cenArr[index]; return cenNum; }, setThird: function(i, j) { //为对角线上的三个三宫格随机生成。 var numArr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var sortedNumArr = numArr.sort(function() { return Math.random() - 0.5 > 0 ? -1 : 1 }); var cenNum = parseInt(i + '' + j); var thIndexArr = [cenNum - 11, cenNum - 1, cenNum + 9, cenNum - 10, cenNum, cenNum + 10, cenNum - 9, cenNum + 1, cenNum + 11]; for (var a = 0; a < 9; a++) { this.sdArr[thIndexArr[a]] = sortedNumArr[a]; } }, drawCells: function() { //将生成的数组填写到九宫格 for (var j = 1; j <= 9; j++) { for (var i = 1; i <= 9; i++) { $(".sdli").eq(j - 1).find(".sdspan").eq(i - 1).html(this.sdArr[parseInt(i + '' + j)]); } } }, createBlank: function(num) { //生成指定数量的空白格子的坐标。 var blankArr = []; var numArr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var item; for (var a = 0; a < num; a++) { do { item = parseInt(numArr[getRandom(9) - 1] + '' + numArr[getRandom(9) - 1]); } while ($.inArray(item, blankArr) > -1); blankArr.push(item); } this.blankArr = blankArr; }, createBlankCells: function() { //在创建好的数独中去除一部分格子的值,给用户自己填写。把对应格子变成可编辑,并添加事件。 var blankArr = this.blankArr, len = this.blankArr.length, x, y, dom; for (var i = 0; i < len; i++) { x = parseInt(blankArr[i] / 10); y = blankArr[i] % 10; dom = $(".sdli").eq(y - 1).find(".sdspan").eq(x - 1); dom.attr('contenteditable', true).html('').addClass('blankCell'); this.backupSdArr[blankArr[i]] = undefined; } $(".sdspan[contenteditable=true]").keyup(function(event) { var val = $(this).html(); var reStr = /^[1-9]{1}$/; if (!reStr.test(val)) { $(this).html(''); }; }); }, checkRes: function() { //检测用户输入结果。检测前将输入加入数组。检测单个的时候将这一个的值缓存起来并从数组中删除,检测结束在赋值回去。 var blankArr = this.blankArr, len = this.blankArr.length, x, y, dom, done, temp; this.getInputVals(); this.errorArr.length = 0; for (var i = 0; i < len; i++) { x = parseInt(blankArr[i] / 10); y = blankArr[i] % 10; temp = this.backupSdArr[blankArr[i]]; this.backupSdArr[blankArr[i]] = undefined; this.checkCell(x, y); this.backupSdArr[blankArr[i]] = temp; } done = this.isAllInputed(); if (this.errorArr.length == 0 && done) { var gameTime = ((new Date().getTime()) - this.createEndTime) / 1000; var createTime = (this.createEndTime - this.createTime) / 1000; alert('你胜利了!创建数独耗时:' + createTime + ';完成数独耗时:' + gameTime + "秒!"); $(".bg_red").removeClass('bg_red'); } else { if (!done) { alert("你没有完成游戏!"); } this.showErrors(); } }, checkCell: function(i, j) { //检测一个格子中输入的值,在横竖宫里是否已存在。 var index = parseInt(i + '' + j); var backupSdArr = this.backupSdArr; var XArr = this.getXArr(j, backupSdArr); var YArr = this.getYArr(i, backupSdArr); var thArr = this.getThArr(i, j, backupSdArr); var arr = getConnect(getConnect(XArr, YArr), thArr); var val = parseInt($(".sdli").eq(j - 1).find(".sdspan").eq(i - 1).html()); if ($.inArray(val, arr) > -1) { this.errorArr.push(index); } }, getInputVals: function() { //将用户输入的结果添加到数组中。 var blankArr = this.blankArr, len = this.blankArr.length, i, x, y, dom, theval; for (i = 0; i < len; i++) { x = parseInt(blankArr[i] / 10); y = blankArr[i] % 10; dom = $(".sdli").eq(y - 1).find(".sdspan").eq(x - 1); theval = parseInt(dom.text()) || undefined; this.backupSdArr[blankArr[i]] = theval; } }, isAllInputed: function() { //检测是否全部空格都有输入。 var blankArr = this.blankArr, len = this.blankArr.length, i, x, y, dom; for (i = 0; i < len; i++) { x = parseInt(blankArr[i] / 10); y = blankArr[i] % 10; dom = $(".sdli").eq(y - 1).find(".sdspan").eq(x - 1); if (dom.text() == '') { return false } } return true; }, showErrors: function() { //把错误显示出来。 var errorArr = this.errorArr, len = this.errorArr.length, x, y, dom; $(".bg_red").removeClass('bg_red'); for (var i = 0; i < len; i++) { x = parseInt(errorArr[i] / 10); y = errorArr[i] % 10; dom = $(".sdli").eq(y - 1).find(".sdspan").eq(x - 1); dom.addClass('bg_red'); } }, createDoms: function() { //生成九宫格。 var html = '<ul class="sd clearfix">'; String.prototype.times = String.prototype.times || function(n) { return (new Array(n + 1)).join(this); }; html = html + ('<li class="sdli">' + '<span class="sdspan"></span>'.times(9) + '</li>').times(9) + '</ul>'; $("#sudoku-body").prepend(html); for (var k = 0; k < 9; k++) { $(".sdli:eq(" + k + ") .sdspan").eq(2).addClass('br'); $(".sdli:eq(" + k + ") .sdspan").eq(5).addClass('br'); $(".sdli:eq(" + k + ") .sdspan").eq(3).addClass('bl'); $(".sdli:eq(" + k + ") .sdspan").eq(6).addClass('bl'); } $(".sdli:eq(2) .sdspan,.sdli:eq(5) .sdspan").addClass('bb'); $(".sdli:eq(3) .sdspan,.sdli:eq(6) .sdspan").addClass('bt'); } } //生成随机正整数 function getRandom(n) { return Math.floor(Math.random() * n + 1) } //两个简单数组的并集。 function getConnect(arr1, arr2) { var i, len = arr1.length, resArr = arr2.slice(); for (i = 0; i < len; i++) { if ($.inArray(arr1[i], arr2) < 0) { resArr.push(arr1[i]); } } return resArr; } //两个简单数组差集,arr1为大数组 function arrMinus(arr1, arr2) { var resArr = [], len = arr1.length; for (var i = 0; i < len; i++) { if ($.inArray(arr1[i], arr2) < 0) { resArr.push(arr1[i]); } } return resArr; }
三、仓库地址与体验地址
这里代码片段展示(布局不会弄,太拉了~ alter 弹窗不支持,最好去网站体验~)
<html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,user-scalable=no" /> <title>我的数独我做主 - 掘金 - 南方者</title> </head> <body> <div style="text-align: center;"> <h1>我的数独我做主</h1>by <a target="_blank" href="https://juejin.cn/user/2840793779295133/posts">掘金-南方者</a> <div id="sudoku-body"> <div class="btn-group"> <div style="display: block;" id="createShow"> <button onclick="sd.createSudoku();">创建数独</button> </div> <div style="display: none;" id="runningShow"> <button onclick="sd.checkRes();">完成</button> <button onclick="sd.reset();">重新开始</button> </div> </div> </div> </div> </body> </html>
(没有做适配,很抱歉~ 手机端也不友好。献丑了~ )
大家可以直接来笔者的网站来体验
在线体验(pc端):体验传送门
仓库地址:等建好活动GitHub的要求申请了,就给大家放~(着急想要的可以直接去扒我的网站~) 传送门
文章小尾巴
文章写作、模板、文章小尾巴可参考:《写作“小心思”》
感谢你看到最后,最后再说两点~
①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
(文章内容仅供学习参考,如有侵权,非常抱歉,请立即联系作者删除。)