您好,登錄后才能下訂單哦!
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" : 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" : 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做的所謂的數獨,就寫到這里了,主要就是邏輯有點繞,其它的問題相信都難不倒大家。這個實例比之前快速入門的三個小實例要麻煩一點,但是也很好理解!大家只要稍微看下估計都不難理解!最后,如果大家覺得文章寫得不好,哪里寫錯了,歡迎給建議或者指點下迷津。期待和大家交流意見,共同進步!
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。