一.游戏简介

2048是一款休闲益智类的数字叠加小游戏,类似于欢乐消消消,很有意思的一个小游戏。

二.游戏玩法

在 4*4 的16宫格中,您可以选择上、下、左、右四个方向进行操作,数字会按方向移动,相邻的两个数字相同就会合并,组成更大的数字,每次移动或合并后会自动增加一个数字。

当16宫格中没有空格子,且四个方向都无法操作时,游戏结束。

三、游戏目的

目的是合并出 2048 这个数字,获得更高的分数。

四.游戏截图

数字叠加Python 数字叠加小游戏_数组

 

五.游戏实现的原理

把16个格子看成4*4的二维数组

在HTML中给每个格子添加类名和属性,来记录每个格子的位置

采用三元表达式随机生成0~1的小数,和0.5(可以设置游戏难度)比较,来给出2或4

最后再把数据更新到每个div中

判断游戏结束:

每个格子上的数都不为零,上下左右的数都不相同的时候就判断游戏结束

HTML设置整体的布局

 

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>2048小游戏</title>
	<link rel="stylesheet" type="text/css" href="2048.css">
</head>
<body>
    <div class="bag">
       <h1>2048</h1>
       <button onclick="game.start();">new game</button>
       <div class="score">
       <span>当前分数:</span>
       <span id="scoreValue">0</span>
       </div>
       <div class="highScore">
	       <span>最高得分:</span>
	       <span id="highScoreValue">0</span>
       </div>

		<div class="box">
		<!-- 第一行 -->
			<div class="item" id= "c00"></div>
			<div class="item" id= "c01"></div>
			<div class="item" id= "c02"></div>
			<div class="item" id= "c03"></div>
		<!-- 第二行 -->
			<div class="item" id= "c10"></div>
			<div class="item" id= "c11"></div>
			<div class="item" id= "c12"></div>
			<div class="item" id= "c13"></div>
		<!-- 第三行 -->
			<div class="item" id= "c20"></div>
			<div class="item" id= "c21"></div>
			<div class="item" id= "c22"></div>
			<div class="item" id= "c23"></div>
		<!-- 第四行 -->
			<div class="item" id= "c30"></div>
			<div class="item" id= "c31"></div>
			<div class="item" id= "c32"></div>
			<div class="item" id= "c33"></div>
	    </div>
	     <header id="false_1">
        <h1>GAME OVER</h1>
        <p>SCORE:<span id="endScore">0</span></p>
        <br>
        <button onclick="game.start();">再玩一次</button>
        </header>
	    </div>  

  <script type="text/javascript" src="2048.js"></script>
</body>
</html>CSS界面设置div样式
body{
	background-color: #fff8dc;
}
.bag{
	width: 600px;
	height: 650px;
	background-color: #ffe4e1;
	border-radius: 20px;
	margin: 0 auto;
	position: relative;
}
.bag>h1{
	position: absolute;
	font-size: 70px;
	color:#ff69b4;
	left: 75px;
	top: 30px;
}
.box{
	width: 450px;
	height: 450px;
	border-radius: 10px;
	background-color:#ffb6c1;
	position: absolute;
    top: 170px;
    left: 75px;

}
header{
	width: 450px;
	height: 450px;
	border-radius: 20px;
	background-color:rgba(20,20,20,0.5);
    z-index: -99;
    position: absolute;
    top: 170px;
    left: 75px;
}
header>h1{
	font-size: 50px;
	text-align: center;
	color: #ffb6c1;
}
header>button{
	width: 200px;
	height: 60px;
	font-size: 30px;
	color: #ff69b4;
	border-radius: 6px;
	border:none;
	background-color:#ffb6c1; 
	margin-left: 125px;
}
header>p{
	color: #ff69b4;
	font-size: 30px;
	font-weight: bold;
    text-align: center;
}
.false{
	z-index: 99;
}
.item{
	width: 100px;
	height: 100px;
	border-radius: 8px;
	background-color:#ff69b4;
	float:left;
	margin: 10px 0 0 10px;
	font-size: 60px;
	text-align: center;
	line-height: 100px;
}
.bag>button{
	width: 100px;
	height: 40px;
	font-size: 18px;
	color: #ff69b4;
	border-radius: 6px;
	border:none;
	background-color:#ffb6c1;
	position: absolute;
	right: 75px;
	top: 110px;
}
.bag>.score{
	width: 100px;
	height: 60px;
	background-color: #ffb6c1;
	position: absolute;
	border-radius: 6px;
	left: 75px;
    top:20px;
    color: #ff69b4;
	font-size: 18px;
	text-align: center;
}
.score>span{
	display: block;
}
.highScore{
	width: 100px;
	height: 60px;
	background-color: #ffb6c1;
	position: absolute;
	border-radius: 6px;
	right:75px;
    top:20px;
    color: #ff69b4;
	font-size: 18px;
	text-align: center;
}
.highScore>span{
	display: block;
}
.n2{background-color:#eee3da}
.n4{background-color:#ede0c8}
.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}
.n2048{background-color:#09c}
.n4096{background-color:#a6c}
.n8192{background-color:#93c}
.n2,.n4{color:#776e65}
.n1024,.n2048,.n4096,.n8192{font-size:40px}最后JS界面设计算法运算调用函数
var game = {
	RN:4,CN:4,
	data:null,//保存游戏的二维数组 //总行数,总列数
	score:0, //分数初始值为0
	highScore:0,//最高分初始值为0
	start(){
		//清零分数
		this.score = 0;
		//1.初始化data二维数组  每一个数据都初始化为
		//新建空数组存储到data
		this.data = [];
		//通过总行数和总列数,循环遍历每个数。并且赋值为0
		for(var r = 0;r<this.RN;r++){
          //空数组中的r行
          //新建一个空数组保存在data的r行中
          this.data[r] = [];
          for(var c = 0;c<this.CN;c++){
          	//空数组中r行c位置的数值  设置为0
             this.data[r][c] = 0;         
          }
		}
		//2.随机在二维数组中添加两个数值,2或4
		this.randomNum();
		this.randomNum();
		console.log(this.data.join('\n'));
		//3.将data二维数组中的数据展示到页面的div中去
		this.updataView();
		//4.触发游戏
		//当在键盘上按上,下,左,右触发事件
		document.onkeydown = function(e){
			switch(e.keyCode){
				case 37://左移
				//this.moveleft(); 这里面的this关键字不在指向game,因此调用game的成员不能使用this关键字
				game.moveLeft();
				break;
				case 39://右移
				game.moveRight();
				break;
				case 38://上移
				game.moveTop();
				break;
				case 40://下移
				game.moveBottom();
				break;
			}
		}
		//清零游戏结束界面的样式
		document.getElementById("false_1").className = '';
	},
	updataView(){
		//将data中的数据更新到div中
		//遍历二维数组data
		//r从0开始,到RN结束
		//c从0开始,到CN结束
		for(var r = 0;r<this.RN;r++){
			for(var c = 0;c<this.CN;c++){
		//找到id为"c"+r+c的div元素
		var div = document.getElementById("c"+r+c);
		//获取到data r行c列的数据  存储变量n中
		   var n = this.data[r][c];
		   //判断n是否为0 
		//如果是0
		//div的内容设置为空,清除div的内容
	    if(n==0){
             div.innerHTML = "";
             //恢复div的class为 item
		    div.className = "item";//否则
		   }else{
		   	//将div的值设置为n
		   	div.innerHTML = n;
		   	//将div的class设置为 item 'n'+n
		   	div.className ="item n"+n;
		   }
			}//c 的循环结束
		     //r 的循环结束
		}
		document.getElementById("scoreValue").innerHTML	= this.score;
		if(this.score>this.highScore){
			this.highScore = this.score;
		}
		document.getElementById('highScoreValue').innerHTML = this.highScore;
	},
	randomNum(){
		//在一个随机位置生成2或4
		//反复:
		while(true){
		// 在0~RN-1之间生成随机数r
		r = Math.floor(Math.random()*this.RN);
		// 在0~CN-1之间生成随机数c
		c = Math.floor(Math.random()*this.CN);
		// 根据data中r行c列的值来处理
		// 如果data中r行c列的值为0
		if(this.data[r][c] == 0){
		 // 将data中的r行c列赋值为:
		// 2或者4的数字  随机生成一个小数,如果<0.5,就取2,否则就取4
		 this.data[r][c] = Math.random()>0.6?2:4;
		 break;
		}
		// 退出循环
		}
	},
    end(){
    	for(var r = 0;r<this.RN;r++){
    		for(var c=0;c<this.CN;c++){
    			if(this.data[r][c]==0){
    				return false;
    			}
    		}
    	}
    for(var r=1;r<this.RN-1;r++){
    	for(var c=0;c<this.CN;c++){
    			if((this.data[r+1][c] == this.data[r][c])||(this.data[r-1][c]==this.data[r][c])){
                   return false;
    			}
    		}
    }
    for(var r=0;r<this.RN;r++){
    	for(var c=1;c<this.CN-1;c++){
    		if((this.data[r][c-1] == this.data[r][c])||(this.data[r][c+1] == this.data[r][c])){
              return false;
    		}
    	}
    }
    return true;
},
isend(){
	if(this.end()){
	document.getElementById("false_1").className = 'false';
	document.getElementById("endScore").innerHTML = this.score;
	}
},
	moveLeft(){
	//左移所有行
	//给数组拍照  var before = String(arr)
	var before = String(this.data);
	//r从0开始,到<RN结束
	for(var r = 0;r<this.RN;r++){	
		this.moveLeftInRow(r);
	}
	//左移r行
	//r循环结束
	//移动结束以后,再给数组拍照  赋值给after
	var after = String(this.data);
	//如果data数组中的数据有变化  如果before != after
	if(before != after){
	//调用updateView更新页面中数据
	this.randomNum();
	this.updataView();
	this.isend();
	}	
},
moveLeftInRow(r){
	//c从0开始,到<CN-1
	////查找r行c列 下一个不为0的位置nextc
	for(var c = 0;c<this.CN-1;c++){
		for(var i = c+1;i<this.CN;i++){ 
		// i从c+1开始,到<CN结束
	    // 当前i位置的值是否为0
			if(this.data[r][i] != 0){
				// 值不为0的位置  i赋值nextc
				var nextc = i;
				break; 
			}else{ 
			// 否则
	        // 将nextc赋值为-1
				 nextc = -1;
			}
			}// i循环结束
	       // 如果nextc的值为-1,证明后面的都是0,直接退出
			if (nextc == -1) {
				break;
			}else{// 否则
	// 找r行c列位置的值,判断是否为0
			 if(this.data[r][c] == 0) {// 如果是0  将nextc位置的值赋值给c位置
			 	this.data[r][c] = this.data[r][nextc];
			 	this.data[r][nextc] = 0;// 将nextc位置的值赋值为0
			 	c--;// 将c留在原地
			 }else if(this.data[r][c] == this.data[r][nextc]){
			 // 否则 else if(c位置的值等于nextc位置的值)
			 	this.data[r][c] *= 2;// 将c位置的值*2
			 	this.score += this.data[r][c];
			 	this.data[r][nextc] = 0;// 将nextc位置的值赋值为0
	      // c的循环结束
			 }             
			}
		}
	},
	moveRight(){
	//右移所有行
	//给数组拍照   var before = String(arr)
	var before = String(this.data);
	//r从0开始,到<RN结束
	for(var r = 0;r<this.RN;r++){	
		this.moveRightInRow(r);
	}
	//右移r行
	//r循环结束
	//移动结束以后,再给数组拍照  赋值给after
	var after = String(this.data);
	//如果data数组中的数据有变化  如果before != after
	if(before != after){
	//调用updateView更新页面中数据
	this.randomNum();
	this.updataView();
	this.isend();
	}	
},
moveRightInRow(r){
	//c从cn-1开始,到0
	////查找r行c列 上一个不为0的位置nextc
	for(var c = this.CN-1;c>0;c--){
		for(var i = c-1;i>=0;i--){ 
		// i从c-1开始,到>-1结束
	    // 当前i位置的值是否为0
			if(this.data[r][i] != 0){
				// 值不为0的位置  i赋值nextc
				var nextc = i;
				break; 
			}else{ 
			// 否则
	        // 将nextc赋值为-1
				 nextc = -1;
			}
			}// i循环结束
	       // 如果nextc的值为-1,证明后面的都是0,直接退出
			if (nextc == -1){
				break;
			}else{// 否则
	// 找r行c列位置的值,判断是否为0
			 if(this.data[r][c] == 0) {// 如果是0  将nextc位置的值赋值给c位置
			 	this.data[r][c] = this.data[r][nextc];
			 	this.data[r][nextc] = 0;// 将nextc位置的值赋值为0
			 	c++;// 将c留在原地
			 }else if(this.data[r][c] == this.data[r][nextc]){
			 // 否则 else if(c位置的值等于nextc位置的值)
			 	this.data[r][c] *= 2;// 将c位置的值*2
			 	this.score += this.data[r][c];
			 	this.data[r][nextc] = 0;// 将nextc位置的值赋值为0
	      // c的循环结束
			 }             
			}
		}
	},
	moveTop(){
	//上移所有列
	//给数组拍照   var before = String(arr)
	var before = String(this.data);
	//c从0开始,到<CN结束
	for(var c = 0;c<this.CN;c++){	
		this.moveTopInRow(c);
	}
	//上移c列
	//c循环结束
	//移动结束以后,再给数组拍照  赋值给after
	var after = String(this.data);
	//如果data数组中的数据有变化  如果before != after
	if(before != after){
	//调用updateView更新页面中数据
	this.randomNum();
	this.updataView();
	this.isend();
	}	
},
moveTopInRow(c){
	//r从0开始,到<CN-1
	////查找r行c列 下一个不为0的位置nextr
	for(var r = 0;r<this.RN-1;r++){
		for(var i = r+1;i<this.RN;i++){ 
		// i从r+1开始,到<RN结束
	    // 当前i位置的值是否为0
			if(this.data[i][c] != 0){
				// 值不为0的位置  i赋值nextr
				var nextr = i;
				break; 
			}else{ 
			// 否则
	        // 将nextr赋值为-1
				 nextr = -1;
			}
			}// i循环结束
	       // 如果nextr的值为-1,证明后面的都是0,直接退出
			if (nextr == -1) {
				break;
			}else{// 否则
	// 找r行c列位置的值,判断是否为0
			 if(this.data[r][c] == 0) {// 如果是0  将nextr位置的值赋值给r位置
			 	this.data[r][c] = this.data[nextr][c];
			 	this.data[nextr][c] = 0;// 将nextr位置的值赋值为0
			 	r--;// 将r留在原地
			 }else if(this.data[r][c] == this.data[nextr][c]){
			 // 否则 else if(c位置的值等于nextc位置的值)
			 	this.data[r][c] *= 2;// 将c位置的值*2
			 	this.score += this.data[r][c];
			 	this.data[nextr][c] = 0;// 将nextc位置的值赋值为0
	      // c的循环结束
			 }             
			}
		}
	},
	moveBottom(){
	//下移所有行
	//给数组拍照   var before = String(arr)
	var before = String(this.data);
	//c从0开始,到<CN结束
	for(var c = 0;c<this.CN;c++){	
		this.moveBottomInRow(c);
	}
	//下移c列
	//c循环结束
	//移动结束以后,再给数组拍照  赋值给after
	var after = String(this.data);
	//如果data数组中的数据有变化  如果before != after
	if(before != after){
	//调用updateView更新页面中数据
	this.randomNum();
	this.updataView();
	this.isend();
	}	
},
moveBottomInRow(c){
	//c从CN-1开始,到>0
	////查找r行c列 下一个不为0的位置nextr
	for(var r = this.CN-1;r>0;r--){
		for(var i = r-1;i>=0;i--){ 
		// i从r-1开始,到>-1结束
	    // 当前i位置的值是否为0
			if(this.data[i][c] != 0){
				// 值不为0的位置  i赋值nextr
				var nextr = i;
				break; 
			}else{ 
			// 否则
	        // 将nextr赋值为-1
				 nextr = -1;
			}
			}// i循环结束
	       // 如果nextr的值为-1,证明后面的都是0,直接退出
			if (nextr == -1){
				break;
			}else{// 否则
	// 找r行c列位置的值,判断是否为0
			 if(this.data[r][c] == 0) {// 如果是0  将nextr位置的值赋值给c位置
			 	this.data[r][c] = this.data[nextr][c];
			 	this.data[nextr][c] = 0;// 将nextr位置的值赋值为0
			 	r++;// 将c留在原地
			 }else if(this.data[r][c] == this.data[nextr][c]){
			 // 否则 else if(c位置的值等于nextr位置的值)
			 	this.data[r][c] *= 2;// 将c位置的值*2
			 	this.score += this.data[r][c];
			 	this.data[nextr][c] = 0;// 将nextr位置的值赋值为0
	      // r的循环结束
			 }             
			}
		}
	},
}

六.总结

1.布局,样式比较简单

2.js算法设计比较麻烦,构造函数不熟练,练习较少

3.游戏进行到一半,出现死循环,界面卡死不动,原因是循环里面的break位置没有放对地方