jQuery实践-网页版2048小游戏
▓▓▓▓▓▓ 大致介绍
看了一个实现网页版2048小游戏的视频,觉得能做出自己以前喜欢玩的小游戏很有意思便自己动手试了试,真正的验证了这句话-不要以为你以为的就是你以为的,看视频时觉得看懂了,会写了,但是自己实现起来会遇到各种问题。比如,在最后判断游戏是否结束的时候,我写的语句语法是对的,但就是不执行。最后通过对视频源码的分析对比,发现原作者写的一个setTimeout定时器有额外的意思,本来我以为它就是简单的一个延时动画,其实他是在等待另外一个函数执行完毕。-_-||。最后还是很高兴能写出来,也改进了一些源代码的不足。
jQuery在这个游戏中的应用并不多,如果对其中的jQuery语法有疑问,可以参考我写的jQuery学习之路(持续更新),里面有讲解
预览:2048网页版
这篇博客并不是详细的讲解,只是大致介绍函数的作用,其中实现的细节注释中有解释,网上的这个源码有点乱,如果想看比较整齐的源码或者视频的可以QQ联系我(免费)(找共同学习的伙伴)
▓▓▓▓▓▓ 思路
这个小游戏可以抽象化分为3层(我觉得这样能更好理解)
◆最底下的一层是基本的样式(可见的)
◆中间的层是最主要的,是一个4x4的二维数组,游戏中我们都是对这个二维数组进行操作(不可见的)
◆最上面的一层也是一个4x4的二维数组,它只是根据第二层数组的每个数显示样式(可见的)
我们通过最底下的一层显示最基本的16个小方格,通过键盘的按键或者手指在屏幕的滑动来操作中间层的数组,最后在通过最上面的一层显示出数字
▓▓▓▓▓▓ 基本结构与样式
基本的结构和样式都挺简单,直接看代码
结构:
1 <div id="test2048"> 2 <div id="header"> 3 <h1>2048</h1> 4 <a href="javascript:newgame()" >开始新的游戏</a> 5 <p>分数:<span id="score">0</span></p> 6 </div> 7 <div id="container"> 8 <div class="cell" id="cell-0-0"></div> 9 <div class="cell" id="cell-0-1"></div> 10 <div class="cell" id="cell-0-2"></div> 11 <div class="cell" id="cell-0-3"></div> 12 <div class="cell" id="cell-1-0"></div> 13 <div class="cell" id="cell-1-1"></div> 14 <div class="cell" id="cell-1-2"></div> 15 <div class="cell" id="cell-1-3"></div> 16 <div class="cell" id="cell-2-0"></div> 17 <div class="cell" id="cell-2-1"></div> 18 <div class="cell" id="cell-2-2"></div> 19 <div class="cell" id="cell-2-3"></div> 20 <div class="cell" id="cell-3-0"></div> 21 <div class="cell" id="cell-3-1"></div> 22 <div class="cell" id="cell-3-2"></div> 23 <div class="cell" id="cell-3-3"></div> 24 </div> 25 </div>
样式:
1 *{ 2 margin: 0; 3 padding: 0; 4 } 5 #test2048{ 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; 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 }
从CSS样式可以看出,我们并没有对每个格子的位置进行设置,因为如果用CSS给每个格子设置样式代码量太大,而且他们的位置有一定的规律,所以我们可以用js循环来完成每个格子样式的设置
代码:
1 // 初始化棋盘格 2 function initialize(){ 3 for(var i=0;i<4;i++){ 4 for(var j=0;j<4;j++){ 5 // 设置棋盘格的位置 6 var everyCell = $('#cell-'+ i +'-'+ j); 7 everyCell.css({top:getPos(i),left:getPos(j)}); 8 } 9 } 10 }
1 // 获取位置 2 function getPos(num){ 3 return 20 + num*120; 4 }
这样我们的第一层就好了
效果:
现在构造第二层,即构建一个4x4的值全部为0的数组,由于在构造第二层时,有两层循环,所以我们可以在构造第一层时也能构造第二层
第三层是用js生成16个格子,它和第一层的16个格子一一对应
代码:
1 // 数字格 2 function numFormat(){ 3 for(var i=0;i<4;i++){ 4 for(var j=0;j<4;j++){ 5 $('#container').append('<div class="number" id="number-'+ i +'-'+ j +'"></div>') 6 7 // 设置数字格的位置,样式 8 var everyNumber = $('#number-'+ i +'-'+ j); 9 if(checkerboard[i][j] == 0){ 10 everyNumber.css({ 11 width:'0px', 12 height:'0px', 13 top:getPos(i) + 50, 14 left:getPos(j) + 50 15 }) 16 }else{ 17 everyNumber.css({ 18 width:'100px', 19 height:'100px', 20 top:getPos(i), 21 left:getPos(j), 22 backgroundColor:getBackgroundColor(checkerboard[i][j]), 23 color:getColor(checkerboard[i][j]) 24 }); 25 everyNumber.text(checkerboard[i][j]); 26 } 27 } 28 } 29 }
1 // 获取相应数字的背景颜色 2 function getBackgroundColor(number){ 3 4 switch (number) { 5 case 2:return "#eee4da";break; 6 case 4:return "#ede0c8";break; 7 case 8:return "#f2b179";break; 8 case 16:return "#f59563";break; 9 case 32:return "#f67c5f";break; 10 case 64:return "#f65e3b";break; 11 case 128:return "#edcf72";break; 12 case 256:return "#edcc61";break; 13 case 512:return "#9c0";break; 14 case 1024:return "#33b5e5";break; 15 case 2048:return "#09c";break; 16 case 4096:return "#a6c";break; 17 case 8192:return "#93c";break; 18 } 19 }
1 // 设置相应数字的文字颜色 2 function getColor(number){ 3 if (number <= 4) { 4 return "#776e65" 5 } 6 return "white"; 7 }
▓▓▓▓▓▓ 初始化
在每次游戏重新开始时,都会在随机的位置出现两个随机的数字,我们写一个在随机位置出现一个随机数的函数,只要调用两次就可以实现了
代码:
1 // 随机的在一个位置上产生一个数字 2 function randomNum(){ 3 // 随机产生一个坐标值 4 var randomX = Math.floor(Math.random() * 4); 5 var randomY = Math.floor(Math.random() * 4); 6 7 // 随机产生一个数字(2或4) 8 var randomValue = Math.random() > 0.5 ? 2 : 4; 9 10 // 在数字格不为0的地方生成一个随机数字 11 while(true){ 12 if(checkerboard[randomX][randomY] == 0){ 13 break; 14 }else{ 15 16 var randomX = Math.floor(Math.random() * 4); 17 var randomY = Math.floor(Math.random() * 4); 18 } 19 } 20 21 // 将随机产生的数字显示在随机的位置上 22 checkerboard[randomX][randomY] = randomValue; 23 24 // 动画 25 randomNumAnimate(randomX,randomY,randomValue); 26 }
1 // 随机产生数字的动画 2 function randomNumAnimate(randomX,randomY,randomValue){ 3 var randomnum = $('#number-'+ randomX +'-'+ randomY); 4 randomnum.css({ 5 backgroundColor:getBackgroundColor(randomValue), 6 color:getColor(randomValue), 7 }) 8 .text(randomValue) 9 .animate({ 10 width:'100px', 11 height:'100px', 12 top:getPos(randomX), 13 left:getPos(randomY) 14 },50); 15 }
▓▓▓▓▓▓ 基本操作
我们通过switch循环,来根据用户不同的输入进行不同的操作
代码:
1 // 获取键盘事件,检测不同的按键进行不同的操作 2 $(document).keydown(function(event){ 3 switch(event.keyCode){ 4 case 37://左 5 if(canMoveLeft(checkerboard)){ 6 // 如果可以向左移动 7 8 MoveLeft(); 9 // 向左移动 10 11 12 setTimeout(function(){ 13 randomNum(); 14 },200); 15 // 随机产生一个数字 16 } 17 break; 18 case 38://上 19 if(canMoveUp(checkerboard)){ 20 // 如果可以向上移动 21 22 MoveUp(); 23 // 向上移动 24 25 setTimeout(function(){ 26 randomNum(); 27 },200); 28 // 随机产生一个数字 29 } 30 break; 31 case 39://右 32 if(canMoveRight(checkerboard)){ 33 // 如果可以向右移动 34 35 MoveRight(); 36 // 向右移动 37 38 setTimeout(function(){ 39 randomNum(); 40 },200); 41 // 随机产生一个数字 42 } 43 break; 44 case 40://下 45 if(canMoveDown(checkerboard)){ 46 // 如果可以向下移动 47 48 MoveDown(); 49 // 向下移动 50 51 setTimeout(function(){ 52 randomNum(); 53 },200); 54 // 随机产生一个数字 55 } 56 break; 57 default: 58 break; 59 } 60 });
由于数字格的移动只有左、上、右、下四种方式,并且他们都是大同小异的,所以就拿向左移动为例,
向左移动,我们首先需要判断它是否能向左移动,能向左移动有两种情况
第一种:当前格子的左边的格子是空的即值为0
第二种:当前格子的值和左边格子的值相同
由于向左移动,所以第一列的格子不可能向左移动,所以不需要判断
代码:
1 // 判断是否可以向左移动 2 function canMoveLeft(checkerboard){ 3 for(var i=0;i<4;i++){ 4 for(var j=1;j<4;j++){ 5 if(checkerboard[i][j] != 0){ 6 // 如果这个数字格它左边的数字格为空或者左边的数字格和它相等,则可以向左移动 7 if(checkerboard[i][j-1] == 0 || checkerboard[i][j] == checkerboard[i][j-1]){ 8 return true; 9 } 10 } 11 } 12 } 13 return false; 14 }
判断能否向左移动后,我们就要对可以移动的格子进行移动,这里需要特别注意,向哪个方向移动就要先从哪个方向开始判断
代码:
1 // 向左移动 2 function MoveLeft(){ 3 for(var i=0;i<4;i++){ 4 for(var j=1;j<4;j++){ 5 if(checkerboard[i][j] != 0){ 6 for(var k=0;k<j;k++){ 7 if(checkerboard[i][k] == 0 && noMiddleNumRow(i,k,j,checkerboard)){ 8 moveAnimation(i,j,i,k); 9 checkerboard[i][k] = checkerboard[i][j]; 10 checkerboard[i][j] = 0; 11 }else if(checkerboard[i][k] == checkerboard[i][j] && noMiddleNumRow(i,k,j,checkerboard) && !hasConflicted[i][k]){ 12 moveAnimation(i,j,i,k); 13 checkerboard[i][k] += checkerboard[i][j]; 14 checkerboard[i][j] = 0; 15 16 } 17 } 18 } 19 } 20 } 21 // 设置刷新的时间是为了让运动的动画走完在进行更新数字格,否则数字格运动的动画将会被打断 22 setTimeout(function(){ 23 numFormat(); 24 },200); 25 }
1 // 判断中间的数字格是否为0(行) 2 function noMiddleNumRow(row,col1,col2,checkerboard){ 3 for(var i=col1+1;i<col2;i++){ 4 if(checkerboard[row][i] != 0){ 5 return false; 6 } 7 } 8 return true; 9 }
将上、右、下四个方向写完以后,游戏基本的操作就已经完成了。
▓▓▓▓▓▓ 游戏分数和判断游戏结束
游戏的分数是每个相加的数的和,所以我们在每个数相加的时候更新分数就可以了
代码:
1 // 更新分数 2 score += checkerboard[k][j]; 3 updateScore(score);
1 // 设置分数 2 function updateScore(num){ 3 $('#score').text(num); 4 }
判断游戏是否结束很简单,用我们之前定义的方法就可以实现
代码:
1 // 判断游戏是否结束 2 function wheGameOver(checkerboard){ 3 if(!canMoveLeft(checkerboard) && !canMoveUp(checkerboard) && !canMoveRight(checkerboard) && !canMoveDown(checkerboard) ){ 4 showGameOver(); 5 } 6 }
1 // 显示游戏结束 2 function showGameOver(){ 3 $('#container').append("<div id='gameover'><p>最终得分</p><span>"+ score +"</span><a href='javascript:resert();'>重新开始游戏</a></div> ") 4 } 5 6 // 重新开始游戏 7 function resert(){ 8 $('#gameover').remove(); 9 newgame(); 10 }
▓▓▓▓▓▓ 最后优化
1、游戏中会出现一次移动,一个数会被累加很多次
在原游戏中,每个数在每次操作中只能累加一次,所以我们在定义一个4x4的值为false的数组,与中间层的数组一一对应,专门用来防止一个数的多次累加,如果是false则可以累加,并将值改为false,否则不可以累加
2、结束死循环
由于在设置随机数的时候用到了一个死循环,但是在游戏结束后,该循环还在,所以我们在死循环中在添加一个条件,如果游戏结束就跳出循环
3、最后的结束游戏提示不执行
1 case 37://左 2 if(canMoveLeft(checkerboard)){ 3 // 如果可以向左移动 4 5 MoveLeft(); 6 // 向左移动 7 8 setTimeout(function(){ 9 wheGameOver(checkerboard) 10 },300); 11 // 判断游戏是否结束,这里设置延时是因为要等到随机产生数字后再进行判断,如果不加 12 // 延时,则最后一次的判断因为canMoveLeft(checkerboard)为false就不会再执行了 13 14 setTimeout(function(){ 15 randomNum(); 16 },200); 17 // 随机产生一个数字 18 } 19 break;
从代码中可以看出,判断游戏是否结束是在随机产生一个数字前执行的,所以在判断游戏结束时,总是有一个空的格子,所以代码执行后认为游戏没有结束,但是当这个随机数字产生后,所有的格子不能移动,当我们按键时,if条件不通过,判断游戏是否结束的函数不能执行。所以我们要给判断游戏结束的函数设置定时器,让他在随机产生一个数字后再进行判断
4、在移动端可以执行
由于原作者没有写有关移动端的操作,所以我在网上找的判断移动端触屏手机滑动位置的代码,加入了游戏的事件就可以执行了
1 //返回角度 2 function GetSlideAngle(dx, dy) { 3 return Math.atan2(dy, dx) * 180 / Math.PI; 4 } 5 6 //根据起点和终点返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑动 7 function GetSlideDirection(startX, startY, endX, endY) { 8 var dy = startY - endY; 9 var dx = endX - startX; 10 varresult = 0; 11 12 //如果滑动距离太短 13 if(Math.abs(dx) < 2 && Math.abs(dy) < 2) { 14 returnresult; 15 } 16 17 var angle = GetSlideAngle(dx, dy); 18 if(angle >= -45 && angle < 45) { 19 result = 4; 20 }else if (angle >= 45 && angle < 135) { 21 result = 1; 22 }else if (angle >= -135 && angle < -45) { 23 result = 2; 24 } 25 else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) { 26 result = 3; 27 } 28 29 return result; 30 } 31 32 //滑动处理 33 var startX, startY; 34 document.addEventListener('touchstart',function (ev) { 35 startX = ev.touches[0].pageX; 36 startY = ev.touches[0].pageY; 37 }, false); 38 document.addEventListener('touchend',function (ev) { 39 var endX, endY; 40 endX = ev.changedTouches[0].pageX; 41 endY = ev.changedTouches[0].pageY; 42 var direction = GetSlideDirection(startX, startY, endX, endY); 43 switch(direction) { 44 case 0: 45 //没滑动 46 break; 47 case 1: 48 if(canMoveUp(checkerboard)){ 49 // 如果可以向上移动 50 51 MoveUp(); 52 // 向上移动 53 54 setTimeout(function(){ 55 wheGameOver(checkerboard) 56 },300); 57 // 判断游戏是否结束 58 59 setTimeout(function(){ 60 randomNum(); 61 },200); 62 // 随机产生一个数字 63 } 64 break; 65 case 2: 66 if(canMoveDown(checkerboard)){ 67 // 如果可以向下移动 68 69 MoveDown(); 70 // 向下移动 71 72 setTimeout(function(){ 73 wheGameOver(checkerboard) 74 },300); 75 // 判断游戏是否结束 76 77 setTimeout(function(){ 78 randomNum(); 79 },200); 80 // 随机产生一个数字 81 } 82 break; 83 case 3: 84 if(canMoveLeft(checkerboard)){ 85 // 如果可以向左移动 86 87 MoveLeft(); 88 // 向左移动 89 90 setTimeout(function(){ 91 wheGameOver(checkerboard) 92 },300); 93 // 判断游戏是否结束,这里设置延时是因为要等到随机产生数字后再进行判断,如果不加 94 // 延时,则最后一次的判断因为canMoveLeft(checkerboard)为false就不会再执行了 95 96 setTimeout(function(){ 97 randomNum(); 98 },200); 99 // 随机产生一个数字 100 } 101 break; 102 case 4: 103 if(canMoveRight(checkerboard)){ 104 // 如果可以向右移动 105 106 MoveRight(); 107 // 向右移动 108 109 setTimeout(function(){ 110 wheGameOver(checkerboard) 111 },300); 112 // 判断游戏是否结束 113 114 setTimeout(function(){ 115 randomNum(); 116 },200); 117 // 随机产生一个数字 118 } 119 break; 120 default: 121 } 122 }, false);
▓▓▓▓▓▓ 总结
总体来说这个游戏实现起来并不是太难,就是许多小的操作集合起来
如果想看视频或者源码请QQ联系我