php数独游戏开发,使用vue如何开发数独游戏

数独是源自18世纪瑞士的一种数学游戏,是一种运用纸、笔进行演算的逻辑游戏。下面这篇文章主要给大家介绍了关于利用vue开发一个所谓的数独的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下。

1.前言

最近工作中遇到一个问题,因为后台管理系统页面功能暂时没有新的需求,就在想首页放什么东西,最近我想到的就是放个所谓的数独,为什么是所谓的数独,因为规则不同于标准的数独,只要求每一行每一列数字不一样就可以了!这个实例也是基于vue的,代码分享给大家。给大家代码,并不是要让大家直接拷贝代码,而是希望能让大家当做是一个练手的项目,或者学习到知识。如果大家觉得我哪里写得不好,写错了,欢迎指出,让大家交流意见,一起进步。

代码上传到github了:有需要的可以star一下!vue-demos

2.运行效果

ee914cd27a58d7d52b564c5545da65c8.gif

3.实现步骤

实现步骤,感觉说得有点绕,建议大家边写边看文章,这样不会懵。或者直接去看源码(sudoku),把源码看懂!这个项目也不复杂!

3-1.准备数据和排版

排版的html+css代码我不多说了,排版很简单,这个相信都难不倒大家的。复杂一点的就是数据的交互!

下面开始第一步,把数独的数据先准备好,数据是什么,大家都知道,就是像下面这样的数据!

f648377867debe4ab2aa0e8c5c304bbe.png

排版出来的效果就是下面这样。

1f9d3287ce56a2bbd5b5d5ede4a67c7d.png

html代码如下

{{allNumText[index][indexSub]}}

代码也很简单,如下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);

59caf9796b304757de4e234fc8132718.png

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;

}

}

da7ab7aec3c6a92735cb3b27e9f4353c.png

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);

b31a22f351fffdba2d34da6b64bbefab.png

看到这个坐标,大家很容易的知道,数组的一个元素,就是第一行,‘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]] = '';

}

}

bc68ec2fc1908391e8e412dc2a18cbf5.png

这样相信大家都觉得奇怪,下面进行下样式的该写,就是把设空了的格子的样式改一下!.no这个class对应的样式我在css那里写好了,大家注意下。

{{allNumText[index][indexSub]}}

f0c0f437a0d645d2a3bee17e17dcdc37.png

3-4.显示数字键盘

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

20ca68709766a8fb448340d669dfe07a.png

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

e61502caa6592b13fe9a602da070a265.png

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

代码如下

:class="{'no':num1==='',

'cur':curRow===index&&indexSub===curCol}"

@click="showCheck(index,indexSub)" class="num-col">

{{allNumText[index][indexSub]}}

v-show="checkShow">

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

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;

}

},

运行效果

2e0f2b32fb6d5e08d9d63e751747e460.png

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

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

57c9dfa6c217c287429f6e8d03b3dcec.png

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

代码如下

: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]}}

运行效果

12d69f3836612058cf3d0c695c174c2b.png

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

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

: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]}}

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;

}

5717ace995d0fa9399220340dce03b85.gif

上面的代码逻辑,简单说下

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));) 。主要就是为了避免下图的情况!

5aa3b0df4edae4035ead0d2b7df08a77.gif

这样是为了往掏空的格子输入数字的时候,然后那个格子就不能再改了,即使是填错了,都不能改。样式控制也不正确!正确的格式应该是下面这样,即使填入了,格子的样式还是灰色的,这样可以方便的知道哪个格子是当时被掏空的,填写错了,也是可以改的。

4.完整代码

vue-所谓的数独

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;

}

所谓的数独:规则

1.每一行数字不重复

2.每一列数字不重复

: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]}}

v-show="checkShow">

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

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));

}

})

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

5.小结

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

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

相关文章:

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注