2023-04-14 使用纯JS实现一个2048小游戏

news2024/10/2 22:17:45

文章目录

      • 一.实现思路
        • 1.2048的逻辑
        • 2.移动操作的过程中会有三种情况
      • 二.代码部分:分为初始化部分和移动部分
        • 1.初始化部分
          • 1.1.生成第一个方块:
          • 1.2.生成第二个方块:
        • 2.移动过程部分:
      • 三.实现代码
        • 1.HTML部分
        • 2.CSS部分
        • 3.JS部分
          • 3.1.game对象的属性
          • 3.2.game对象的start方法
          • 3.3.game对象的randomNum方法
          • 3.4.game对象的dataView方法
          • 3.5.game对象的isGameOver方法
          • 3.6.game对象中设置移动的方法(以左移动为例)
            • 3.6.1.moveLeft()方法
            • 3.6.2.moveLeftinRow()方法
            • 3.6.3.moveLeftNum()方法
          • 3.7.监听用户的键盘按下事件,用户按下"上下左右"键位时,触发相应的移动函数
          • 3.8.游戏开始时,调用game对象中的start方法
          • 3.9.游戏结束时弹出结算框,结算框中绑定一个try again按钮,功能是可以再来一次
          • 3.10.完整js代码
      • 四.效果

一.实现思路

1.2048的逻辑

一共有16个格子,开局5个随机格子上生成带数字的方块,2或者4
通过控制方向来使原有的方块移动并产生新的方块

2.移动操作的过程中会有三种情况

  • 方块移动的方向没有其他方块
  • 方块移动的方向有其他方块,但数字不同
  • 方块移动的方向有其他方块,且数字相同

二.代码部分:分为初始化部分和移动部分

1.初始化部分

新建一个4X4二维数组,存储16个方块的值,
分两个步骤生成第一个和第二个方块

1.1.生成第一个方块:

用0-15的随机数代表第几个数组元素上面生成第一个方块块的值(值为2或者4),其余的值为0
具体生成2或者4,设置为随机,其中2的概率高一些,4的概率低一些,比如:
0-10之间,若随机值为0,生成2,否则生成4,比例:10:1

1.2.生成第二个方块:
  • 为什么第二个方块(以及其他所有后续的方块)不能使用上述方法?
    因为如果两次生成的随机数一样,那么就会出现bug:
    同一位置产生两个值

  • 第二个方块如何生成?
    先获取16个格子中,空格的个数,也就是二维数组中0的个数
    以空格的个数作为循环的次数

2.移动过程部分:

移动过程是根据输入的方向来控制方块的移动的

  • 首先获取输入的状态,即玩家操作的上下左右键
  • 其次,移动的控制逻辑是:
    • 遍历二维数组,找到每一个方块(以左移动为例)
      由于第一列无需移动,只从每一行的第二个方块开始判断
      (若第一个方块不存在:交换值,并让交换的原位置的值清0
    • 若当前方块的紧邻左方块和当前方块,两者的值相同,那么左边的值变为原来的两倍,当前方块的值清0
    • 若当前方块的紧邻左方块看和当前方块,两者的值不同,不作处理,不移动

三.实现代码

1.HTML部分

  • 结构:分数实时显示,4X4的2048游戏页面,如果游戏结束,则弹出结算框

  • 快捷键:div#all>div.cell#n0$@0*4+div.cell#n1$@0*4+div.cell#n2$@0*4+div.cell#n3$@0*4

  • 代码:2048.html

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>2048小游戏</title>
	<link rel="stylesheet" href="2048.css" />
</head>
<body>
	<!--分数-->
	<p class="header">SCORE: <span id="score01"></span></p>
	<!--游戏部分-->
	
	<div id="all">
		<!--第一行-->
		<div class="cell"  id="n00"></div>
		<div class="cell"  id="n01"></div>
		<div class="cell"  id="n02"></div>
		<div class="cell"  id="n03"></div>
		<!--第二行-->
		<div class="cell"  id="n10"></div>
		<div class="cell"  id="n11"></div>
		<div class="cell"  id="n12"></div>
		<div class="cell"  id="n13"></div>
		<!--第三行-->
		<div class="cell"  id="n20"></div>
		<div class="cell"  id="n21"></div>
		<div class="cell"  id="n22"></div>
		<div class="cell"  id="n23"></div>
		<!--第四行-->
		<div class="cell"  id="n30"></div>
		<div class="cell"  id="n31"></div>
		<div class="cell"  id="n32"></div>
		<div class="cell"  id="n33"></div>
	</div>
	<!--弹框-->
	<div id="gameover" >
		<p>
			GAME OVER
			<br />
			SCORE:<span id="score02"></span>
			<br />
			<a onclick="typeonce()">TYPE AGAIN</a>
		</p>
		
	</div>
	<script type="text/javascript" src="2048.js" ></script>
</body>
</html>

2.CSS部分

2038.css:每一个数量级的格子会获得相应的颜色

*{
  font-family: arial;
  /*	user-select: none;*/
  font-weight: bold;
  }
  
  .header{
  width: 480px;
  font-size:40px ;
  margin: 60px auto 5px auto;
  }
  
  #score01{
  color: #F00;
  }
  
  #all{
  width: 480px;
  height: 480px;
  background-color: #bbada0;
  margin: 0 auto;
  border-radius: 20px;
  }
  
  .cell{
  width: 100px;
  height: 100px;
  background-color: #ccc0b3;;
  border-radius: 10px;
  text-align: center;
  line-height: 100px;
  font-size: 40px;
  color: #fff;
  float: left;
  margin: 16px 0px 0px 16px;
  
  }
  
  .n2{
  background-color:#eee3da;
  color:#776e65;
  }
  .n4{
  background-color:#ede0c8;
  color:#776e65;
  }
  .n8{
  background-color:#f2b179;
  }
  .n16{
  background-color:#f59563;
  }
  .n32{
  background-color:#f67c5f;
  }
  .n64{
  background-color:#f65e3b;
  }
  .n128{
  background-color:#edcf72;
  }
  .n256{
  background-color:#edcc61;
  }
  .n512{
  background-color:#9c0;
  }
  .n1024{
  background-color:#33b5e5;
  font-size:40px;
  }
  .n2048{
  background-color:#09c;
  font-size:40px;
  }
  .n4096{
  background-color:#a6c;
  font-size:40px;
  }
  .n8192{
  background-color:#93c;
  font-size:40px;
  }
  
  #gameover{
  position: absolute;
  background-color: rgba(0,0,0,0.2);
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  font-size: 40px;
  display: none;
  }
  
  #gameover p{
  background-color: #fff;
  width: 300px;
  height: 200px;
  border-radius: 10px;
  line-height: 66.66px;
  text-align: center;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: -150px;
  margin-top: -150px;
  }
  
  #gameover p a{
  padding: 10px;
  text-decoration: none;
  background-color: #9f8d77;
  color: #fff;
  border-radius: 10px;
  cursor: pointer;
  }
  

3.JS部分

3.1.game对象的属性
  • data属性:是一个二维数组,用于存放数据
  • score属性:用于存放分数
  • gameruning属性:游戏开始时的状态码
  • gameover属性:游戏结束时的状态码
  • status属性:当前状态码
  /****对象属性****/
    data: [],  //二维数组:用于存放4X4的2048格子中的数字显示
    score: 0,  //分数:用于计算当前得分
    gamerunning: 1,  //游戏开始时的状态码:1
    gameover: 0,  //游戏结束时的状态码:0
    status: 1,  //当前状态码:取值是0或1(默认)
3.2.game对象的start方法

初始化属性值

  • data:二维数组的所有元素的值都为0
  • score:分数清零
  • status:状态码=游戏开始时的状态码

调用对象方法:

  • randomNum():让一个二维数组元素获得一个数(2或4)
  • dataView():更新视图,即把数组元素的值赋给对应的HTML结构中
//1.start方法:游戏开始时调用的对象方法,用于初始化
    start: function () {
    //还原对象属性
      this.data = [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0]
      ];
      this.score = 0;
      this.status = this.gamerunning;
	///调用对象方法
	for(var i=0;i<5;i++){
		this.randomNum();//游戏开始时需要在几个格子上出现2或4,就调用几次
	}
      this.dataView(); //更新视图
    },
3.3.game对象的randomNum方法

用于让一个随机的数组元素获取到随机的数字
步骤:
- 设置随机行数
- 设置随机列数
- 设置随机获得2或4的参数num
- 让随机行数+随机列数对应的二维数组元素获得num

 //2.randomNum方法:用于让一个随机的数组元素获取到随机的数字
    randomNum: function () {
      while (true) {//判断是否存在空白位置
        //随机行数
        var row = Math.floor(Math.random() * 4);//取值范围:0,1,2,3
        //随机列数
        var col = Math.floor(Math.random() * 4);//取值范围:0,1,2,3
        if (this.data[row][col] == 0) {
          //随机整数
          var num = Math.random() > 0.2 ? 2 : 4;//取值范围:2或者4,比例分别为80%和20%
          this.data[row][col] = num;//让一个随机的数组元素获取到随机的数字
          break;//停止循环
        }
    
      }
    },
3.4.game对象的dataView方法

用于更新视图,让数组中的值在对应的HTML元素中显示

思路是

  • 遍历#n00`#n3312个div
  • 把对应的二维数组元素赋给这些div的innerHTML
  • 并让这些被赋值的地址获得新类名.n32,.n128等等,这样显示出来会有不同的颜色
//3.dataView方法:用于更新视图,让数组中的值在对应的HTML元素中显示
	dataView:function(){
		/*为html元素赋值*/
		for(var row=0;row<4;row++){
			for(var col=0;col<4;col++){	
				var div=document.getElementById("n"+row+col);//字符串拼接:目标是往<div id="n12"></div>里面写内容
				if(this.data[row][col]!=0){
					div.innerHTML=this.data[row][col];
					div.className="cell n"+this.data[row][col];//css中每个阶段都有其样式如".n64{}","n128{}"等
				}else{
					div.innerHTML="";
					div.className="cell";
				}
			}
		}
		/*更新分数*/
		document.getElementById("score01").innerHTML=this.score;
		/*判断游戏是否结束*/
		if(this.status==this.gameover){//游戏已结束
			document.getElementById("gameover").style.display="block";//弹出结算框
			document.getElementById("score02").innerHTML=this.score;
		}else{//游戏未结束
			document.getElementById("gameover").style.display="none";//隐藏结算框
		}
	},
3.5.game对象的isGameOver方法

用于判断游戏是否结束,该函数返回一个布尔值
游戏未结束的三个情况:

  • 1.存在空格子
  • 2.上下行的数字相同
  • 3.左右列的数字相同
isGameOver: function () {
    //游戏未结束的三种情况:1.存在空格子;2.左右格子数字一样;3.上下格子数字一样
    for(var row=0;row<4;row++){
      for(var col=0;col<4;col++){
        if(this.data[row][col]==0) return false;//1.存在空格子
        if(col<3){//2.左右格子数字一样
          if(this.data[row][col]==this.data[row][col+1]) return false;
        }
        if(row<3){//3.上下格子数字一样
          if(this.data[row][col]==this.data[row+1][col]) return false;
        }
      }
    }
    return true;
  },
3.6.game对象中设置移动的方法(以左移动为例)
3.6.1.moveLeft()方法

当用户有左移操作时,触发该函数
该函数主要做这些事:

  • 调用moveLeftinRow()方法,目的是处理移动时的数据合并
  • 判断二维数组在调用上述方法前后是否发生变化
  • 若有,说明移动过程中发生了数据合并,那么需要做这些事:
    • 调用randomNum()方法,让一个随机的二维数组元素生成2或4
    • 判断游戏是否结束,若结束则让状态码=gameover,即游戏结束的状态码
    • 调用dataView()更新视图
//1.1.moveLeft方法:用户有左移操作时触发
	moveLeft:function(){
		// 左移前的data数组(为方便对比先进行字符化)
		var before=String(this.data);
		// 处理每一行的数据:调用moveLeftinRow方法
		for(var row=0;row<4;row++){
			this.moveLeftinRow(row);
		}
		// 左移后的data数组(为方便对比先进行字符化)
		var after=String(this.data);
		//若处理数据后data发生了变化
		if(before!=after){
			// 随机生成2或4
			this.randomNum();
			// 判断游戏是否结束
			if(this.isGameOver()) this.status==this.gameover;//更新状态码 
			// 更新视图
			this.dataView();
		}
	},
3.6.2.moveLeftinRow()方法

用于处理左移过程中的数据合并,分为两种情况:

  • 当前元素为0时:让当前元素与moveLeftNum的值调换
  • 当前元素与moveLeftNum的值相同时,当前元素的值倍增,moveLeftNum的值清零,分数累计
//1.2.moveLeftinRow方法:处理每一行的数据
	moveLeftinRow:function(row){
		for(var col=0;col<3;col++){//此处的col<3要与下面moveLeftNum方法中的循环条件i=col+1相对应
			var nextCol=this.moveLeftNum(row,col);//nextCol是指:与当前元素的同一行中,后面非空的那一列元素
			if(nextCol!=-1){//i
				// 向左移动时的两种情况:1.当前元素为0;2.当前元素与nextCol的值一样
				if(this.data[row][col]==0){
					// 1.当前元素为0——让当前元素的值变为nextCol,以及nextCol的值变为0
					this.data[row][col]=this.data[row][nextCol];
					this.data[row][nextCol]=0;
					col--;//继续返回最前列再次循环
				}else if(this.data[row][col]==this.data[row][nextCol]){
					//2.当前元素与nextCol的值一样——让当前元素的值倍增,nextCol的值变为0,分数累计
					this.data[row][col]*=2;
					this.data[row][nextCol]=0;
					this.score+=this.data[row][col]
				}
			}else{//-1
				break;
			}
		}
	},
3.6.3.moveLeftNum()方法

用于返回左移过程中,与当前元素同行不同列的非空元素的下标

	//1.3.moveLeftNum方法:返回左移过程中符合条件的下标col(第几列)
	moveLeftNum:function(row,col){
		for(var i=col+1;i<4;i++){
			if(this.data[row][i]!=0) {//条件就是:后面的元素非空就返回其下标
				return i;
			}
		}
		return -1;
	},
	/*
	 *后面的右移,上移和下移,参考左移即可
	 *难点主要是改变循环条件
	 *其中,上移对应的应该是左移,因为是从高行数(列数)向低行数(列数)遍历
	 *同理.下移对应的是右移,低==>高
	 */
3.7.监听用户的键盘按下事件,用户按下"上下左右"键位时,触发相应的移动函数
document.onkeydown = function (event) {
  if (event.keyCode == 65) game.moveLeft();//键盘左:A
  if (event.keyCode == 87) game.moveUp();//键盘上:W
  if (event.keyCode == 68) game.moveRight();//键盘右:D
  if (event.keyCode == 83) game.moveDown();//键盘下:S
}
3.8.游戏开始时,调用game对象中的start方法
game.start();
3.9.游戏结束时弹出结算框,结算框中绑定一个try again按钮,功能是可以再来一次
function typeonce() {
  document.getElementById("gameover").style.display = "none";
  window.location.href = "2048.html";
}
3.10.完整js代码
/******思路*******/

/*一.游戏环节:创建game对象*/
//属性:data,score,gamerunning,gameover,status
//普通方法:start,randomNum,dataView,isGameOver
//移动的方法:moveLeft,moveRight,moveUp,moveDown
//移动方法中处理每一行数据的方法:
//moveLeftinRow,moveLeftNum,moveRightinRow,moveRightNum
//moveUpinRow,moveUpNum,moveDowninRow,moveDownNum

// var game={};

/*二.监听鼠标按下事件,并调用game对象中的移动方法*/
//如果监听到上下左右对应的ASCII码,就触发相应的上下左右game移动方法

// document.οnkeydοwn=function(){}

/*三.游戏开始时,调用game对象中的start方法*/

// game.start();

/*四.点击再来一次*/
//隐藏#gameover元素,并跳转回2048.html

// function typeonce(){}
/**********************************************************************************/
/*一.游戏环节:创建game对象*/
//属性:data,score,gamerunning,gameover,status
//普通方法:start,randomNum,dataView,isGameOver
//移动的方法:moveLeft,moveRight,moveUp,moveDown
//移动方法中处理每一行数据的方法:
//moveLeftinRow,moveLeftNum,moveRightinRow,moveRightNum
//moveUpinRow,moveUpNum,moveDowninRow,moveDownNum
var game = {
  /****对象属性****/
    data: [],  //二维数组:用于存放4X4的2048格子中的数字显示
    score: 0,  //分数:用于计算当前得分
    gamerunning: 1,  //游戏开始时的状态码:1
    gameover: 0,  //游戏结束时的状态码:0
    status: 1,  //当前状态码:取值是0或1(默认)
	/****普通的对象方法****/
	//1.start方法:游戏开始时调用的对象方法,用于初始化
    start: function () {
    //还原对象属性
      this.data = [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0]
      ];
      this.score = 0;
      this.status = this.gamerunning;
	///调用对象方法
	for(var i=0;i<5;i++){
		this.randomNum();//游戏开始时需要在几个格子上出现2或4,就调用几次
	}
      this.dataView(); //更新视图
    },
  //2.randomNum方法:用于让一个随机的数组元素获取到随机的数字
    randomNum: function () {
      while (true) {//判断是否存在空白位置
        //随机行数
        var row = Math.floor(Math.random() * 4);//取值范围:0,1,2,3
        //随机列数
        var col = Math.floor(Math.random() * 4);//取值范围:0,1,2,3
        if (this.data[row][col] == 0) {
          //随机整数
          var num = Math.random() > 0.2 ? 2 : 4;//取值范围:2或者4,比例分别为80%和20%
          this.data[row][col] = num;//让一个随机的数组元素获取到随机的数字
          break;//停止循环
        }
    
      }
    },
	//3.dataView方法:用于更新视图,让数组中的值在对应的HTML元素中显示
	dataView:function(){
		/*为html元素赋值*/
		for(var row=0;row<4;row++){
			for(var col=0;col<4;col++){	
				var div=document.getElementById("n"+row+col);//字符串拼接:目标是往<div id="n12"></div>里面写内容
				if(this.data[row][col]!=0){
					div.innerHTML=this.data[row][col];
					div.className="cell n"+this.data[row][col];//css中每个阶段都有其样式如".n64{}","n128{}"等
				}else{
					div.innerHTML="";
					div.className="cell";
				}
			}
		}
		/*更新分数*/
		document.getElementById("score01").innerHTML=this.score;
		/*判断游戏是否结束*/
		if(this.status==this.gameover){//游戏已结束
			document.getElementById("gameover").style.display="block";//弹出结算框
			document.getElementById("score02").innerHTML=this.score;
		}else{//游戏未结束
			document.getElementById("gameover").style.display="none";//隐藏结算框
		}
	},
  //4.isGameOver:用于判断游戏是否已经结束,返回一个布尔值
  isGameOver: function () {
    //游戏未结束的三种情况:1.存在空格子;2.左右格子数字一样;3.上下格子数字一样
    for(var row=0;row<4;row++){
      for(var col=0;col<4;col++){
        if(this.data[row][col]==0) return false;//1.存在空格子
        if(col<3){//2.左右格子数字一样
          if(this.data[row][col]==this.data[row][col+1]) return false;
        }
        if(row<3){//3.上下格子数字一样
          if(this.data[row][col]==this.data[row+1][col]) return false;
        }
      }
    }
    return true;
  },
	/****设置移动的对象方法****/
	/**1.左移**/
	//1.1.moveLeft方法:用户有左移操作时触发
	moveLeft:function(){
		// 左移前的data数组(为方便对比先进行字符化)
		var before=String(this.data);
		// 处理每一行的数据:调用moveLeftinRow方法
		for(var row=0;row<4;row++){
			this.moveLeftinRow(row);
		}
		// 左移后的data数组(为方便对比先进行字符化)
		var after=String(this.data);
		//若处理数据后data发生了变化
		if(before!=after){
			// 随机生成2或4
			this.randomNum();
			// 判断游戏是否结束
			if(this.isGameOver()) this.status==this.gameover;//更新状态码 
			// 更新视图
			this.dataView();
		}
	},
	//1.2.moveLeftinRow方法:处理每一行的数据
	moveLeftinRow:function(row){
		for(var col=0;col<3;col++){//此处的col<3要与下面moveLeftNum方法中的循环条件i=col+1相对应
			var nextCol=this.moveLeftNum(row,col);//nextCol是指:与当前元素的同一行中,后面非空的那一列元素
			if(nextCol!=-1){//i
				// 向左移动时的两种情况:1.当前元素为0;2.当前元素与nextCol的值一样
				if(this.data[row][col]==0){
					// 1.当前元素为0——让当前元素的值变为nextCol,以及nextCol的值变为0
					this.data[row][col]=this.data[row][nextCol];
					this.data[row][nextCol]=0;
					col--;//继续返回最前列再次循环
				}else if(this.data[row][col]==this.data[row][nextCol]){
					//2.当前元素与nextCol的值一样——让当前元素的值倍增,nextCol的值变为0,分数累计
					this.data[row][col]*=2;
					this.data[row][nextCol]=0;
					this.score+=this.data[row][col]
				}
			}else{//-1
				break;
			}
		}
	},
	//1.3.moveLeftNum方法:返回左移过程中符合条件的下标col(第几列)
	moveLeftNum:function(row,col){
		for(var i=col+1;i<4;i++){
			if(this.data[row][i]!=0) {//条件就是:后面的元素非空就返回其下标
				return i;
			}
		}
		return -1;
	},
	/*
	 *后面的右移,上移和下移,参考左移即可
	 *难点主要是改变循环条件
	 *其中,上移对应的应该是左移,因为是从高行数(列数)向低行数(列数)遍历
	 *同理.下移对应的是右移,低==>高
	 */
	/**2.右移**/
	//2.1.moveRight
	moveRight:function(){
		var before=String(this.data);
		for(var row=0;row<4;row++){//右移时,行数遍历不变,都是从第一行开始遍历
			this.moveRightinRow(row);
		}
		var after=String(this.data);
		if(before!=after){
			this.randomNum();
			if(this.isGameOver()) this.status==this.gameover;
			this.dataView();
		}
	},
	//2.2.moveRightinRow方法:处理每一行的数据
	moveRightinRow:function(row){
		for(var col=3;col>=0;col--){//右移时,要从右向左遍历,当前元素是右元素,lastCol是左元素
			var lastCol=this.moveRightNum(row,col);
			if(lastCol!=-1){
				//向右移动需要处理的两个情况:1.当前元素为0;2.当前元素和lastCol一样
				if(this.data[row][col]==0){
					//1.当前元素为0:当前元素的值变为lastCol,lastCol的值变为0
					this.data[row][col]=this.data[row][lastCol];
					this.data[row][lastCol]=0;
					col++;
				}else if(this.data[row][col]==this.data[row][lastCol]){
					//2.当前元素和lastCol一样:当前元素的值倍增,lastcol的值变为0,分数累计
					this.data[row][col]*=2;
					this.data[row][lastCol]=0;
					this.score+=this.data[row][col];
				}
			}else{
				break;
			}
		}
	},
	//2.3.moveRightNum方法:获取非空元素的下标
	moveRightNum:function(row,col){
		for(var i=col-1;i>=0;i--){//右移时,从右向左遍历,因此lastCol的值不能包含最后一列
			if(this.data[row][i]!=0) return i;
		}
		return -1;
	},
  	/**3.上移**/
	//3.1.moveUp方法:用户有上移操作时触发
	moveUp:function(){
		var before=String(this.data);
		for(var col=0;col<4;col++){//上移时,从第一列开始处理数据
			this.moveUpinCol(col);
		}
		var after=String(this.data);
		if(before!=after){
			this.randomNum();//获取2或4
			if(this.isGameOver()) this.status==this.gameover;//若游戏结束就更新状态码
			this.dataView();//更新视图
		}
	},
	//3.2.moveUpinCol
	moveUpinCol:function(col){
		// 上移过程中需要处理的两种情况:1.当前元素值为0;2.当前元素和nextRow一样
		for(var row=0;row<3;row++){
			var nextRow=this.moveUpNum(row,col);
      if(nextRow!=-1){
        if(this.data[row][col]==0){
          this.data[row][col]=this.data[nextRow][col];
          this.data[nextRow][col]=0;
          row--;
        }else if(this.data[row][col]==this.data[nextRow][col]){
          this.data[row][col]*=2;
          this.data[nextRow][col]=0;
          this.score+=this.data[row][col];
        }
      }else{
        break;
      }
		}
	},
	//3.3.moveUpNum方法:获取向上遍历时符合条件的非空元素
	moveUpNum:function(row,col){
		for(var i=row+1;i<4;i++){//让nextCol的值从第二行开始遍历
			if(this.data[i][col]!=0) return i;
		}
		return -1;
	},
	/**4.下移**/
	//4.1.moveDown:用户有下移操作时触发
  moveDown:function(){
		var before=String(this.data);
		for(var col=0;col<4;col++){//下移时,从第一列开始处理数据
			this.moveDowninCol(col);
		}
		var after=String(this.data);
		if(before!=after){
			this.randomNum();//获取2或4
			if(this.isGameOver()) this.status==this.gameover;//若游戏结束就更新状态码
			this.dataView();//更新视图
		}
	},
	//4.2.moveDowninCol
	moveDowninCol:function(col){
		// 下移过程中需要处理的两种情况:1.当前元素值为0;2.当前元素和lastRow一样
		for(var row=3;row>=0;row--){//下移时需要从最后一行开始遍历,当前元素是下元素,lastRow是下元素
			var lastRow=this.moveDownNum(row,col);
			if(lastRow!=-1){
				if(this.data[row][col]==0){// 1.当前元素的值为0
					this.data[row][col]=this.data[lastRow][col];
					this.data[lastRow][col]=0;
					row++;//重新回到下面一行进行循环
				}else if(this.data[row][col]==this.data[lastRow][col]){// 2.当前元素和lastRow一样
					this.data[row][col]*=2;
					this.data[lastRow][col]=0;
					this.score+=this.data[row][col];
				}
			}else{
				break;
			}
		}
	},
	//4.3.moveDownNum方法:获取向下遍历时符合条件的非空元素
	moveDownNum:function(row,col){
    for (var i = row - 1; i >= 0; i--) {//让nextCol的值从第二行开始遍历
      if (this.data[i][col] != 0) return i;
    }
    return -1;
  }
};

/*二.监听鼠标按下事件,并调用game对象中的移动方法*/
//如果监听到上下左右对应的ASCII码,就触发相应的上下左右game移动方法
document.onkeydown = function (event) {
  if (event.keyCode == 65) game.moveLeft();//键盘左:A
  if (event.keyCode == 87) game.moveUp();//键盘上:W
  if (event.keyCode == 68) game.moveRight();//键盘右:D
  if (event.keyCode == 83) game.moveDown();//键盘下:S
}

/*三.游戏开始时,调用game对象中的start方法*/
game.start();

/*四.点击再来一次*/
//隐藏#gameover元素,并跳转回2048.html
function typeonce() {
  document.getElementById("gameover").style.display = "none";
  window.location.href = "2048.html";
}

四.效果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/428379.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

材料科学基础学习指导-吕宇鹏-名词和术语解释-第5章:相图

目录 第一部分 第二部分​​​​​​​ 第三部分 第四部分​ 第一部分 1.1组元&#xff1a;是材料科学中的基本术语。意思是组成合金的独立的、最基本的单元。 1.2相&#xff1a; 指合金中具有同一聚集状态、同一晶体结构和性质并以界面相互隔开的均匀组成部分。​​​​…

【DS】河南省第十三届ICPC大学生程序设计竞赛 J-甜甜圈

明天就要省赛了&#xff0c;感觉已经寄了捏 J-甜甜圈_河南省第十三届ICPC大学生程序设计竞赛&#xff08;重现赛&#xff09; (nowcoder.com) 题意&#xff1a; 思路&#xff1a; 直接模拟复杂度太高&#xff0c;因此考虑用DS优化 我们考虑用树状数组维护 在用线段树和树状…

python 填充Word文档模板 循环填充表格、图片 docxtpl、 jinja2

python 填充Word文档 循环填充表格、图片codeword模板input.txt 内容参考code from docxtpl import DocxTemplate, InlineImage from docx.shared import Inches, Cm, Mm import jinja2word_template tpl.docx out_word_file new_test.docx input_file "input.txt&quo…

【机器学习】决策树(实战)

决策树&#xff08;实战&#xff09; 目录一、准备工作&#xff08;设置 jupyter notebook 中的字体大小样式等&#xff09;二、树模型的可视化展示1、通过鸢尾花数据集构建一个决策树模型2、对决策树进行可视化展示的具体步骤3、概率估计三、决策边界展示四、决策树的正则化&a…

【Microsoft Edge】关于 Microsoft Edge 浏览器多版本安装目录结构的测试分析

文章目录1. 问题描述准备工作二、测试2.1. 运行手动保存的安装程序无法安装2.2、依次从低版本到高版本安装2.2.1 运行腾讯电脑管家下载的正式版 112.0.1722.392.2.2 用 MicrosoftEdgeSetup 安装包安装正式版 112.0.1722.482.2.3 用 MicrosoftEdgeSetupBeta 安装包安装 Beta 版 …

别再只会使用简单的ping命令了,Linux中这些高级ping命令可以提高工作效率!

当你需要测试网络连接或者诊断网络问题时&#xff0c;ping命令是一个非常有用的工具。除了基本的用法&#xff0c;ping还有一些高级用法&#xff0c;可以帮助你更好地使用它。 一、基本用法 首先&#xff0c;让我们回顾一下ping的基本用法。ping命令用于测试与另一台计算机的…

C++ 类之间的横向关系(组合、依赖、关联和聚合)

目录 组合&#xff08;复合&#xff09; 定义 举例 依赖 定义 举例 关联 定义 举例 聚合 定义 举例 组合&#xff08;复合&#xff09; 定义 它是一种"is a part of"的关系&#xff0c;部分与整体&#xff0c;包含与被包含。组合是一个类中包含另一个类对…

企业如何高效管理新媒体矩阵账号?提升运营效率,监管内容风险

2023年&#xff0c;面对不确定的市场环境&#xff0c;更要精准把控业务动向&#xff0c;对于在抖音、小红书、视频号、快手等新媒体平台&#xff0c;布局大量账号的企业&#xff0c;实现矩阵账号的统一治理&#xff0c;不仅是应对市场不确定性影响的关键&#xff0c;更为有效降…

NC65合并报表如何取消上报并退回以及注意事项和相关问题总结

NC65合并报表如何取消上报并退回&#xff1f; 在【企业绩效管理】-【合并报表】-【合并】-【合并执行】节点中&#xff0c;点击〖数据中心〗按钮&#xff0c;在弹出的〖合并报表数据中心〗界面中&#xff0c;点击〖报送管理〗-〖合并方案请求退回〗&#xff0c;然后到【合并综…

Qt编写推流综合应用示例-文件推流

一、功能特点 1.1 文件推流 指定网卡和监听端口,接收网络请求推送音视频等各种文件。实时统计显示每个文件对应的访问数量、总访问数量、不同IP地址访问数量。可指定多种模式,0-直接播放、1-下载播放。实时打印显示各种收发请求和应答数据。每个文件对应MD5加密的唯一标识符…

JavaScript的基础

文章目录一、JavaScript 声明提升二、JavaScript 严格模式(use strict)三、JavaScript 表单四、JavaScript 表单验证五、JavaScript 验证 API总结一、JavaScript 声明提升 JavaScript 中&#xff0c;函数及变量的声明都将被提升到函数的最顶部。 JavaScript 中&#xff0c;变…

ClickHouse之采样查询(SAMPLE)

文章目录SAMPLE概念SAMPLE功能SAMPLE语法sample Ksample Nsample K offset mgroupArraySample语法SAMPLE概念 该 SAMPLE 子句允许近似于 SELECT 查询处理。 启用数据采样时&#xff0c;不会对所有数据执行查询&#xff0c;而只对特定部分数据&#xff08;样本&#xff09;执行…

电脑C盘被格式化 如何快速将被删的数据文件恢复找回?

如今的互联网时代&#xff0c;不管是办公还是休闲&#xff0c;一般都喜欢在电脑上进行操作&#xff0c;尤其是办公&#xff0c;感觉有了电脑能轻松不少&#xff0c;可以将一些工作中的重要数据保存在电脑里面&#xff0c;那么电脑里面的数据真就很安全了吗&#xff1f;这个就让…

Unity ShaderVariant 变体收集方案分析

最近遇到一个问题&#xff0c;在editor中场景渲染正确&#xff0c;打包android之后&#xff0c;渲染异常。 经过排查得出原因&#xff1a;工程把所有shader单独打包Assetbundle&#xff0c;editor打包ab包的时候&#xff0c;未收集到正确的shader变体&#xff0c;未将场景中使…

go语言并发编程

并发编程1.并发介绍1.1进程和线程1.2并发和并行1.3协程和线程1.协程2.线程1.4goroutine只是由官方实现的超级"线程池"2.Goroutine2.1使用Goroutine1.启动单个goroutine2.启动多个goroutine3.goroutine与线程3.1可增长的栈3.2goroutine调度3.runtime包3.1runtime.Gosc…

人员玩手机离岗识别检测系统 yolov5

人员玩手机离岗识别检测系统根通过pythonyolov5网络模型识别算法技术&#xff0c;人员玩手机离岗识别检测算法可以对画面中人员睡岗离岗、玩手机打电话、脱岗睡岗情况进行全天候不间断进行识别检测报警提醒。Python是一种由Guido van Rossum开发的通用编程语言&#xff0c;它很…

Nginx 的docker部署及宿主机配置文件修改重启

Nginx是一款高性能的Web服务器&#xff0c;用于反向代理、负载均衡、HTTP缓存等。在docker中部署Nginx可以更加方便地管理和配置。下面是部署Nginx的步骤&#xff1a; 步骤一&#xff1a;拉取Nginx镜像 首先需要从Docker Hub上拉取Nginx镜像&#xff0c;可以使用以下命令&…

windows11系统关闭右键【显示更多选项】

在新的Win11操作系统之中&#xff0c;微软为了美化界面&#xff0c;将右键菜单进行了整合更改&#xff0c;但是实用性却大幅度下降&#xff0c;引起了很多用户的反感&#xff0c;并迫切希望能够将Win11显示更多选项这个反人类的设置恢复成Win10的状态。 方法一&#xff1a;更改…

学成在线笔记0-面试问题

【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线牛客面试题 目录 介绍你的项目 项目难点 CDN是什么&#xff1f; 负载均衡是怎么做的&#xff1f; git使用了什么仓库&#xff1f; git代码冲突怎么处理&#xff1f; 你…

linux入门---文件系统

目录标题为什么会有文件系统磁盘的物理结构磁盘的存储结构磁盘的逻辑结构文件系统为什么会有文件系统 在我们的云服务器上存在着很多的文件&#xff0c;但并不是所有的文件都是被打开的&#xff0c;操作系统得管理好已经被打开的文件&#xff0c;那么同样的道理在磁盘中没有被…