>  기사  >  웹 프론트엔드  >  vue를 사용하여 스도쿠 게임을 개발하는 방법

vue를 사용하여 스도쿠 게임을 개발하는 방법

亚连
亚连원래의
2018-06-19 14:28:193700검색

스도쿠는 18세기 스위스에서 시작된 수학 게임으로 종이와 펜을 사용하여 계산을 수행하는 논리 게임입니다. 다음 글에서는 vue를 사용하여 소위 스도쿠를 개발하는 방법에 대한 관련 정보를 주로 소개합니다. 필요한 친구들이 참고할 수 있도록 샘플 코드를 통해 자세히 소개합니다.

1. 머리말

최근 직장에서 문제가 발생했습니다. 백엔드 관리 시스템의 페이지 기능에 대한 새로운 수요가 없어서, 최근 홈페이지에 무엇을 올려야 할지 고민 중이었습니다. 소위 스도쿠라고 불리는 것을 생각했는데 왜 스도쿠라고 불리는 걸까요? 규칙이 표준 스도쿠와 다르기 때문에 각 행과 열의 숫자만 다릅니다. 이 예제도 vue를 기반으로 하며 코드는 모든 사람과 공유됩니다. 코드를 제공하는 목적은 코드를 직접 복사하도록 하는 것이 아니라 실습 프로젝트로 취급하거나 지식을 배우기 위한 것입니다. 제가 쓴 내용이 서툴거나 잘못되었다고 생각되시면 모두가 함께 의견을 교환하고 발전할 수 있도록 지적해 주시기 바랍니다.

코드가 github에 업로드되었습니다: 필요하다면 별표 표시를 해주세요! vue-demos

2. 실행 효과

3. 구현 단계

글을 작성하면서 조금 복잡해지는 느낌이 들지 않도록 읽어보세요. 혼란스러운. 아니면 소스코드(스도쿠)로 직접 가서 소스코드를 이해해보세요! 이 프로젝트도 복잡하지 않아요!

3-1. 데이터 및 조판 준비

조판을 위한 html+css 코드에 대해서는 자세히 설명하지 않겠습니다. 이는 모든 사람에게 문제가 되지 않을 것입니다. 조금 더 복잡한 것은 데이터의 상호작용입니다!
첫 번째 단계는 먼저 스도쿠 데이터를 준비하는 것입니다. 데이터가 무엇인지는 다들 아시겠지만 아래와 같은 데이터입니다!

레이아웃 효과는 다음과 같습니다.

html 코드는 다음과 같습니다

<p class="num-table" @mouseleave="hoverCol=&#39;&#39;" :class="{&#39;shake&#39;:isShake}">
 <!--遍历每一行-->
 <p v-for="row,index in allNum" class="num-row chearfix">
 <!--遍历行里面的每一列-->
 <p v-for="num1,indexSub in row" class="num-col">
  {{allNumText[index][indexSub]}}
 </p>
 </p>
</p>

코드도 매우 간단합니다. 다음과 같습니다

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. 행을 섞은 후

순서를 무작위로 섞습니다. 순서를 섞는 전제 조건은 각 행과 열의 숫자가 다른지 확인하는 것입니다. 이번 경우에는 행이나 열 단위로 간단하고 투박한 방식의 중단(disrupt) 방식을 사용했습니다. 예를 들어, 첫 번째 행과 세 번째 행이 서로 상호작용하고, 첫 번째 열과 다섯 번째 열의 위치가 바뀌는 것입니다. 행동 단위의 무질서한 순서에 대해 이야기해 봅시다!

행 섞기는 매우 간단합니다. 배열을 무작위로 섞는 것뿐입니다! 코드 한 줄이면 끝!

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 = &#39;&#39;, arrText = []
for (let i = 0; i < 9; i++) {
 rowText = &#39;&#39;
 for (let j = 0; j < 9; j++) {
  rowText += i + &#39;-&#39; + j + &#39;,&#39;;
 }
 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(&#39;,&#39;);
 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]] = &#39;&#39;;
 }
}

모두들 이상하다고 생각하실 거라 생각합니다. 이제 빈 그리드의 스타일을 바꾸는 스타일을 작성해 보겠습니다. CSS에 .no 클래스에 해당하는 스타일을 작성해 두었습니다.

<!--遍历每一行-->
<p v-for="row,index in allNum" class="num-row chearfix">
 <!--遍历行里面的每一列-->
 <!--
  no:被掏空数组的样式
 -->
 <p v-for="num1,indexSub in row" :class="{&#39;no&#39;:num1===&#39;&#39;}" class="num-col">
  {{allNumText[index][indexSub]}}
 </p>
</p>

3-4.显示数字键盘

首先,我简单的用一个流程图说下逻辑,如下

然后关于数字键盘的位置,看下图(数字键盘的样式我不多说了,就是一个是相对定位,一个绝对定位的设置而已)

如上图,我点击的是第一行第三个格子,首先,我期待被点击的格子的样式有所改变,方便我区分,这个不难,用一个class改变样式就可以了,这个可以看下面的代码,我用一个.cur的class控制样式。还有一个就是期待数字键盘在第二行,第四个格子那里出现。这样的话,大家就知道,数字键盘的位置是怎么定位的了!数字键盘的top就是,被点击格子所在的行的索引+160(60是格子的宽高),left就是,被点击格子所在的列的索引+160(60是格子的宽高)。比如上图,第一行第三个格子,top=(0+1)*60+'px',left=(2+1)*60+'px'。

代码如下

<!--遍历每一行-->
 <p v-for="row,index in allNum" class="num-row chearfix">
  <!--遍历行里面的每一列-->
  <!--
   no:被掏空数组的样式
   cur:格子被点击时触发,被点击的格子样式
  -->
  <p v-for="num1,indexSub in row"
    :class="{&#39;no&#39;:num1===&#39;&#39;,
    &#39;cur&#39;:curRow===index&&indexSub===curCol}"
    @click="showCheck(index,indexSub)" class="num-col">
   {{allNumText[index][indexSub]}}
  </p>
 </p>
<!--数字键盘-->
<p class="num-check chearfix" :style="{&#39;top&#39;:(curRow+1)*60+&#39;px&#39;,&#39;left&#39;:(curCol+1)*60+&#39;px&#39;}"
  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>
</p>

js代码

/**
 * @description 显示数字键盘
 * @param i1
 * @param i2
 */
showCheck(i1, i2){
 //点击的格子是否是被掏空的格子
 if (this.allNum[i1][i2] !== &#39;&#39;) {
  return
 }
 //点击的格子如果是上一次点击的格子(当前格子)
 if (i1 === this.curRow && i2 === this.curCol) {
  //隐藏数字键盘,curRow和curCol设空
  this.checkShow = false;
  this.curRow = &#39;&#39;;
  this.curCol = &#39;&#39;;
 }
 else {
  //隐藏数字键盘,curRow和curCol分别设置成当前的点
  this.checkShow = true;
  this.curRow = i1;
  this.curCol = i2;
 }
},

运行效果

3-5.高亮显示同行同列

这一步很简单,首先,高亮显示行,大家都知道怎么做了,就是行对应的p,设置一个:hover,然后对应设置单元格的样式而已!这个不多说!

然后,高亮显示列,复杂一点,但是也很简单,原理我想大家也知道,就是当鼠标进如格子的时候,在data里面,用一个变量储存进入的格子的列的索引,然后加上判断,如果格子的列的索引等于进入的格子的列的索引。就加上一个class,这里我用.cur-col。

代码如下

<!--遍历每一行-->
<p v-for="row,index in allNum" class="num-row clear">
 <!--遍历行里面的每一列-->
 <!--
  no:被掏空数组的样式
  cur:格子被点击时触发,被点击的格子样式
  cur-col:鼠标进入的时候触发,和被点击格子同一列的格子的样式
 -->
 <p v-for="num1,indexSub in row"
   :class="{&#39;no&#39;:num1===&#39;&#39;,
   &#39;cur&#39;:curRow===index&&indexSub===curCol,
   &#39;cur-col&#39;:hoverCol===indexSub}"
   @click="showCheck(index,indexSub)" @mouseenter="hoverCol=indexSub;" class="num-col">
  {{allNumText[index][indexSub]}}
 </p>
</p>

运行效果

3-6.填写操作和错误提示

这一步的操作函数,我直接发代码吧,看代码比我说的会清晰些,毕竟说的有点绕

<!--遍历每一行-->
<p v-for="row,index in allNum" class="num-row clear">
 <!--遍历行里面的每一列-->
 <!--
  no:被掏空数组的样式
  cur:格子被点击时触发,被点击的格子样式
  cur-col:鼠标进入的时候触发,和被点击格子同一列的格子的样式
  err:填写错误的时候触发的样式
 -->
 <p v-for="num1,indexSub in row"
   :class="{&#39;no&#39;:num1===&#39;&#39;,
   &#39;cur&#39;:curRow===index&&indexSub===curCol,
   &#39;cur-col&#39;:hoverCol===indexSub,
   &#39;err&#39;:(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]}}
 </p>
</p>

js代码

inputText(_text){
 //*****************************检查前的初始化
 let _row = this.curRow, _col = this.curCol;
 this.curRow = &#39;&#39;;
 this.curCol = &#39;&#39;;
 this.isErr = false;
 this.optionNow = {
  x: &#39;&#39;,
  y: &#39;&#39;,
 }
 this.optionNowInRow = {
  x: &#39;&#39;,
  y: &#39;&#39;,
 }
 this.optionNowInCol = {
  x: &#39;&#39;,
  y: &#39;&#39;,
 }
 //*****************************检查行
 //根据当前格子进行赋值
 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 !== &#39;&#39;);
 if (rowCheck.length !== 9) {
  //console.log(&#39;行没完成&#39;)
  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 !== &#39;&#39;);
  if (coloCheck.length !== 9) {
   //console.log(&#39;没完成&#39;)
   return;
  }
 }
 alert(&#39;挑战成功,但是没奖品&#39;);
 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.完整代码




 
 vue-所谓的数独
 
 


所谓的数独:规则

1.每一行数字不重复

2.每一列数字不重复

{{allNumText[index][indexSub]}}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

<script> new Vue({ el:&#39;#num&#39;, data:{ name: &#39;welcome&#39;, testText: &#39;欢迎来到&#39;, nowIndex: 0, allNum: [],//数字排列 answer: [],//所有答案的坐标点 allNumText: [],//数字,包括输入后的数字 curRow: &#39;&#39;,//当前格子所在的行的索引 curCol: &#39;&#39;,//当前格子所在的列的索引 checkShow: false,//数字键盘的显示 hoverCol: &#39;&#39;,//鼠标进去的当前列 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] !== &#39;&#39;) { return } //点击的格子如果是上一次点击的格子(当前格子) if (i1 === this.curRow && i2 === this.curCol) { //隐藏数字键盘,curRow和curCol设空 this.checkShow = false; this.curRow = &#39;&#39;; this.curCol = &#39;&#39;; } else { //隐藏数字键盘,curRow和curCol分别设置成当前的点 this.checkShow = true; this.curRow = i1; this.curCol = i2; } }, inputText(_text){ //*****************************检查前的初始化 let _row = this.curRow, _col = this.curCol; this.curRow = &#39;&#39;; this.curCol = &#39;&#39;; this.isErr = false; this.optionNow = { x: &#39;&#39;, y: &#39;&#39;, } this.optionNowInRow = { x: &#39;&#39;, y: &#39;&#39;, } this.optionNowInCol = { x: &#39;&#39;, y: &#39;&#39;, } //*****************************检查行 //保存当前格子的值 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 !== &#39;&#39;); if (rowCheck.length !== 9) { console.log(&#39;行没完成&#39;) 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 !== &#39;&#39;); if (coloCheck.length !== 9) { console.log(&#39;没完成&#39;) return; } } alert(&#39;挑战成功,但是没奖品&#39;); 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) =&gt; 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 = &#39;&#39;, arrText = [] for (let i = 0; i < 9; i++) { rowText = &#39;&#39; for (let j = 0; j < 9; j++) { rowText += i + &#39;-&#39; + j + &#39;,&#39;; } 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(&#39;,&#39;); 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]] = &#39;&#39;; } } //深度拷贝数独的数字 this.allNumText = JSON.parse(JSON.stringify(this.allNum)); } }) </script>

reset.css和vue.min.js大家自行到github下载!

5.小结

好了,用vue做的所谓的数独,就写到这里了,主要就是逻辑有点绕,其它的问题相信都难不倒大家。这个实例比之前快速入门的三个小实例要麻烦一点,但是也很好理解!大家只要稍微看下估计都不难理解!最后,如果大家觉得文章写得不好,哪里写错了,欢迎给建议或者指点下迷津。期待和大家交流意见,共同进步!

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

使用js如何实现从新赋值

在js中如何将canvas生成图片保存

js에서 양방향 바인딩을 구현하는 방법

웹팩의 좀 더 실용적인 기능을 자세히 소개

jQuery를 사용해 메뉴 추가, 제거 기능을 구현하는 방법

위 내용은 vue를 사용하여 스도쿠 게임을 개발하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.