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 相关文章推荐
用于判断用户注册时,密码强度的JS代码
Jan 01 Javascript
JavaScript在IE和Firefox浏览器下的7个差异兼容写法小结
Jun 18 Javascript
返回上一页并自动刷新的JavaScript代码
Feb 19 Javascript
详解Javascript动态操作CSS
Dec 08 Javascript
jQuery统计指定子元素数量的方法
Mar 17 Javascript
jQuery validate验证插件使用详解
May 11 Javascript
jquery事件绑定解绑机制源码解析
Sep 19 Javascript
webpack之devtool详解
Feb 10 Javascript
微信小程序异步API为Promise简化异步编程的操作方法
Aug 14 Javascript
vue.js引入外部CSS样式和外部JS文件的方法
Jan 06 Javascript
JS实现利用闭包判断Dom元素和滚动条的方向示例
Aug 26 Javascript
openlayers实现地图弹窗
Sep 25 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
ThinkPHP写数组插入与获取最新插入数据ID实例
2014/11/03 PHP
javascript 单选框,多选框美化代码
2008/08/01 Javascript
模仿jQuery each函数的链式调用
2009/07/22 Javascript
JS控制显示隐藏兼容问题(IE6、IE7、IE8)
2010/04/01 Javascript
jQuery实战之仿淘宝商城左侧导航效果
2011/04/12 Javascript
css3元素简单的闪烁效果实现(html5 jquery)
2013/12/28 Javascript
jQuery层级选择器用法分析
2015/02/10 Javascript
JavaScript判断按钮被点击的方法
2015/12/13 Javascript
第一篇初识bootstrap
2016/06/21 Javascript
js数字计算 误差问题的快速解决方法
2017/02/28 Javascript
@ResponseBody 和 @RequestBody 注解的区别
2017/03/08 Javascript
js canvas实现写字动画效果
2018/11/30 Javascript
过滤器vue.filters的使用方法实现
2019/09/18 Javascript
对layui中table组件工具栏的使用详解
2019/09/19 Javascript
JS数组属性去重并校验重复数据
2020/01/10 Javascript
微信公众号中的JSSDK接入及invalid signature等常见错误问题分析(全面解析)
2020/04/11 Javascript
javascript设计模式 ? 观察者模式原理与用法实例分析
2020/04/22 Javascript
vuex实现购物车的增加减少移除
2020/06/28 Javascript
JavaScript位置参数实现原理及过程解析
2020/09/14 Javascript
Python牛刀小试密码爆破
2011/02/03 Python
Python程序员开发中常犯的10个错误
2014/07/07 Python
Windows和Linux下Python输出彩色文字的方法教程
2017/05/02 Python
Django中数据库的数据关系:一对一,一对多,多对多
2018/10/21 Python
Python中numpy模块常见用法demo实例小结
2019/03/16 Python
python求平均数、方差、中位数的例子
2019/08/22 Python
python GUI库图形界面开发之PyQt5访问系统剪切板QClipboard类详细使用方法与实例
2020/02/27 Python
CSS3 倾斜的网页图片库实例教程
2009/11/14 HTML / CSS
HTML5安全介绍之内容安全策略(CSP)简介
2012/07/10 HTML / CSS
新加坡第一的杂货零售商:NTUC FairPrice
2020/12/05 全球购物
数据库基础的一些面试题
2012/02/25 面试题
企业诚信承诺书
2014/05/23 职场文书
优秀求职信
2014/05/29 职场文书
宿舍卫生管理制度
2015/08/05 职场文书
丧事答谢词大全
2015/09/30 职场文书
Arthas排查Kubernetes中应用频繁挂掉重启异常
2022/02/28 MySQL
Spring Boot配合PageHelper优化大表查询数据分页
2022/04/20 Java/Android