JavaScript实现2048小游戏

首先要明白该游戏怎么玩,即

在 4*4 的16宫格中,您可以选择上、下、左、右四个方向进行操作,数字会按方向移动,相邻的两个数字相同就会合并,组成更大的数字,每次移动或合并后会自动增加一个数字。当16宫格中没有空格子,且四个方向都无法操作时,游戏结束。

首先,把16宫格看成是矩阵的形式

游戏开始时,随机生成两个数字,2或者4,出现在矩阵中任意位置

这部分是通过类名emptyItemnonEmptyItem来实现的。

步骤:

① 随机生成一个数字2或者4

② 获取所有空元素(类名emptyItem

③ 随机选择一个空元素,将生成的数字填充到空元素中,并将类名emptyItem移除,添加类名nonEmptyItem,即非空元素

④ 重复①、②、③步,再随机生成一个数字填充到随机的位置。

游戏的核心在于移动

移动有四个方向:上、下、左、右。实现思路如下:

如果触发向左移动
  遍历所有非空元素
    如果当前元素在第一个位置
           不动
    如果当前元素不在第一个位置
      如果当前元素左侧是空元素    
              向左移动
      如果当前元素左侧是非空元素    
        如果左侧元素和当前元素的内容不同    
                  不动
        如果左侧元素和当前元素的内容相同    
                  向左合并
 

如果触发向右移动
  遍历所有非空元素
    如果当前元素在最后一个位置     
           不动
    如果当前元素不在最后一个位置
      如果当前元素右侧是空元素   
              向右移动
      如果当前元素右侧是非空元素    
        如果右侧元素和当前元素的内容不同    
                  不动
        如果右侧元素和当前元素的内容相同    
                  向右合并

向上移动 和 向下移动的思路同上。

判断游戏是否结束

获取所有元素
获取所有非空元素
如果所有元素的个数 == 所有非空元素的个数
  循环遍历所有非空元素
    上面元素存在 && (当前元素的内容 == 上面元素的内容)   return
    下面元素存在 && (当前元素的内容 == 下面元素的内容)   return
    左边元素存在 && (当前元素的内容 == 左边元素的内容)   return
    右边元素存在 && (当前元素的内容 == 右边元素的内容)   return
   以上条件都不满足,Game Over! 

来看效果图

可以看到,游戏框上方是一些文字描述,游戏框外面是一个大的灰色的div,里面设定有4行,每行有4个单元格,一共是16个单元格。使用bootstrap的栅栏布局可以写成div.container>div.main>h2.title+h3.highestScore+h3.currentScore+div.panel.col-md-6>(div.row>(div.item.emptyItem)*4)*4,其中panel样式的div就是表示游戏框,row样式表示每一行,row里的每一行用小div来充当单元格,辅助说明的样式使用item表示单元格,emptyItem样式表示空单元格,nonEmptyItem表示非空单元格,然后根据矩阵中单元格的位置,再给各自的单元格添加样式,如x0y0,x1y0......根据这些样式,单独给每个单元格自定义一个x属性和y属性,x=“0”,y=“0”......具体作用在JS中体现。

html代码

		<div class="container">
			<div class="main">
				<h2 class="title">2048小游戏</h2>
				<h3 class="highestScore" >最高得分:<span id="highestScore"></span></h3>
				<h3 class="center">当前得分:<span id="score" class="score"></span></h3>
				<div class="panel col-md-offset-3 col-md-6 ">
					<div class="row">
						<div class="item emptyItem x0y0" x="0" y="0"></div>
						<div class="item emptyItem x0y1" x="0" y="1"></div>
						<div class="item emptyItem x0y2" x="0" y="2"></div>
						<div class="item emptyItem x0y3" x="0" y="3"></div>
					</div>
					<div class="row">
						<div class="item emptyItem x1y0" x="1" y="0"></div>
						<div class="item emptyItem x1y1" x="1" y="1"></div>
						<div class="item emptyItem x1y2" x="1" y="2"></div>
						<div class="item emptyItem x1y3" x="1" y="3"></div>
					</div>
					<div class="row">
						<div class="item emptyItem x2y0" x="2" y="0"></div>
						<div class="item emptyItem x2y1" x="2" y="1"></div>
						<div class="item emptyItem x2y2" x="2" y="2"></div>
						<div class="item emptyItem x2y3" x="2" y="3"></div>
					</div>
					<div class="row">
						<div class="item emptyItem x3y0" x="3" y="0"></div>
						<div class="item emptyItem x3y1" x="3" y="1"></div>
						<div class="item emptyItem x3y2" x="3" y="2"></div>
						<div class="item emptyItem x3y3" x="3" y="3"></div>
					</div>
				</div>	
				<div class="col-md-offset-3 col-md-6 score">
					<span>请通过键盘方向键进行操作</span>
					<button id="refreshButton" class="btn btn-danger col-md-offset-2">刷新</button>	
				</div>
			</div>
		</div>

在css代码中首先进行css初始化,然后根据效果图,自行配置。

在js文件中,首先进入游戏初始化函数gameInit(),

gameInit()中先将分数初始化,然后给“刷新”按钮绑定监听事件指向gameRefresh()函数,然后调用两次newItem函数添加两个随机元素(2或4),然后调用refreColor()函数遍历单元格,根据单元格文本值刷新背景色。

newItem()函数中,根据一个包含2和4的数组,随机选出一个,然后遍历所有空白单元格(有样式emptyItem的)然后随机选择一个,添加样式nonEmptyItem,删除样式emptyItem,然后修改文本值为2或4的随机数。

refreshColor()函数中,根据样式item,将所有单元格依据文本值进行背景颜色的修改。

全局添加键盘响应事件,根据keyCode的值,选择方向键的不同事件,先将isNewRndItem=false;表明每次移动之前将产生新元素的标记置为false,调用move函数和isGameOver()函数。

move()函数中,首先遍历所有非空的单元格(有nonEmptyItem样式),根据传入的方向direction参数,决定正反向遍历,其中正向遍历为左和上,反向遍历为右和下。

对于左移动和上移动,需要正向遍历有数值的单元格,当需要移动的时候,下标小的元素可以先往上移动或往左移动,
反之,对于右移动和下移动,需要反向遍历单元格,让下标大的单元格先移动,如果下标小的先移动,如果同一行有单元格的话,小的单元格会被大单元格挡住。在遍历的每个单元格中,调用moveItem()函数。

moveItem()函数中,首先调用getSideItem()函数,得到其旁边元素的信息(有或无,有值或空),根据旁边元素的样式和文本值,进行操作(移动,合并等)。

其余函数自行查看。

window.onload=function(){
	var isNewRndItem=false;//是否产生新元素
	var score=0;//初始分数
	var maxScore=0//最高分数
	if(localStorage.maxScore){//如果保存了最高分数
		maxScore=localStorage.maxScore-0;
	}
	else{
		maxScore=0;
	}
	//初始化游戏
	gameInit();
	
	
	//初始化设置
	function gameInit(){
		//初始化分数
		document.getElementById('score').innerHTML=score;
		
		//初始化最高分数
		document.getElementById('highestScore').innerHTML=maxScore;
		
		//为刷新按钮绑定刷新事件
		document.getElementById('refreshButton').onclick=gameRefresh;
		
		//初始化随机添加两个元素
		newItem();
		newItem();
		
		//刷新颜色
		refreshColor();
		
	}
	
	
	//重新开始游戏
	function gameRefresh(){
		
		//将所有单元格的nonEmptyItem样式清除并且数值清空
		var items=document.getElementsByClassName('item');
		for(var i=0;i<items.length;i++){
			items[i].innerHTML='';	
			items[i].classList.remove('nonEmptyItem');		
			items[i].classList.add('emptyItem');	
		}

		
		//将分数重置为score
		score=0
		document.getElementById('score').innerHTML=score;
//		document.getElementById('highestScore').innerHTML=localStorage.maxScore;
		
		//调用两次生成随机元素的函数
		newItem();
		newItem();
		
		//刷新颜色
		refreshColor();
				
	}
	
	//根据当前元素获取旁边的元素
	function getSideItem(currentItem,direction){
		//根据当前元素自定义的xy属性来获取当前元素的位置
		var currentItemX=currentItem.getAttribute('x')-0;
		var currentItemY=currentItem.getAttribute('y')-0;
		
		switch(direction){
			case "left":
				var sideItemX=currentItemX;
				var sideItemY=currentItemY-1;
				break;
			case "up":
				var sideItemX=currentItemX-1;
				var sideItemY=currentItemY;			
				break;
			case "right":
				var sideItemX=currentItemX;
				var sideItemY=currentItemY+1;			
				break;
			case "down":
				var sideItemX=currentItemX+1;
				var sideItemY=currentItemY;			
				break;			
		}
		
		var sideItem=document.getElementsByClassName("x"+sideItemX+"y"+sideItemY);
//		console.log(currentItem);
		console.log(sideItem);
		return sideItem;
		
	}
	
	//元素移动
	function moveItem(currentItem,direction){
		//首先获得旁边元素
		var sideItem=getSideItem(currentItem,direction);
		
//		if(sideItem.length==0){
		if(sideItem.length==0){
			//不存在旁边元素,说明当前元素在边上,不进行操作
		}
		else if(sideItem[0].innerHTML==""){//存在旁边元素,且为空元素
			sideItem[0].innerHTML=currentItem.innerHTML;
			sideItem[0].classList.remove('emptyItem');
			sideItem[0].classList.add('nonEmptyItem');
			
			currentItem.innerHTML="";
			currentItem.classList.remove('nonEmptyItem');
			currentItem.classList.add('emptyItem');
			moveItem(sideItem[0],direction);//继续遍历

			isNewRndItem=true;
		}
		else if(sideItem[0].innerHTML!=currentItem.innerHTML){//存在旁边元素,不为空,但是与当前元素的数值不相同
			//不移动
		}
		else{//存在旁边元素,不为空,与当前元素数值相同,两个相同元素合成一个大元素后需要产生两个新的元素
			sideItem[0].innerHTML=(currentItem.innerHTML-0)*2;
			
			currentItem.innerHTML="";
			currentItem.classList.remove('nonEmptyItem');
			currentItem.classList.add('emptyItem');
			moveItem(sideItem[0],direction);
			score+=(sideItem[0].innerHTML-0)*10;//更新当前分数
			document.getElementById('score').innerHTML=score;
			maxScore=maxScore<score?score:maxScore;
			document.getElementById('highestScore').innerHTML=maxScore;//更新最高分数
			localStorage.maxScore=maxScore;//更新存储中的最高分数
			isNewRndItem=true;
			return;
		}
		
	}
	
	//响应键盘监听事件,接收方向参数direction
	function move(direction){
		//获取所有有数值的单元格
		var nonEmptyItems=document.getElementsByClassName('nonEmptyItem');
		
		//此时判断移动分为左、上/右、下
		//对于左移动和上移动,需要正向遍历有数值的单元格,当需要移动的时候,下标小的元素可以先往上移动或往左移动
		//反之,对于右移动和下移动,需要反向遍历单元格,让下标大的单元格先移动,如果下标小的先移动,如果同一行有单元格的话,
		//小的单元格会被大单元格挡住
		
		if(direction=='left'||direction=='up'){
			for(var i=0;i<nonEmptyItems.length;i++){//正向遍历
				var currentItem=nonEmptyItems[i];
				moveItem(currentItem,direction);
			}
		}
		else if(direction=='right'||direction=='down'){
			for(var i=nonEmptyItems.length-1;i>=0;i--){//反向遍历
				var currentItem=nonEmptyItems[i];
				moveItem(currentItem,direction);				
			}
		}		
		
		if(isNewRndItem){//遍历完一遍后产生新的元素并刷新颜色
			newItem();
			refreshColor();
		}
	}


	//判断游戏是否结束,即遍历所有元素,
	function isGameOver(){
		var nonEmptyItems=document.getElementsByClassName('nonEmptyItem');
		var allItems=document.getElementsByClassName('item');
		if(nonEmptyItems.length==allItems.length){//所有单元格铺满,检查是否有可以合并的两个单元格
			for(var i=0;i<nonEmptyItems.length;i++){
				var currentItem=nonEmptyItems[i];
				
				//这个单元格最上方有元素且可以合并
				if(getSideItem(currentItem,"up").length!=0&&getSideItem(currentItem,"up")[0].innerHTML==currentItem.innerHTML){
					return;
				}
				
				//这个单元格最左方有元素且可以合并
				else if(getSideItem(currentItem,"left").length!=0&&getSideItem(currentItem,"left")[0].innerHTML==currentItem.innerHTML){
					return;				
				}
				
				//这个单元格最下方有元素且可以合并
				else if(getSideItem(currentItem,"down").length!=0&&getSideItem(currentItem,"down")[0].innerHTML==currentItem.innerHTML){
					return;
				}
				
				//这个单元格最右方有元素且可以合并
				else if(getSideItem(currentItem,"right").length!=0&&getSideItem(currentItem,"right")[0].innerHTML==currentItem.innerHTML){
					return;
				}
			}			
		}else{
			return;
		}	
		var x=confirm("游戏结束,是否重新开始");
		if(x){
			gameRefresh();
		}	
	}
	
	//生成新元素
	function newItem(){
		//创建包含2,4的数组
		var newRndArr=[2,2,4];
		
		//从数组中随机拿出元素。
		var newRndNum=newRndArr[Math.floor(Math.random()*3)];//产生0、1、2表示数组下标	
		
		//便于调试,调试随机生成的数字
//		console.log("新产生的元素是"+newRndNum);
		
		//将数字随机放到空白元素位置
		var emptyItems=document.getElementsByClassName('emptyItem');//获取所有空白元素单元格
		console.log(emptyItems);
		var newRndItem=Math.floor(Math.random()*(emptyItems.length));//选择随机一个空白单元格
//		console.log("位置是"+newRndItem);
		
		emptyItems[newRndItem].innerHTML=newRndNum;//将产生的随机数放到选好的随机的空白单元格中
		emptyItems[newRndItem].classList.add('nonEmptyItem');//表示该单元格已有元素
		emptyItems[newRndItem].classList.remove('emptyItem');//移除表示空元素的样式

//		refreshColor();
	}
	
	//刷新颜色
	function refreshColor(){
		//遍历所有单元格,分为空白单元格和有数值的单元格,分别设置颜色
		var items=document.getElementsByClassName('item');
		for(var i=0;i<items.length;i++){
			var itemsHTML=items[i].innerHTML;
			switch(itemsHTML){
				case '':
					items[i].style.background='#ECFFFF';
					break;
				case '2':
					items[i].style.background='#B0C4DE	';
					break;
				case '4':
					items[i].style.background='#00FFFF';
					break;
				case '8':
					items[i].style.background='#00FF7F';
					break;
				case '16':
					items[i].style.background='#FFFF00';
					break;
				case '32':
					items[i].style.background='#DAA520';
					break;
				case '64':
					items[i].style.background='#8B4513';
					break;
				case '128':
					items[i].style.background='#FF4500';
					break;
				case '256':
					items[i].style.background='#696969';
					break;
				case '512':
					items[i].style.background='#FF00FF';
					break;
			}
		}
	}
	
	//方向键监听事件
	document.onkeydown=function(e){
		switch(e.keyCode){//判断按键
			case 37://左方向键
				console.log("左移动");
				isNewRndItem=false;//不用产生新元素	
				move("left");//向左移动
				isGameOver();//判断是否游戏结束
				break;
			case 38://上方向键
				console.log("上移动");
				isNewRndItem=false;//不用产生新元素
				move("up");//向上移动
				isGameOver();//判断是否游戏结束
				break;
			case 39://右方向键
				console.log("右移动");
				isNewRndItem=false;//不用产生新元素
				move("right");//向右移动
				isGameOver();//判断是否游戏结束
				break;
			case 40://下方向键
				console.log("下移动");
				isNewRndItem=false;//不用产生新元素
				move("down");//向下移动
				isGameOver();//判断是否游戏结束
				break;
		}
	}
	
//	手机屏幕滑动事件	
	
}

参考地址:https://github.com/nnngu/js_game_2048

猜你喜欢

转载自blog.csdn.net/CWH0908/article/details/88756924