JQuery实现1024小游戏

最近用Jqery写了一个1024小游戏,由于是第一次写小游戏,所以就选了一个基础的没什么难度游戏。具体实现如下:

在慕课网学习制作了2048小游戏,代码放在Github,效果图如下

首先在开发时将整个游戏分成两层(自认为),底层是游戏的数据结构以及对数据的操作,上层是显示出来的用户界面。底层选择使用一个4x4的二维数组,整个游戏的数据操作都围绕着这个二维数组进行。

图片 1

【一】游戏基础界面

 1 <div id="game"> 2 <div id="header"> 3 <h1>1024</h1> 4 <button id="newGame">开始新的游戏</button> 5 <p>分数:0&nbsp;&nbsp;&nbsp;&nbsp;最高分:0</p> 6 <div id="movescore"><p>+16</p></div> 7 </div> 8 <div id="container"> 9 <div class="cell" id="cell-0-0"></div>10 <div class="cell" id="cell-0-1"></div>11 <div class="cell" id="cell-0-2"></div>12 <div class="cell" id="cell-0-3"></div>13 <div class="cell" id="cell-1-0"></div>14 <div class="cell" id="cell-1-1"></div>15 <div class="cell" id="cell-1-2"></div>16 <div class="cell" id="cell-1-3"></div>17 <div class="cell" id="cell-2-0"></div>18 <div class="cell" id="cell-2-1"></div>19 <div class="cell" id="cell-2-2"></div>20 <div class="cell" id="cell-2-3"></div>21 <div class="cell" id="cell-3-0"></div>22 <div class="cell" id="cell-3-1"></div>23 <div class="cell" id="cell-3-2"></div>24 <div class="cell" id="cell-3-3"></div>25 </div>26 <div class="gameover">27 <div id="gameoverText">28 <p></p>29 </div>30 <p id="gameoverScore"></p>31 <div id="reStart">32 <button id="reStartBtn">再玩一次</button>33 </div>34 </div>35 </div>

CSS:

 1 *{ 2 margin: 0; 3 padding: 0; 4 } 5 #game{ 6 font-family: Arial; 7 margin: 0 auto; 8 text-align: center; 9 }10 #header{11 margin: 20px;12 }13 #header a{14 font-family: Arial;15 text-decoration: none;/*设置 h1、h2、h3、h4 元素的文本修饰*/16 display: block;17 color: white;18 margin: 20px auto;19 width: 125px;20 height: 35px;21 text-align: center;22 line-height: 40px;23 background-color: #8f7a66;24 border-radius: 10px;25 font-size: 15px;26 }27 #header p{28 font-family: Arial;29 font-size: 20px;30 }31 #container{32 width: 460px;33 height: 460px;34 background-color: #bbada0;35 margin: 0 auto;36 border-radius: 10px;37 position: relative;38 padding: 20px;39 }40 .cell{41 width: 100px;42 height: 100px;43 border-radius: 6px;44 background-color: #ccc0b3;45 position: absolute;46 font-size: 3.5em;47 font-weight:700;48 text-align: center;49 line-height:100px;50 }51 #newGame{52 width: 120px;53 height: 30px;54 border-radius: 5px;55 border: 1px solid rgb(143,122,102);56 background-color: rgb(143,122,102);57 color: white;58 margin-top: 10px;59 margin-bottom: 10px;60 }61 .gameover{62 width: 100%;63 height: 500px;64 background-color: rgba(255,255,255,0.5);65 position: absolute;66 top:153px;67 display: none;68 }69 #gameoverText{70 font-size: 4em;71 font-weight: 600;72 color: #363636;73 text-align: center;74 opacity: 1;75 padding-top: 10%;76 }77 #reStartBtn{78 width: 100px;79 height: 40px;80 border-radius: 5px;81 border: 1px solid rgb(143,122,102);82 background-color: rgb(143,122,102);83 color: white;84 margin-top: 3%;85 }86 #gameoverScore{87 font-size: 1.5em;88 }89 #movescore{90 position: absolute;91 text-align: center;92 padding-left: 45%;93 top:110px;94 font-weight: 600;95 display: none;96 }

此时界面的16个格子是重叠在一起的,给每个格子写css比较麻烦,所以通过js循环进行设置:

1 //初始化绘制表格2 function printTab(){3 for(var i=0;i<4;i++){4 for(var j=0;j<4;j++){5 var cell=$('#cell-'+i+'-'+j);6 cell.css({top:(20+i*120),left:(20+j*120)});7 }8 }9 }

不同的数字显示不同的背景颜色与文字颜色:

 1 function getBackgroundColor(number){ 2 switch (number) { 3 case 2:return "#eee4da";break; 4 case 4:return "#ede0c8";break; 5 case 8:return "#f2b179";break; 6 case 16:return "#f59563";break; 7 case 32:return "#f67c5f";break; 8 case 64:return "#f65e3b";break; 9 case 128:return "#edcf72";break;10 case 256:return "#edcc61";break;11 case 512:return "#9c0";break;12 case 1024:return "#33b5e5";break;13 case 2048:return "#09c";break;14 case 4096:return "#a6c";break;15 case 8192:return "#93c";break;16 }17 }18 // 设置相应数字的文字颜色19 function getColor(number){20 if (number <= 4) {21 return "#776e65"22 }23 return "white";24 }

每次操作后根据当前二维数组进行重绘画面:

 1 //根据二维数组绘制画面 2 function rePrint(checkerboard){ 3 for(var i=0;i<4;i++){ 4 for(var j=0;j<4;j++){ 5 if(checkerboard[i][j]!=0){ 6 var printCell=$('#cell-'+i+'-'+j); 7 printCell.css('background-color',getBackgroundColor(checkerboard[i][j])); 8 printCell.css('color',getColor(checkerboard[i][j])); 9 printCell.text(checkerboard[i][j]);10 if(checkerboard[i][j]>=1024){11 printCell.css('font-size','2.5em');12 printCell.css('font-weight','500');13 }14 }else{15 var printCell=$('#cell-'+i+'-'+j);16 printCell.css('background-color','#ccc0b3');17 printCell.css('color','black');18 printCell.text('');19 }20 }21 }22 }

效果图

 【二】游戏逻辑

除了需要定义一个二维数组checkerboard,还需要定义一个存总分的变量score,一个存每次操作得分的变量addScore,一个记录键盘是否可以操作的变量ableKeyDown,0可以响应键盘操作,1禁止键盘操作。

<a href="#p1">一、 初始化棋盘格(绝对定位)</a>
<a href="#p2">二、并随机两个2/4
数字,显示在棋盘格中的随机位置</a>
<a
href="#p3">三、按下方向键,执行对应的方向移动函数,如果可以移动,则移动后随机一个新数,并判断游戏是否结束</a>
<a
href="#p4">四、避免在一次移动操作中,同一个单元格发生超过一次相加</a>
<a href="#p5">五、移动端响应式</a>
<a href="#p6">六、移动端操作</a>
<a href="#p7">七、定制——小白成长记</a>
<h3>一、初始化棋盘格(绝对定位)</h3>

①游戏初始化

初始化封装成一个函数方便后续的【开始新的游戏】以及【再玩一次】功能。

 1 //初始化游戏 2 function newgame(){ 3 ableKeyDown=0; 4 score=0; 5 checkerboard=[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]; 6 $('#score').text('0'); 7 $('#maxScore').text(window.localStorage.getItem("maxScore")); 8 rePrint(checkerboard); 9 randNum(checkerboard);10 randNum(checkerboard);11 }
  1. 实现棋盘格布局。二维循环获取HTML中的十六个单元格#grid-cell-i-j,根据单元格的行号确定绝对定位的top值,根据列号确定绝对定位的left值。
  2. 创建一个二维数组board[i][j],并将其中的16个数组项全部初始化赋值为0。
  3. 更新二维数组board中的内容。二维循环创建十六个数字容器number-cell并append到单元格容器grid-container中;获取每个数值容器number-cell中的值并判断,如果为零,则根据二维索引值绝对定位在对应位置(本来就不显示,是不是可以省略位置的设置,测试发现不可以,如果不先绝对定位到相应位置,后面随机数显示就会像是从棋盘左上角产生后移动到随机位置的感觉,不是原版中的在随机单元格中间产生),宽高设为0,不显示;非零,则根据二维索引值绝对定位在对应位置,并根据非零数值确定文本和背景颜色,并将board[i][j]赋值到对应的number-cell-i-j。

②在随即位置生成2或4

根据游戏规则,在游戏刚开始时会在两个随即位置分别生成2或4,在每次操作后若游戏没结束,会在一个空的位置随机生成一个2或4,将这一功能封装成一个函数,Math.random()生成的随机数范围是[0,1),由于二维数组范围是[0,3],所以通过Math.floor(Math.random()*4)将得到0-3的随机数。另外需要随机得到2或4,定义一个数组initRandNum=[2,4],只需随机得到initRandNum[0]或initRandNum[1]即可,所以同理使用Math.floor(Math.random()*2)随机得到1或2即可。

 1 function randNum(checkerboard){//在随机位置随机产生2或4 2 var randX = Math.floor(Math.random()*4); 3 var randY = Math.floor(Math.random()*4); 4 var initRandNum=[2,4]; 5 var randNum=Math.floor(Math.random()*2); 6 var randVal=initRandNum[randNum]; 7 while(true){ 8 if(checkerboard[randX][randY]==0){ 9 break;10 }else{11 var randX = Math.floor(Math.random()*4);12 var randY = Math.floor(Math.random()*4);13 }14 }15 checkerboard[randX][randY]=randVal;16 printRandNum(randX,randY,randVal);//将randNum()绘制出来17 }

在得到上述随即位置的2或4后需要将其绘制出来:

1 function printRandNum(randX,randY,randVal){2 var printRandCell=$('#cell-'+randX+'-'+randY);3 printRandCell.css('background-color',getBackgroundColor(randVal));4 printRandCell.css('color',getColor(randVal));5 printRandCell.text(randVal);6 }

<h3 id="p2">二、随机两个2/4
数字,显示在棋盘格中的随机位置</h3>

③游戏中最重要的两个函数

即判断当前能否移动的函数以及如何移动合并的函数。

1)--------判断能否移动,以左移为例

根据对游戏的观察发现在两中情况下可以移动,一是存在某个不为零的方块,该数字左边为空,即其左边的数组值为0;二是存在某个不为零的方块,,该数字左边与其相同,即其左边的数组值与其相等。对二维数组进行循环,若满足上述条件就返回1,即可以移动。

 1 function canMoveLeft(checkerboard){ 2 for(var i=0;i<4;i++){ 3 for(var j=0;j<4;j++){ 4 if(checkerboard[i][j]!=0){ 5 if(j!=0){ 6 if(checkerboard[i][j-1]==0||checkerboard[i][j-1]==checkerboard[i][j]){ 7 return 1; 8 break; 9 }10 }11 }12 }13 }14 }

2)--------移动合并函数,以左移为例

这个应该是游戏中最重要的函数,根据游戏规则,1)若一个非零方块左边全部为空,它将移动到最左边,若左边的某个不为0,它将移动到这个不为0方块的右边;2)若一个非零方块与左边的数字相同,它将移动到左边并与之合并相加;3)若一个非零方块与左边的数字相同,他与左边合并后的值恰巧与再左边的数相同,此时应该只进行第一次合并,不进行第二次合并。例如当前一排是4,2,2,0,其移动后结果应该是4,4,0,0,而不是8,0,0,0。

第三点尤为重要,最开始由于忽略了这点写出来后结果是不对的。我真对3)的解决办法是,在每次循环一行时初始化一个长度为4的数组var
tag=[0,0,0,0],若某个数在此次循环制相加过一次,就在tag相应位置置为1,再然后在每次合并前做一次判断,若该tag为1,则不进行相加。

 1 function moveLeft(checkerboard){ 2 addscore=0; 3 for(var i=0;i<4;i++){ 4 var tag=[0,0,0,0]; 5 for(var j=1;j<4;j++){ 7 if(checkerboard[i][j]!=0){ 8 if(checkerboard[i][j-1]==0){//左边为空时 9 for(k=j-1;k>=0;k--){10 if(checkerboard[i][k]!=0){11 checkerboard[i][k+1]=checkerboard[i][j];12 checkerboard[i][j]=0;13 if(checkerboard[i][k+1]==checkerboard[i][k]){//移动后与左边相等14 if(tag[k]==0&&tag[k+1]==0){15 checkerboard[i][k]+=checkerboard[i][k+1];16 score += checkerboard[i][k];17 addscore+=checkerboard[i][k];18 tag[k]=1;20 checkerboard[i][k+1]=0;21 }22 }23 break;24 }else if(k==0){//左边全空25 checkerboard[i][0]=checkerboard[i][j];26 checkerboard[i][j]=0;27 break;28 }29 }30 }else if(checkerboard[i][j-1]==checkerboard[i][j]){//左边相等时31 if(tag[j-1]==0&&tag[j]==0){32 checkerboard[i][j-1]+=checkerboard[i][j];33 score += checkerboard[i][j-1];34 addscore+=checkerboard[i][j-1];35 tag[j-i]=1;37 checkerboard[i][j]=0;38 } 39 } 40 }41 }42 }43 rePrint(checkerboard);//停止移动后重绘画面44 }

值得注意的是,为了计算游戏的总分以及每次操作的得分,需要在每次合并后对addScore和score进行计算。

右移、上移、下移与左移逻辑基本相同,只需对左移代码稍作修改即可。

  • Math.random()随机0~1数值乘4得到0<=a<4,向上取整得到1<=a<=4,再转成整型得到1,2,3,4。作为随机位置的下标值。如果采用一直随机直到找打一个空格子的思路实现,则随着空格子的减少,随机的时间越来越久,体验很不好;改进为随机50次,如果找到空格子就随机赋值2/4,如果50次后没找到空格子,则通过遍历找到空格子并赋值。

④关于分数

1 //更新分数2 function updateScore(num){3 $('#score').text(num);4 }

在每次得分后会在总分位置产生一个加分动画

 

图片 2

 1 //分数动画 2 function movescore(score){ 3 if(score>0){ 4 $('#movescore p').text('+'+score); 5 $('#movescore').css('top','110px'); 6 $('#movescore').show(); 7 var top; 8 var topVal=110; 9 var opacityVal=1;10 var timer=null;11 timer=setInterval(function(){12 topVal-=5;13 opacityVal-=0.1;14 top=topVal+'px';15 $('#movescore').css('top',top);16 $('#movescore').css('opacity',opacityVal);17 if(topVal<50){18 clearInterval(timer);19 $('#movescore').hide();20 }21 },40);22 } 23 }

<h3
id="p3">三、按下方向键,执行对应的方向移动函数;如果可以移动,则移动后随机一个新数,并判断游戏是否结束</h3>
<i>注:当出现滚动条时,按下方向键默认是滚动滚动条,这时就会同时发生滚动条滚动和上下左右移动操作,为了避免这种情况,在每次按下方向键时都阻止此方向键的默认行为。</i>
</br>
<b>左移为例,其他方向类似</b>

⑤游戏结束

若二维数组中某一项等于1024游戏结束,显示最终得分,并设置键盘不可继续操作;或者当前无法继续移动游戏结束,显示最终得分。

 1 //游戏结束函数 2 function gameOver(checkerboard){ 3 if(canMoveLeft(checkerboard)!=1&&canMoveRight(checkerboard)!=1&&canMoveUp(checkerboard)!=1&&canMoveDown(checkerboard)!=1){ 4 $('.gameover').show(); 5 $('#gameoverText p').text('Game Over !'); 6 $('#gameoverScore').text('最终得分'+score); 7 if(score>window.localStorage.getItem("maxScore")){ 8 window.localStorage.setItem("maxScore",score); 9 $('#maxScore').text(window.localStorage.getItem("maxScore"));10 }11 }12 for(var i=0;i<4;i++){13 for(var j=0;j<4;j++){14 if(checkerboard[i][j]==1024){15 ableKeyDown=1;//键盘不可操作16 $('.gameover').show();17 $('#gameoverText p').text('Congratulations!');18 $('#gameoverScore').text('最终得分'+score);19 if(score>window.localStorage.getItem("maxScore")){20 window.localStorage.setItem("maxScore",score);21 $('#maxScore').text(window.localStorage.getItem("maxScore"));22 }23 break;24 }25 }26 }27 }
  1. 先判断是否可以左移(循环遍历每一行,遍历右侧三列,如果当前位置非0,如果当前位置左侧数值为0,或者与左侧位置相等,则可以左移),不可以返回false,可以则执行左移后返回true。
  2. 执行左移。遍历每一行,遍历右侧三列,如果当前位置非空,遍历当前位置左侧单元格,如果当前位置左侧单元格为0,且两者之间没有非空单元格,则将当前位置直接移动到左侧空单元格,board值也左移;如果当前位置与左侧位置值相同,且两者之间没有非空单元格,则相加后左移,并计算、更新得分。
  3. 为了避免在动画执行前执行更新board显示内容,延时执行updateBoardView函数,返回true。
  4. 左移成功后,随机一个新数,并判断游戏是否结束。为了避免出现随机数和判断游戏是否结束发生在移动完成前,将这两个函数延时执行。
  5. 判断isgameover。如果board中没有空格子,且当前棋盘无法移动。

相关文章

Comment ()
评论是一种美德,说点什么吧,否则我会恨你的。。。