利用vue开发一个所谓的数独方法实例
1.前言
最近工作中遇到一个问题,因为后台管理系统页面功能暂时没有新的需求,就在想首页放什么东西,最近我想到的就是放个所谓的数独,为什么是所谓的数独,因为规则不同于标准的数独,只要求每一行每一列数字不一样就可以了!这个实例也是基于vue的,代码分享给大家。给大家代码,并不是要让大家直接拷贝代码,而是希望能让大家当做是一个练手的项目,或者学习到知识。如果大家觉得我哪里写得不好,写错了,欢迎指出,让大家交流意见,一起进步。
代码上传到github了:有需要的可以star一下!vue-demos
2.运行效果
3.实现步骤
实现步骤,感觉说得有点绕,建议大家边写边看文章,这样不会懵。或者直接去看源码(sudoku),把源码看懂!这个项目也不复杂!
3-1.准备数据和排版
排版的html+css代码我不多说了,排版很简单,这个相信都难不倒大家的。复杂一点的就是数据的交互!
下面开始第一步,把数独的数据先准备好,数据是什么,大家都知道,就是像下面这样的数据!
排版出来的效果就是下面这样。
html代码如下
<div class="num-table" @mouseleave="hovercol=''" :class="{'shake':isshake}"> <!--遍历每一行--> <div v-for="row,index in allnum" class="num-row chearfix"> <!--遍历行里面的每一列--> <div v-for="num1,indexsub in row" class="num-col"> {{allnumtext[index][indexsub]}} </div> </div> </div>
代码也很简单,如下
mounted(){ let arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]; let row = [], rowcol = 0; for (let i = 0, len = arr1.length; i < len; i++) { row = object.assign([], arr1); this.allnum.push(row); //删除第一个数字并记录下来 rowcol = arr1.splice(0, 1)[0]; //在最后面插入数字 arr1.push(rowcol) } }
大家也可以发现,这个数据,的每一行和每一列的数字都是不同样的!
3-2.打乱行
之后就是随机打乱顺序了,打乱顺序这个得保证一个前提,就是保证每一行每一列的数字都不一样。这样的话,我用了一个简单粗暴的方法-以行或者列为单位,进行打乱。比如,第一行和第三行进行位置交互,第一列和第五列进行位置的交换。下面说下以行为单位的打乱顺序!
行的打乱,很简单,就是随机打乱数组而已!一行代码搞定!
this.allnum.sort((n1, n2) => math.random() - 0.5);
3-3.打乱列
行打乱了,下面进行以列为单位的打乱,这个稍微复杂一点。
大家想下,比如第二列是第五列的值进行交换,那就是每一行的第二个格子的值和第五个格子的值进行交换,那么就需要遍历每一行!来进行交换,至于前面说的第二列和第五列的这个列数,可以用一个函数实现!
下面看代码!
//随机获取两列的索引 function randomtext() { let rondomindex = 0, rondomindexafter = 0; //获取第一列的索引 rondomindex = math.floor(math.random() * 9); function randomdo() { rondomindexafter = math.floor(math.random() * 9); //如果第一列和第二列索引一样,第二列的索引再次重新随机获取 if (rondomindexafter === rondomindex) { randomdo(); } } randomdo(); //返回两列的索引 return [rondomindex, rondomindexafter] } //打乱列 let randomarr = [], nowvalue = 0; //同样遍历9次 for (let i = 0; i < 9; i++) { randomarr = object.assign([], randomtext()); //遍历每一行,给每一行的随机两列交换值 for (let j = 0, len = this.allnum.length; j < len; j++) { //随机两列交换值 nowvalue = this.allnum[j][randomarr[0]]; this.allnum[j][randomarr[0]] = this.allnum[j][randomarr[1]]; this.allnum[j][randomarr[1]] = nowvalue; } }
3-3.随机掏空单元格
掏空单元格就是把一些格子随机设空,然后让玩数独的人。把这些单元格给填上!
需求,我现在实现的就是,每一行有把两个格子设空,这里我的做法是,把每一个格子的坐标先记录下来,然后再从记录的坐标里面随机获取坐标,用获取到的坐标,进行设空!
首先,获取所有点的坐标
//记录所有坐标 let rowtext = '', arrtext = [] for (let i = 0; i < 9; i++) { rowtext = '' for (let j = 0; j < 9; j++) { rowtext += i + '-' + j + ','; } arrtext.push(rowtext.substr(0, rowtext.length - 1)) } console.log(arrtext);
看到这个坐标,大家很容易的知道,数组的一个元素,就是第一行,‘0-0'就是第一行第一个格子。数组最后一个元素,就是最后一行,‘8-8'就是最后一行,最后一个格子,其他如此类推!
下面进行随机掏空,代码也很简单!
//随机掏空 let nowitme = [], _option, nowoption = []; for (let i = 0; i < 9; i++) { //抽取当前行的所有坐标 nowitme = arrtext[i].split(','); nowoption = []; //当前行的随机两个坐标掏空 for (let j = 0; j < 2; j++) { //抽取当前行的随机一个坐标 _option = math.floor(math.random() * nowitme.length); //分割坐标的x,y nowoption = nowitme.splice(_option,1)[0].split("-"); this.allnum[nowoption[0]][nowoption[1]] = ''; } }
这样相信大家都觉得奇怪,下面进行下样式的该写,就是把设空了的格子的样式改一下!.no这个class对应的样式我在css那里写好了,大家注意下。
<!--遍历每一行--> <div v-for="row,index in allnum" class="num-row chearfix"> <!--遍历行里面的每一列--> <!-- no:被掏空数组的样式 --> <div v-for="num1,indexsub in row" :class="{'no':num1===''}" class="num-col"> {{allnumtext[index][indexsub]}} </div> </div>
3-4.显示数字键盘
首先,我简单的用一个流程图说下逻辑,如下
然后关于数字键盘的位置,看下图(数字键盘的样式我不多说了,就是一个是相对定位,一个绝对定位的设置而已)
如上图,我点击的是第一行第三个格子,首先,我期待被点击的格子的样式有所改变,方便我区分,这个不难,用一个class改变样式就可以了,这个可以看下面的代码,我用一个.cur的class控制样式。还有一个就是期待数字键盘在第二行,第四个格子那里出现。这样的话,大家就知道,数字键盘的位置是怎么定位的了!数字键盘的top就是,被点击格子所在的行的索引+160(60是格子的宽高),left就是,被点击格子所在的列的索引+160(60是格子的宽高)。比如上图,第一行第三个格子,top=(0+1)*60+'px',left=(2+1)*60+'px'。
代码如下
<!--遍历每一行--> <div v-for="row,index in allnum" class="num-row chearfix"> <!--遍历行里面的每一列--> <!-- no:被掏空数组的样式 cur:格子被点击时触发,被点击的格子样式 --> <div v-for="num1,indexsub in row" :class="{'no':num1==='', 'cur':currow===index&&indexsub===curcol}" @click="showcheck(index,indexsub)" class="num-col"> {{allnumtext[index][indexsub]}} </div> </div> <!--数字键盘--> <div class="num-check chearfix" :style="{'top':(currow+1)*60+'px','left':(curcol+1)*60+'px'}" v-show="checkshow"> <ul> <li @click="inputtext(1)">1</li> <li @click="inputtext(2)">2</li> <li @click="inputtext(3)">3</li> <li @click="inputtext(4)">4</li> <li @click="inputtext(5)">5</li> <li @click="inputtext(6)">6</li> <li @click="inputtext(7)">7</li> <li @click="inputtext(8)">8</li> <li @click="inputtext(9)">9</li> </ul> </div>
js代码
/** * @description 显示数字键盘 * @param i1 * @param i2 */ showcheck(i1, i2){ //点击的格子是否是被掏空的格子 if (this.allnum[i1][i2] !== '') { return } //点击的格子如果是上一次点击的格子(当前格子) if (i1 === this.currow && i2 === this.curcol) { //隐藏数字键盘,currow和curcol设空 this.checkshow = false; this.currow = ''; this.curcol = ''; } else { //隐藏数字键盘,currow和curcol分别设置成当前的点 this.checkshow = true; this.currow = i1; this.curcol = i2; } },
运行效果
3-5.高亮显示同行同列
这一步很简单,首先,高亮显示行,大家都知道怎么做了,就是行对应的div,设置一个:hover,然后对应设置单元格的样式而已!这个不多说!
然后,高亮显示列,复杂一点,但是也很简单,原理我想大家也知道,就是当鼠标进如格子的时候,在data里面,用一个变量储存进入的格子的列的索引,然后加上判断,如果格子的列的索引等于进入的格子的列的索引。就加上一个class,这里我用.cur-col。
代码如下
<!--遍历每一行--> <div v-for="row,index in allnum" class="num-row clear"> <!--遍历行里面的每一列--> <!-- no:被掏空数组的样式 cur:格子被点击时触发,被点击的格子样式 cur-col:鼠标进入的时候触发,和被点击格子同一列的格子的样式 --> <div v-for="num1,indexsub in row" :class="{'no':num1==='', 'cur':currow===index&&indexsub===curcol, 'cur-col':hovercol===indexsub}" @click="showcheck(index,indexsub)" @mouseenter="hovercol=indexsub;" class="num-col"> {{allnumtext[index][indexsub]}} </div> </div>
运行效果
3-6.填写操作和错误提示
这一步的操作函数,我直接发代码吧,看代码比我说的会清晰些,毕竟说的有点绕
<!--遍历每一行--> <div v-for="row,index in allnum" class="num-row clear"> <!--遍历行里面的每一列--> <!-- no:被掏空数组的样式 cur:格子被点击时触发,被点击的格子样式 cur-col:鼠标进入的时候触发,和被点击格子同一列的格子的样式 err:填写错误的时候触发的样式 --> <div v-for="num1,indexsub in row" :class="{'no':num1==='', 'cur':currow===index&&indexsub===curcol, 'cur-col':hovercol===indexsub, 'err':(optionnow.x===index&&optionnow.y===indexsub)||(optionnowinrow.x===index&&optionnowinrow.y===indexsub)||(optionnowincol.x===index&&optionnowincol.y===indexsub)}" @click="showcheck(index,indexsub)" @mouseenter="hovercol=indexsub;" class="num-col"> {{allnumtext[index][indexsub]}} </div> </div>
js代码
inputtext(_text){ //*****************************检查前的初始化 let _row = this.currow, _col = this.curcol; this.currow = ''; this.curcol = ''; this.iserr = false; this.optionnow = { x: '', y: '', } this.optionnowinrow = { x: '', y: '', } this.optionnowincol = { x: '', y: '', } //*****************************检查行 //根据当前格子进行赋值 this.allnumtext[_row][_col] = _text; let rowcheck = object.assign(this.allnumtext[_row], []); this.checkshow = false; for (let i = 0, len = rowcheck.length; i < len; i++) { //如果值一样,但是坐标不一样,就是填写错误 if (_text === rowcheck[i] && _col !== i) { this.iserr = true; this.isshake = true; //记录当前格子的信息 this.optionnow = { x: _row, y: _col, } //记录和当前格子同一行,以及同一个值的格子的坐标 this.optionnowinrow = { x: _row, y: i, } } } //*****************************检查列 let colcheck = []; //首先把每一行的那一列的数值保存起来 for (let i = 0, len = this.allnumtext.length; i < len; i++) { colcheck.push(this.allnumtext[i][_col]); } //遍历检查 for (let i = 0, len = colcheck.length; i < len; i++) { //如果值一样,但是坐标不一样,就是填写错误 if (_text === colcheck[i] && _row !== i) { this.iserr = true; this.isshake = true; //记录和当前格子同一列,以及同一个值的格子的坐标 this.optionnowincol = { x: i, y: _col, } } } //如果发现的同样的 if (this.iserr) { settimeout(() => { this.isshake = false; }, 1000) return; } //如果数组去重后,长度小于9,就是行没完成 rowcheck = rowcheck.filter(item => item !== ''); if (rowcheck.length !== 9) { //console.log('行没完成') return; } let colocheck = []; //如果数组去重后,长度小于9,就是列没完成 for (let i = 0, len = this.allnumtext.length; i < len; i++) { colocheck = [...new set(this.allnumtext[i])]; colocheck = colocheck.filter(item => item !== ''); if (colocheck.length !== 9) { //console.log('没完成') return; } } alert('挑战成功,但是没奖品'); this.numshow = false; }
上面的代码逻辑,简单说下
1..err 这个class是设置红色字体所使用的,至于判断,就是在inputtext这个函数里面,有optionnow和 optionnowinrow和optionnowincol。只要格子的坐标等于三者其中之一,就会添加这个class,就会变红。
2..isshake这个class是控制,抖动的动画,添加上了之后,在一秒后,要去掉这个class,不然下次添加没有动画效果。
3.在inputtext这个函数里面,我操作的数独列表,并不是之前,提到的allnum,而是利用allnum,深度拷贝生成出的allnumtext(this.allnumtext = json.parse(json.stringify(this.allnum));)
。主要就是为了避免下图的情况!
这样是为了往掏空的格子输入数字的时候,然后那个格子就不能再改了,即使是填错了,都不能改。样式控制也不正确!正确的格式应该是下面这样,即使填入了,格子的样式还是灰色的,这样可以方便的知道哪个格子是当时被掏空的,填写错了,也是可以改的。
4.完整代码
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>vue-所谓的数独</title> <link rel="stylesheet" href="../../reset.css" rel="external nofollow" > <style> li{ list-style-type: none; } .shake { animation: shake-opacity 500ms 1 ease-in-out; } @keyframes shake-opacity { 0% { transform: translate(0px, 0px) rotate(0deg); opacity: 0.6; } 10% { transform: translate(-2px, -1px) rotate(-0.5deg); opacity: 0.5; } 20% { transform: translate(-4px, 4px) rotate(1.5deg); opacity: 0.4; } 30% { transform: translate(-4px, -1px) rotate(-1.5deg); opacity: 0.8; } 40% { transform: translate(-2px, -1px) rotate(-2.5deg); opacity: 0.3; } 50% { transform: translate(-4px, 1px) rotate(-2.5deg); opacity: 0.5; } 60% { transform: translate(-2px, 4px) rotate(0.5deg); opacity: 0.1; } 70% { transform: translate(-3px, 1px) rotate(-0.5deg); opacity: 0.4; } 80% { transform: translate(0px, 0px) rotate(-0.5deg); opacity: 0.5; } 90% { transform: translate(2px, -1px) rotate(-2.5deg); opacity: 0.8; } } .num-box { margin: 0 auto; width: 540px; position: relative; } .num-box .num-check { position: absolute; width: 180px; box-shadow: 0 0 10px 0 #000; left: 0; top: 0; } .num-box .num-check li { box-sizing: border-box; float: left; background: #fff; color: #58b7ff; width: 60px; height: 60px; text-align: center; line-height: 60px; font-size: 24px; border: 1px solid #58b7ff; cursor: pointer; transition: all .5s; } .num-box .num-check li:hover { color: #fff; background: #58b7ff; border: 1px solid #fff; } .num-tips{ color: #333; line-height: 32px; font-size: 16px; } .num-table{ position: relative; } .num-row { font-size: 0; } .num-row:hover .num-col, .num-row:hover .num-col.no, .num-row:hover .num-col.cur-col { background: #0068b7; } .num-row .num-col { width: 60px; height: 60px; line-height: 60px; float: left; box-sizing: border-box; text-align: center; background: #58b7ff; color: #fff; font-size: 24px; font-weight: bold; border: 1px solid #ccc; } .num-row .num-col.no { background: #ccc; border: 1px solid #fff; } .num-row .num-col.err { color: #ff4949; } .num-row .num-col.cur-col { background: #0068b7; } .num-row .num-col.cur { background: #fff !important; } </style> </head> <body> <div class="num-box" v-show="numshow" id="num"> <div class="num-tips"> <p>所谓的数独:规则</p> <p>1.每一行数字不重复</p> <p>2.每一列数字不重复</p> </div> <div class="num-table" @mouseleave="hovercol=''" :class="{'shake':isshake}"> <!--遍历每一行--> <div v-for="row,index in allnum" class="num-row clear"> <!--遍历行里面的每一列--> <!-- no:被掏空数组的样式 cur:格子被点击时触发,被点击的格子样式 cur-col:鼠标进入的时候触发,和被点击格子同一列的格子的样式 err:填写错误的时候触发的样式 --> <div v-for="num1,indexsub in row" :class="{'no':num1==='', 'cur':currow===index&&indexsub===curcol, 'cur-col':hovercol===indexsub, 'err':(optionnow.x===index&&optionnow.y===indexsub)||(optionnowinrow.x===index&&optionnowinrow.y===indexsub)||(optionnowincol.x===index&&optionnowincol.y===indexsub)}" @click="showcheck(index,indexsub)" @mouseenter="hovercol=indexsub;" class="num-col"> {{allnumtext[index][indexsub]}} </div> </div> <!--数字键盘--> <div class="num-check clear" :style="{'top':(currow+1)*60+'px','left':(curcol+1)*60+'px'}" v-show="checkshow"> <ul> <li @click="inputtext(1)">1</li> <li @click="inputtext(2)">2</li> <li @click="inputtext(3)">3</li> <li @click="inputtext(4)">4</li> <li @click="inputtext(5)">5</li> <li @click="inputtext(6)">6</li> <li @click="inputtext(7)">7</li> <li @click="inputtext(8)">8</li> <li @click="inputtext(9)">9</li> </ul> </div> </div> </div> </body> <script src="../vue.min.js"></script> <script> new vue({ el:'#num', data:{ name: 'welcome', testtext: '欢迎来到', nowindex: 0, allnum: [],//数字排列 answer: [],//所有答案的坐标点 allnumtext: [],//数字,包括输入后的数字 currow: '',//当前格子所在的行的索引 curcol: '',//当前格子所在的列的索引 checkshow: false,//数字键盘的显示 hovercol: '',//鼠标进去的当前列 hoverrow: 0,//鼠标进入的当前行 numshow: true,//数独的显示 optionnow: {},//输入后的格子的坐标 optionnowinrow: {},//和输入后的格子在同一行,并且同样值的格子的坐标 optionnowincol: {},//和输入后的格子在同一列,并且同样值的格子的坐标 iserr: false,//是否输入错误后 isshake: false//是否显示震动的样式 }, methods: { /** * @description 显示数字键盘 * @param i1 * @param i2 */ showcheck(i1, i2){ //点击的格子是否是被掏空的格子 if (this.allnum[i1][i2] !== '') { return } //点击的格子如果是上一次点击的格子(当前格子) if (i1 === this.currow && i2 === this.curcol) { //隐藏数字键盘,currow和curcol设空 this.checkshow = false; this.currow = ''; this.curcol = ''; } else { //隐藏数字键盘,currow和curcol分别设置成当前的点 this.checkshow = true; this.currow = i1; this.curcol = i2; } }, inputtext(_text){ //*****************************检查前的初始化 let _row = this.currow, _col = this.curcol; this.currow = ''; this.curcol = ''; this.iserr = false; this.optionnow = { x: '', y: '', } this.optionnowinrow = { x: '', y: '', } this.optionnowincol = { x: '', y: '', } //*****************************检查行 //保存当前格子的值 this.allnumtext[_row][_col] = _text; let rowcheck = object.assign(this.allnumtext[_row], []); this.checkshow = false; for (let i = 0, len = rowcheck.length; i < len; i++) { //如果值一样,但是坐标不一样,就是填写错误 if (_text === rowcheck[i] && _col !== i) { this.iserr = true; this.isshake = true; //记录当前格子的信息 this.optionnow = { x: _row, y: _col } //记录和当前格子同一行,以及同一个值的格子的坐标 this.optionnowinrow = { x: _row, y: i } } } //*****************************检查列 let colcheck = []; //首先把每一行的那一列的数值保存起来 for (let i = 0, len = this.allnumtext.length; i < len; i++) { colcheck.push(this.allnumtext[i][_col]); } //遍历检查 for (let i = 0, len = colcheck.length; i < len; i++) { //如果值一样,但是坐标不一样,就是填写错误 if (_text === colcheck[i] && _row !== i) { this.iserr = true; this.isshake = true; //记录和当前格子同一列,以及同一个值的格子的坐标 this.optionnowincol = { x: i, y: _col } } } //如果发现的同样的 if (this.iserr) { settimeout(() => { this.isshake = false; }, 1000) return; } //如果数组去重后,长度小于9,就是行没完成 rowcheck = rowcheck.filter(item => item !== ''); if (rowcheck.length !== 9) { console.log('行没完成') return; } let colocheck = []; //如果数组去重后,长度小于9,就是列没完成 for (let i = 0, len = this.allnumtext.length; i < len; i++) { colocheck = [...new set(this.allnumtext[i])]; colocheck = colocheck.filter(item => item !== ''); if (colocheck.length !== 9) { console.log('没完成') return; } } alert('挑战成功,但是没奖品'); this.numshow = false; } }, mounted(){ let arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]; let row = [], rowcol = 0; for (let i = 0, len = arr1.length; i < len; i++) { row = object.assign([], arr1); this.allnum.push(row); rowcol = arr1.splice(0, 1)[0]; arr1.push(rowcol) } //打乱行 this.allnum.sort((n1, n2) => math.random() - 0.5); //随机获取两列的索引 function randomtext() { let rondomindex = 0, rondomindexafter = 0; //获取第一列的索引 rondomindex = math.floor(math.random() * 9); function randomdo() { rondomindexafter = math.floor(math.random() * 9); //如果第一列和第二列索引一样,第二列的索引再次重新获取 if (rondomindexafter === rondomindex) { randomdo(); } } randomdo(); //返回两列的索引 return [rondomindex, rondomindexafter] } //打乱列 let randomarr = [], nowvalue = 0; //同样遍历9次 for (let i = 0; i < 9; i++) { randomarr = object.assign([], randomtext()); //遍历每一行,给每一行的随机两列交换值 for (let j = 0, len = this.allnum.length; j < len; j++) { //随机两列交换值 nowvalue = this.allnum[j][randomarr[0]]; this.allnum[j][randomarr[0]] = this.allnum[j][randomarr[1]]; this.allnum[j][randomarr[1]] = nowvalue; } } //记录所有坐标 let rowtext = '', arrtext = [] for (let i = 0; i < 9; i++) { rowtext = '' for (let j = 0; j < 9; j++) { rowtext += i + '-' + j + ','; } arrtext.push(rowtext.substr(0, rowtext.length - 1)) } console.log(arrtext); //随机掏空 let nowitme = [], _option, nowoption = []; for (let i = 0; i < 9; i++) { //抽取当前行的所有坐标 nowitme = arrtext[i].split(','); nowoption = []; //当前行的随机两个坐标掏空 for (let j = 0; j < 2; j++) { //抽取当前行的随机一个坐标 _option = math.floor(math.random() * nowitme.length); //分割坐标的x,y nowoption = nowitme.splice(_option,1)[0].split("-"); this.allnum[nowoption[0]][nowoption[1]] = ''; } } //深度拷贝数独的数字 this.allnumtext = json.parse(json.stringify(this.allnum)); } }) </script> </html>
reset.css和vue.min.js大家自行到github下载!
5.小结
好了,用vue做的所谓的数独,就写到这里了,主要就是逻辑有点绕,其它的问题相信都难不倒大家。这个实例比之前快速入门的三个小实例要麻烦一点,但是也很好理解!大家只要稍微看下估计都不难理解!最后,如果大家觉得文章写得不好,哪里写错了,欢迎给建议或者指点下迷津。期待和大家交流意见,共同进步!
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。