jQuery编写网页版2048小游戏


Posted in Javascript onJanuary 06, 2017

大致介绍

看了一个实现网页版2048小游戏的视频,觉得能做出自己以前喜欢玩的小游戏很有意思便自己动手试了试,真正的验证了这句话-不要以为你以为的就是你以为的,看视频时觉得看懂了,会写了,但是自己实现起来会遇到各种问题。比如,在最后判断游戏是否结束的时候,我写的语句语法是对的,但就是不执行。最后通过对视频源码的分析对比,发现原作者写的一个setTimeout定时器有额外的意思,本来我以为它就是简单的一个延时动画,其实他是在等待另外一个函数执行完毕。-_-||。最后还是很高兴能写出来,也改进了一些源代码的不足。

这篇博客并不是详细的讲解,只是大致介绍函数的作用,其中实现的细节注释中有解释,网上的这个源码有点乱,如果想看比较整齐的源码或者视频的可以QQ联系我(免费)(找共同学习的伙伴)

思路

这个小游戏可以抽象化分为3层(我觉得这样能更好理解)

◆最底下的一层是基本的样式(可见的)

◆中间的层是最主要的,是一个4x4的二维数组,游戏中我们都是对这个二维数组进行操作(不可见的)

◆最上面的一层也是一个4x4的二维数组,它只是根据第二层数组的每个数显示样式(可见的)

我们通过最底下的一层显示最基本的16个小方格,通过键盘的按键或者手指在屏幕的滑动来操作中间层的数组,最后在通过最上面的一层显示出数字

基本结构与样式

基本的结构和样式都挺简单,直接看代码

结构:

<div id="test2048">
 <div id="header">
  <h1>2048</h1>
  <a href="javascript:newgame()" >开始新的游戏</a>
  <p>分数:<span id="score">0</span></p>
 </div>
 <div id="container">
  <div class="cell" id="cell-0-0"></div>
  <div class="cell" id="cell-0-1"></div>
  <div class="cell" id="cell-0-2"></div>
  <div class="cell" id="cell-0-3"></div>
  <div class="cell" id="cell-1-0"></div>
  <div class="cell" id="cell-1-1"></div>
  <div class="cell" id="cell-1-2"></div>
  <div class="cell" id="cell-1-3"></div>
  <div class="cell" id="cell-2-0"></div>
  <div class="cell" id="cell-2-1"></div>
  <div class="cell" id="cell-2-2"></div>
  <div class="cell" id="cell-2-3"></div>
  <div class="cell" id="cell-3-0"></div>
  <div class="cell" id="cell-3-1"></div>
  <div class="cell" id="cell-3-2"></div>
  <div class="cell" id="cell-3-3"></div>
 </div>
 </div>

样式:

*{
 margin: 0;
 padding: 0;
}
#test2048{
 font-family: Arial;
 margin: 0 auto;
 text-align: center;
}
#header{
 margin: 20px;
}
#header a{
 font-family: Arial;
 text-decoration: none;
 display: block;
 color: white;
 margin: 20px auto;
 width: 125px;
 height: 35px;
 text-align: center;
 line-height: 40px;
 background-color: #8f7a66;
 border-radius: 10px;
 font-size: 15px;
}
#header p{
 font-family: Arial;
 font-size: 20px;
}
#container{
 width: 460px;
 height: 460px;
 background-color: #bbada0;
 margin: 0 auto;
 border-radius: 10px;
 position: relative;
 padding: 20px;
}
.cell{
 width: 100px;
 height: 100px;
 border-radius: 6px;
 background-color: #ccc0b3;
 position: absolute;
}

从CSS样式可以看出,我们并没有对每个格子的位置进行设置,因为如果用CSS给每个格子设置样式代码量太大,而且他们的位置有一定的规律,所以我们可以用js循环来完成每个格子样式的设置

代码:

// 初始化棋盘格
function initialize(){
 for(var i=0;i<4;i++){
  for(var j=0;j<4;j++){
   // 设置棋盘格的位置
   var everyCell = $('#cell-'+ i +'-'+ j);
   everyCell.css({top:getPos(i),left:getPos(j)});
  }
 }
}
// 获取位置
function getPos(num){
 return 20 + num*120;
}

这样我们的第一层就好了

效果:

 jQuery编写网页版2048小游戏

现在构造第二层,即构建一个4x4的值全部为0的数组,由于在构造第二层时,有两层循环,所以我们可以在构造第一层时也能构造第二层

第三层是用js生成16个格子,它和第一层的16个格子一一对应

代码:

// 数字格
function numFormat(){
 for(var i=0;i<4;i++){
  for(var j=0;j<4;j++){
   $('#container').append('<div class="number" id="number-'+ i +'-'+ j +'"></div>')
   // 设置数字格的位置,样式
   var everyNumber = $('#number-'+ i +'-'+ j);
   if(checkerboard[i][j] == 0){
    everyNumber.css({
     width:'0px',
     height:'opx',
     top:getPos(i) + 50,
     left:getPos(j) + 50
    })
   }else{
    everyNumber.css({
     width:'100px',
     height:'100px',
     top:getPos(i),
     left:getPos(j),
backgroundColor:getBackgroundColor(checkerboard[i][j]),
     color:getColor(checkerboard[i][j])
    });
    everyNumber.text(checkerboard[i][j]);
   }
  }
 }
}
// 获取相应数字的背景颜色
function getBackgroundColor(number){
 switch (number) {
  case 2:return "#eee4da";break;
  case 4:return "#ede0c8";break;
  case 8:return "#f2b179";break;
  case 16:return "#f59563";break;
  case 32:return "#f67c5f";break;
  case 64:return "#f65e3b";break;
  case 128:return "#edcf72";break;
  case 256:return "#edcc61";break;
  case 512:return "#9c0";break;
  case 1024:return "#33b5e5";break;
  case 2048:return "#09c";break;
  case 4096:return "#a6c";break;
  case 8192:return "#93c";break;
 }
}
// 设置相应数字的文字颜色
function getColor(number){
 if (number <= 4) {
  return "#776e65"
 }
 return "white";
}

初始化

在每次游戏重新开始时,都会在随机的位置出现两个随机的数字,我们写一个在随机位置出现一个随机数的函数,只要调用两次就可以实现了

代码:

// 随机的在一个位置上产生一个数字
function randomNum(){
 // 随机产生一个坐标值
 var randomX = Math.floor(Math.random() * 4);
 var randomY = Math.floor(Math.random() * 4);
 // 随机产生一个数字(2或4)
 var randomValue = Math.random() > 0.5 ? 2 : 4;
 // 在数字格不为0的地方生成一个随机数字
 while(true){
  if(checkerboard[randomX][randomY] == 0){
   break;
  }else{
   var randomX = Math.floor(Math.random() * 4);
   var randomY = Math.floor(Math.random() * 4);
  }
 }
 // 将随机产生的数字显示在随机的位置上
 checkerboard[randomX][randomY] = randomValue;
 // 动画
 randomNumAnimate(randomX,randomY,randomValue);
}
// 随机产生数字的动画
function randomNumAnimate(randomX,randomY,randomValue){
 var randomnum = $('#number-'+ randomX +'-'+ randomY);
 randomnum.css({
  backgroundColor:getBackgroundColor(randomValue),
  color:getColor(randomValue),
 })
    .text(randomValue)
    .animate({
     width:'100px',
     height:'100px',
     top:getPos(randomX),
     left:getPos(randomY)
    },50);
}

基本操作

我们通过switch循环,来根据用户不同的输入进行不同的操作

代码:

// 获取键盘事件,检测不同的按键进行不同的操作
$(document).keydown(function(event){
 switch(event.keyCode){
  case 37://左
   if(canMoveLeft(checkerboard)){
    // 如果可以向左移动
    MoveLeft();
    // 向左移动
    setTimeout(function(){
     randomNum();
    },200);
    // 随机产生一个数字
   }
   break;
  case 38://上
   if(canMoveUp(checkerboard)){
    // 如果可以向上移动
    MoveUp();
    // 向上移动
    setTimeout(function(){
     randomNum();
    },200);
    // 随机产生一个数字
   }
   break;
  case 39://右
   if(canMoveRight(checkerboard)){
    // 如果可以向右移动
    MoveRight();
    // 向右移动
    setTimeout(function(){
     randomNum();
    },200);
    // 随机产生一个数字
   }
   break;
  case 40://下
   if(canMoveDown(checkerboard)){
    // 如果可以向下移动
    MoveDown();
    // 向下移动
    setTimeout(function(){
     randomNum();
    },200);
    // 随机产生一个数字
   }
   break;
  default:
   break;
 }
});

由于数字格的移动只有左、上、右、下四种方式,并且他们都是大同小异的,所以就拿向左移动为例,

向左移动,我们首先需要判断它是否能向左移动,能向左移动有两种情况

第一种:当前格子的左边的格子是空的即值为0

第二种:当前格子的值和左边格子的值相同

由于向左移动,所以第一列的格子不可能向左移动,所以不需要判断

代码:

// 判断是否可以向左移动
function canMoveLeft(checkerboard){
 for(var i=0;i<4;i++){
  for(var j=1;j<4;j++){
   if(checkerboard[i][j] != 0){
    // 如果这个数字格它左边的数字格为空或者左边的数字格和它相等,则可以向左移动
    if(checkerboard[i][j-1] == 0 || checkerboard[i][j] == checkerboard[i][j-1]){
     return true;
    }
   }
  }
 }
 return false;
}

判断能否向左移动后,我们就要对可以移动的格子进行移动,这里需要特别注意,向哪个方向移动就要先从哪个方向开始判断

代码:

// 向左移动
function MoveLeft(){
 for(var i=0;i<4;i++){
  for(var j=1;j<4;j++){
   if(checkerboard[i][j] != 0){
    for(var k=0;k<j;k++){
     if(checkerboard[i][k] == 0 && noMiddleNumRow(i,k,j,checkerboard)){
      moveAnimation(i,j,i,k);
      checkerboard[i][k] = checkerboard[i][j];
      checkerboard[i][j] = 0;
     }else if(checkerboard[i][k] == checkerboard[i][j] && noMiddleNumRow(i,k,j,checkerboard) && !hasConflicted[i][k]){
      moveAnimation(i,j,i,k);
      checkerboard[i][k] += checkerboard[i][j];
      checkerboard[i][j] = 0;
     }
    }
   }
  }
 }
 // 设置刷新的时间是为了让运动的动画走完在进行更新数字格,否则数字格运动的动画将会被打断
 setTimeout(function(){
  numFormat();
 },200);
}
// 判断中间的数字格是否为0(行)
function noMiddleNumRow(row,col1,col2,checkerboard){
 for(var i=col1+1;i<col2;i++){
  if(checkerboard[row][i] != 0){
   return false;
  }
 }
 return true;
}

将上、右、下四个方向写完以后,游戏基本的操作就已经完成了。

游戏分数和判断游戏结束

游戏的分数是每个相加的数的和,所以我们在每个数相加的时候更新分数就可以了

代码:

// 更新分数
score += checkerboard[k][j];
updateScore(score);
// 设置分数
function updateScore(num){
 $('#score').text(num);
}

判断游戏是否结束很简单,用我们之前定义的方法就可以实现

代码:

// 判断游戏是否结束
function wheGameOver(checkerboard){
 if(!canMoveLeft(checkerboard) && !canMoveUp(checkerboard) && !canMoveRight(checkerboard) && !canMoveDown(checkerboard) ){
  showGameOver();
 }
}
// 显示游戏结束
function showGameOver(){
 $('#container').append("<div id='gameover'><p>最终得分</p><span>"+ score +"</span><a href='javascript:resert();'>重新开始游戏</a></div> ")
}
// 重新开始游戏
function resert(){
 $('#gameover').remove();
 newgame();
}

最后优化

1、游戏中会出现一次移动,一个数会被累加很多次

在原游戏中,每个数在每次操作中只能累加一次,所以我们在定义一个4x4的值为false的数组,与中间层的数组一一对应,专门用来防止一个数的多次累加,如果是false则可以累加,并将值改为false,否则不可以累加

2、结束死循环

由于在设置随机数的时候用到了一个死循环,但是在游戏结束后,该循环还在,所以我们在死循环中在添加一个条件,如果游戏结束就跳出循环

3、最后的结束游戏提示不执行

case 37://左
   if(canMoveLeft(checkerboard)){
    // 如果可以向左移动
    MoveLeft();
    // 向左移动
    setTimeout(function(){
     wheGameOver(checkerboard)
    },300);
    // 判断游戏是否结束,这里设置延时是因为要等到随机产生数字后再进行判断,如果不加
    // 延时,则最后一次的判断因为canMoveLeft(checkerboard)为false就不会再执行了
    setTimeout(function(){
     randomNum();
    },200);
    // 随机产生一个数字
   }
   break;

从代码中可以看出,判断游戏是否结束是在随机产生一个数字前执行的,所以在判断游戏结束时,总是有一个空的格子,所以代码执行后认为游戏没有结束,但是当这个随机数字产生后,所有的格子不能移动,当我们按键时,if条件不通过,判断游戏是否结束的函数不能执行。所以我们要给判断游戏结束的函数设置定时器,让他在随机产生一个数字后再进行判断

4、在移动端可以执行

由于原作者没有写有关移动端的操作,所以我在网上找的判断移动端触屏手机滑动位置的代码,加入了游戏的事件就可以执行了

//返回角度 
   function GetSlideAngle(dx, dy) { 
    return Math.atan2(dy, dx) * 180 / Math.PI; 
   } 
   //根据起点和终点返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑动 
   function GetSlideDirection(startX, startY, endX, endY) { 
    var dy = startY - endY; 
    var dx = endX - startX; 
    varresult = 0; 
    //如果滑动距离太短 
    if(Math.abs(dx) < 2 && Math.abs(dy) < 2) { 
     returnresult; 
    } 
    var angle = GetSlideAngle(dx, dy); 
    if(angle >= -45 && angle < 45) { 
     result = 4; 
    }else if (angle >= 45 && angle < 135) { 
     result = 1; 
    }else if (angle >= -135 && angle < -45) { 
     result = 2; 
    } 
    else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) { 
     result = 3; 
    } 
    return result; 
   } 
   //滑动处理 
   var startX, startY; 
   document.addEventListener('touchstart',function (ev) { 
    startX = ev.touches[0].pageX; 
    startY = ev.touches[0].pageY; 
   }, false); 
   document.addEventListener('touchend',function (ev) { 
    var endX, endY; 
    endX = ev.changedTouches[0].pageX; 
    endY = ev.changedTouches[0].pageY; 
    var direction = GetSlideDirection(startX, startY, endX, endY); 
    switch(direction) { 
     case 0: 
      //没滑动 
      break; 
     case 1: 
      if(canMoveUp(checkerboard)){
      // 如果可以向上移动
      MoveUp();
      // 向上移动
      setTimeout(function(){
       wheGameOver(checkerboard)
      },300);
      // 判断游戏是否结束
      setTimeout(function(){
       randomNum();
      },200);
      // 随机产生一个数字
      } 
      break; 
     case 2: 
      if(canMoveDown(checkerboard)){
      // 如果可以向下移动
      MoveDown();
      // 向下移动
      setTimeout(function(){
       wheGameOver(checkerboard)
      },300);
      // 判断游戏是否结束
      setTimeout(function(){
       randomNum();
      },200);
      // 随机产生一个数字
      } 
      break; 
     case 3: 
      if(canMoveLeft(checkerboard)){
      // 如果可以向左移动
      MoveLeft();
      // 向左移动
      setTimeout(function(){
       wheGameOver(checkerboard)
      },300);
      // 判断游戏是否结束,这里设置延时是因为要等到随机产生数字后再进行判断,如果不加
      // 延时,则最后一次的判断因为canMoveLeft(checkerboard)为false就不会再执行了
      setTimeout(function(){
       randomNum();
      },200);
      // 随机产生一个数字
      } 
      break; 
     case 4: 
      if(canMoveRight(checkerboard)){
      // 如果可以向右移动
      MoveRight();
      // 向右移动
      setTimeout(function(){
       wheGameOver(checkerboard)
      },300);
      // 判断游戏是否结束
      setTimeout(function(){
       randomNum();
      },200);
      // 随机产生一个数字
      } 
      break; 
     default:    
    } 
   }, false);

总结

总体来说这个游戏实现起来并不是太难,就是许多小的操作集合起来

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
jQuery 选择器、DOM操作、事件、动画
Nov 25 Javascript
简单谈谈javascript代码复用模式
Jan 28 Javascript
JavaScript中for循环的使用详解
Jun 03 Javascript
Bootstrap框架动态生成Web页面文章内目录的方法
May 12 Javascript
js获取指定字符前/后的字符串简单实例
Oct 27 Javascript
常用JS图片滚动(无缝、平滑、上下左右滚动)代码大全(推荐)
Dec 20 Javascript
前端面试知识点锦集(JavaScript篇)
Dec 28 Javascript
简单实现jQuery轮播效果
Aug 18 jQuery
Angular实现较为复杂的表格过滤,删除功能示例
Dec 23 Javascript
echarts大屏字体自适应的方法步骤
Jul 12 Javascript
Vue数字输入框组件使用方法详解
Feb 10 Javascript
如何在Vue.JS中使用图标组件
Aug 04 Javascript
利用JQuery实现datatables插件的增加和删除行功能
Jan 06 #Javascript
javascript正则表达式模糊匹配IP地址功能示例
Jan 06 #Javascript
bootstrap导航栏、下拉菜单、表单的简单应用实例解析
Jan 06 #Javascript
使用BootStrap进行轮播图的制作
Jan 06 #Javascript
BootStrap便签页的简单应用
Jan 06 #Javascript
bootstrap table实例详解
Jan 06 #Javascript
node.js发送邮件email的方法详解
Jan 06 #Javascript
You might like
PHP实现文件安全下载
2006/10/09 PHP
PHP生成不同颜色、不同大小的tag标签函数
2013/09/23 PHP
PHP.ini中配置屏蔽错误信息显示和保存错误日志的例子
2014/05/12 PHP
详解PHP字符串替换str_replace()函数四种用法
2017/10/13 PHP
JS操作XML中DTD介绍及使用方法分析
2019/07/04 PHP
jquery-easyui关闭tab自动切换到前一个tab
2010/07/29 Javascript
JS分页控件 可用于无刷新分页
2013/07/23 Javascript
JavaScript判断变量是否为空的自定义函数分享
2015/01/31 Javascript
bootstrap输入框组代码分享
2016/06/07 Javascript
JSON键值对序列化和反序列化解析
2017/01/24 Javascript
JS表格组件神器bootstrap table使用指南详解
2017/04/12 Javascript
easyui简介_动力节点Java学院整理
2017/07/14 Javascript
layui select 禁止点击的实现方法
2019/09/05 Javascript
vue引入静态js文件的方法
2020/06/20 Javascript
Vue 同步异步存值取值实现案例
2020/08/05 Javascript
微信小程序学习之自定义滚动弹窗
2020/12/20 Javascript
对于Python异常处理慎用“except:pass”建议
2015/04/02 Python
python随机数分布random测试
2018/08/27 Python
解读python如何实现决策树算法
2018/10/11 Python
python中metaclass原理与用法详解
2019/06/25 Python
python SVD压缩图像的实现代码
2019/11/05 Python
Python验证码截取识别代码实例
2020/05/16 Python
用python实现名片管理系统
2020/06/18 Python
解决c++调用python中文乱码问题
2020/07/29 Python
Windows下Sqlmap环境安装教程详解
2020/08/04 Python
Python -m参数原理及使用方法解析
2020/08/21 Python
pycharm实现猜数游戏
2020/12/07 Python
移动端html5 meta标签的神奇功效
2016/01/06 HTML / CSS
三维科技面试题
2013/07/27 面试题
销售工作人员的自我评价分享
2013/11/10 职场文书
投标担保书范文
2014/04/02 职场文书
个人工作表现评价材料
2014/09/21 职场文书
店铺转让协议书
2014/12/02 职场文书
2015年社会实践个人总结
2015/03/06 职场文书
pandas 操作 Excel操作总结
2021/03/31 Python
vue实现简单数据双向绑定
2021/04/28 Vue.js