1. 程式人生 > >+js實現簡單的2048小遊戲

+js實現簡單的2048小遊戲

學習前端半年,本著興趣用canvas結合了js寫了個2048小遊戲,有的地方沒有完善的歡迎各位大神指出。

在寫遊戲開始,首先要在html裡建立canvas標籤,併為canvas增添id,以便使用js呼叫。

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>2048小遊戲</title>
</head>
<body>
	<canvas id="canvas">當前瀏覽器不支援canvas,請換取較高階瀏覽器嘗試該遊戲</canvas>
	<script type="text/javascript" src="js/2048.js"></script>
</body>
</html>

這裡在canvas標籤中寫入了“當前瀏覽器不支援canvas,請換取較高階瀏覽器嘗試該遊戲”,以達到平穩退化的效果,如果瀏覽器不支援canvas標籤,就會顯示這句話,而當瀏覽器支援canvas標籤時這句話不會顯示出來。

緊接著呼叫了在根目錄下建立的js檔案裡的2048.js。

首先使用變數儲存對canvas的呼叫,設定canvas的寬高,初始化儲存2048方格中數字的陣列和方向按鍵對應的鍵碼,以方便修改。

var canvas=document.getElementById('canvas');
var context=canvas.getContext('2d');
//設定畫布寬高
canvas.width=400;
canvas.height=400;
//建立記錄數字的陣列
var numbers=new Array(4);
for(var i=0;i<4;i++)
{
	numbers[i]=new Array(4);
}
//初始化按鍵
var left=37;
var up=38;
var right=39;
var down=40;

然後開始建立顯示2048的方格,編寫一個方法在canvas畫布上畫出方格。(這裡用到的canvas的API就不一一贅述了)

//畫出圓角正方形
function drawRct(x,y,l,r)//引數依次為方格左上角x座標,y座標,邊長,四個角的彎曲弧度
{
		context.beginPath();
		context.moveTo(x+r,y);
		context.lineTo(x+l-r,y);
		context.arcTo(x+l,y,x+l,y+r,r);
		context.lineTo(x+l,y+l-r);
		context.arcTo(x+l,y+l,x+l-r,y+l,r);
		context.lineTo(x+r,y+l);
		context.arcTo(x,y+l,x,y+l-r,r);
		context.lineTo(x,y+r);
		context.arcTo(x,y,x+r,y,r);
		context.fillStyle='rgba(102,102,102,0.6)';
		context.closePath();
		context.fill();
		context.stroke();
}

使用已經建立的drawRct方法繪製出大的方格和包含每個數字的方格。

//畫出數字容器
function drawContainer(){
	drawRct(0,0,400,10);
	for(var i=0;i<4;i++)
	{
		for(var j=0;j<4;j++)
		{
			drawRct(10+100*i,10+100*j,80,5);
		}
	}
}

建立方格後,要考慮將數字新增到方格中,在2048中方塊移動後會隨機在空的方格中生成一個2,這裡首先判斷方格是否為空,再將2新增進去,當方格不為空時,重新呼叫該方法,重新獲取隨機值,在另一個方格生成2。為了防止陷入迴圈,首先判斷當前是否還有方格為空。

//判斷當前是否被填滿
function doubleEmpty(){
	for(var i=0;i<4;i++)
	{
		for(var j=0;j<4;j++)
		{
			if(numbers[i][j]=='')
				return true;//還有空格
		}
	}
	return false;
}
//隨機在位置生成數字2
function generateNumber(){
	if(doubleEmpty())
	{
		var randomI=Math.floor(Math.random()*4);
		var randomJ=Math.floor(Math.random()*4);
		if(numbers[randomI][randomJ]=='')
		{
			numbers[randomI][randomJ]='2';
		}
		else
			generateNumber();
	}
}

完成了對陣列中數字的生成,接下來就要將數字寫入canvas畫布中,這裡建立一個方法將陣列中對應位置的數字寫入canvas畫布中對應的位置。

//將陣列中的數字寫入canvas畫布中
function numbersWrite(){
	for(var i=0;i<numbers.length;i++)
	{
		for(var j=0;j<numbers[i].length;j++)
		{
			context.beginPath();
			if(numbers[i][j]=='0')
				numbers[i][j]='';
			context.textAlign='center';
			context.textBaseline='middle';
			context.font='28px Adobe Ming Std';
			context.fillText(numbers[i][j],50+i*100,50+j*100);
			context.fill();
		}
	}
}

完成了數字的寫入,緊接著要完成的就是2048的核心,數字的移動和合並,首先通過將數字移向方向鍵對應的方向,再進行合併。以向上的按鍵為例。

document.onkeydown=function(event){
	var e=event||window.event;
    if(e.keyCode==up)//按上鍵時
	{
	//移動
	for(var i=0;i<4;i++)
	{
		for(var j=1;j<4;j++)
		{
			var k=1;
			while(j-k>=0)
			{
				if(numbers[i][j-k]=='')
				{
					numbers[i][j-k]=numbers[i][j-k+1];
					numbers[i][j-k+1]='';
				}
				k++;
			}
		}
	}
	//合併方塊
	for(var i=0;i<4;i++)
	{
		for(var j=1;j<4;j++)
		{
			if(numbers[i][j]==numbers[i][j-1]&&numbers[i][j]!='')
			{
				numbers[i][j-1]=parseInt(int(numbers[i][j]))+parseInt(int(numbers[i][j-1]))+'';
				numbers[i][j]='';
			}
		}
	}
    context.clearRect(0,0,canvas.width,canvas.height);
    generateNumber();
    drawContainer();
	numbersWrite();
}
//在合併將空的方格變為0以便合併
function int(element){
	if(element=='')
		return 0;
	else
		return element;
}

在這裡因為合併是通過陣列內的數字相加而達到的效果,但當方格內為空時會出現錯誤,為了解決這個問題,我編寫了一個int方法,當傳入變數為空時返回0,否則返回原來的變數。在每次改變陣列內的值後,呼叫之前定義的方法將方格容器和數字寫入canvas畫布中並隨機生成數字2。但是這裡會出現一個問題,即按下按鍵後無論方格是否有移動,都會隨機生成2,為了解決這一問題,我在一開始重新聲明瞭另外一個數組numDouble,使其中每個位置對應等於numbers的每個位置,在每次按下按鍵時先用numDouble儲存之前陣列的值,在按下按鍵後再用方法來判斷兩個陣列內的值是否完全相同。如果不同則生成2,否則不生成2。

var numDouble=new Array(4);
for(var i=0;i<4;i++)
{
	numDouble[i]=new Array(4);
}
document.onkeydown=function(event){
	var e=event||window.event;
    for(var i=0;i<4;i++)
	{
		for(var j=0;j<4;j++)
		{
			numDouble[i][j]=parseInt(int(numbers[i][j]))+'';
			if(numDouble[i][j]=='0')
				numDouble[i][j]='';
		}
	}
    if(e.keyCode==up)//按上鍵時
	{
	//移動
	for(var i=0;i<4;i++)
	{
		for(var j=1;j<4;j++)
		{
			var k=1;
			while(j-k>=0)
			{
				if(numbers[i][j-k]=='')
				{
					numbers[i][j-k]=numbers[i][j-k+1];
					numbers[i][j-k+1]='';
				}
				k++;
			}
		}
	}
	//合併方塊
	for(var i=0;i<4;i++)
	{
		for(var j=1;j<4;j++)
		{
			if(numbers[i][j]==numbers[i][j-1]&&numbers[i][j]!='')
			{
				numbers[i][j-1]=parseInt(int(numbers[i][j]))+parseInt(int(numbers[i][j-1]))+'';
				numbers[i][j]='';
			}
		}
	}
    context.clearRect(0,0,canvas.width,canvas.height);
    if(!doubleChange(numDouble,numbers))
		generateNumber();
    drawContainer();
	numbersWrite();
}

完成上述程式碼後執行,其中會存在一個bug,因為我是在移動後合併的,但合併後的數字沒有再移動,導致當有一行的數字為“0 4 2 2”時按下右鍵,這一行會變成“0 4 0 4”,而不是我們預期中的“0 0 4 4”,為了解決這一問題,我在合併之後進行了第二次移動。

在完成移動合併後,需要判斷遊戲的失敗,遊戲的失敗首先是要方格中所有的格子都被入數字,且所有數字都與其相鄰的四個方向上的數字不同,因此要寫入兩個方法,判斷格子是否已填滿和判斷是否每個格子的數字都在所有方向上無法移動。將這兩個方法寫入一個方法中已判斷是否失敗。

//判斷當前是否被填滿
function doubleEmpty(){
	for(var i=0;i<4;i++)
	{
		for(var j=0;j<4;j++)
		{
			if(numbers[i][j]=='')
				return true;//還有空格
		}
	}
	return false;
}
//判斷方塊與其四個方位是否相同
function doubleSame(i,j){
	if(i!=0)
	{
		if(numbers[i][j]==numbers[i-1][j])
			return true;
	}
	if(i!=3)
	{
		if(numbers[i][j]==numbers[i+1][j])
			return true;
	}
	if(j!=0)
	{
		if (numbers[i][j]==numbers[i][j-1])
			return true;
	}
	if(j!=3)
	{
		if(numbers[i][j]==numbers[i][j+1])
			return true;
	}
	return false;
}
//判斷當前是否已失敗
function doubleLose(){
	var flag=0;
	if(!doubleEmpty())
	{
		for(var i=0;i<4;i++)
		{
			for(var j=0;j<4;j++)
				if(!doubleSame(i,j))//方格四個方向都無法移動時flag加一
					flag++;
		}
	}
	if(flag==16)
		return false;//所有方格都無法移動
	else
		return true;//還未失敗
}

完成之後將判斷失敗放在按鍵按下的方法中。四個方向的按鍵按下後的程式碼如下:

//按方向鍵時方塊移動
function numberMove(){
	document.onkeydown=function(event){
		var e=event||window.event;
		for(var i=0;i<4;i++)
		{
			for(var j=0;j<4;j++)
			{
				numDouble[i][j]=parseInt(int(numbers[i][j]))+'';
				if(numDouble[i][j]=='0')
					numDouble[i][j]='';
			}
		}
		if(e.keyCode==up)//按上鍵時
		{
			//第一次移動
			for(var i=0;i<4;i++)
			{
				for(var j=1;j<4;j++)
				{
					var k=1;
					while(j-k>=0)
					{
						if(numbers[i][j-k]=='')
						{
							numbers[i][j-k]=numbers[i][j-k+1];
							numbers[i][j-k+1]='';
						}
						k++;
					}
				}
			}
			//合併方塊
			beforeScore=score;
			for(var i=0;i<4;i++)
			{
				for(var j=1;j<4;j++)
				{
					if(numbers[i][j]==numbers[i][j-1]&&numbers[i][j]!='')
					{
						numbers[i][j-1]=parseInt(int(numbers[i][j]))+parseInt(int(numbers[i][j-1]))+'';
						numbers[i][j]='';
					}
				}
			}
			//二次移動
			for(var i=0;i<4;i++)
			{
				for(var j=1;j<4;j++)
				{
					var k=1;
					while(j-k>=0)
					{
						if(numbers[i][j-k]=='')
						{
							numbers[i][j-k]=numbers[i][j-k+1];
							numbers[i][j-k+1]='';
						}
						k++;
					}
				}
			}
			context.clearRect(0,0,canvas.width,canvas.height);
			if(!doubleChange(numDouble,numbers))
				generateNumber();
			drawContainer();
			numbersWrite();
			if(!doubleLose())
				alert('game over');
		}
		else if(e.keyCode==left)//按左鍵
		{
			//第一次移動
			for(var i=1;i<4;i++)
			{
				for(var j=0;j<4;j++)
				{
					var k=1;
					while(i-k>=0)
					{
						if(numbers[i-k][j]=='')
						{
							numbers[i-k][j]=numbers[i-k+1][j];
							numbers[i-k+1][j]='';
						}
						k++;
					}
				}
			}
			//合併方塊
			beforeScore=score;
			for(var i=1;i<4;i++)
			{
				for(var j=0;j<4;j++)
				{
					if(numbers[i][j]==numbers[i-1][j]&&numbers[i][j]!='')
					{
						numbers[i-1][j]=parseInt(int(numbers[i][j]))+parseInt(int(numbers[i-1][j]))+'';
						numbers[i][j]='';
					}
				}
			}
			//第二次移動
			for(var i=1;i<4;i++)
			{
				for(var j=0;j<4;j++)
				{
					var k=1;
					while(i-k>=0)
					{
						if(numbers[i-k][j]=='')
						{
							numbers[i-k][j]=numbers[i-k+1][j];
							numbers[i-k+1][j]='';
						}
						k++;
					}
				}
			}
			context.clearRect(0,0,canvas.width,canvas.height);
			if(!doubleChange(numDouble,numbers))
				generateNumber();
			drawContainer();
			numbersWrite();
			if(!doubleLose())
				alert('game over');
		}
		else if(e.keyCode==down)//按下鍵
		{
			//第一次移動
			for(var i=0;i<4;i++)
			{
				for(var j=2;j>=0;j--)
				{
					var k=1;
					while(j+k<=3)
					{
						if(numbers[i][j+k]=='')
						{
							numbers[i][j+k]=numbers[i][j+k-1];
							numbers[i][j+k-1]='';
						}
						k++;
					}
				}
			}
			//合併方塊
			beforeScore=score;
			for(var i=0;i<4;i++)
			{
				for(var j=2;j>=0;j--)
				{
					if(numbers[i][j]==numbers[i][j+1]&&numbers[i][j]!='')
					{
						numbers[i][j+1]=parseInt(int(numbers[i][j]))+parseInt(int(numbers[i][j+1]))+'';
						numbers[i][j]='';
					}
				}
			}
			//第二次移動
			for(var i=0;i<4;i++)
			{
				for(var j=2;j>=0;j--)
				{
					var k=1;
					while(j+k<=3)
					{
						if(numbers[i][j+k]=='')
						{
							numbers[i][j+k]=numbers[i][j+k-1];
							numbers[i][j+k-1]='';
						}
						k++;
					}
				}
			}
			context.clearRect(0,0,canvas.width,canvas.height);
			if(!doubleChange(numDouble,numbers))
				generateNumber();
			drawContainer();
			numbersWrite();
			if(!doubleLose())
				alert('game over');
		}
		else if(e.keyCode==right)//按下鍵
		{
			//第一次移動
			for(var i=2;i>=0;i--)
			{
				for(var j=0;j<4;j++)
				{
					var k=1;
					while(i+k<=3)
					{
						if(numbers[i+k][j]=='')
						{
							numbers[i+k][j]=numbers[i+k-1][j];
							numbers[i+k-1][j]='';
						}
						k++;
					}
				}
			}
			//合併方塊
			beforeScore=score;
			for(var i=2;i>=0;i--)
			{
				for(var j=0;j<4;j++)
				{
					if(numbers[i][j]==numbers[i+1][j]&&numbers[i][j]!='')
					{
						numbers[i+1][j]=parseInt(int(numbers[i][j]))+parseInt(int(numbers[i+1][j]))+'';
						numbers[i][j]='';
					}
				}
			}
			//第二次移動
			for(var i=2;i>=0;i--)
			{
				for(var j=0;j<4;j++)
				{
					var k=1;
					while(i+k<=3)
					{
						if(numbers[i+k][j]=='')
						{
							numbers[i+k][j]=numbers[i+k-1][j];
							numbers[i+k-1][j]='';
						}
						k++;
					}
				}
			}
			context.clearRect(0,0,canvas.width,canvas.height);
			if(!doubleChange(numDouble,numbers))
				generateNumber();
			drawContainer();
			numbersWrite();
			if(!doubleLose())
				alert('game over');
		}
	}
}

最後將方法放入window.onload()中,大功告成

window.onload=function(){
	drawContainer();
	generateNumber();
	numbersWrite();
    numberMove();
}

這只是最簡單的2048,大家可以在這個的基礎上在數字移動時新增一些動畫效果,還有分數的記錄,希望對大家有所幫助。