<canvas>+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,大家可以在这个的基础上在数字移动时添加一些动画效果,还有分数的记录,希望对大家有所帮助。

猜你喜欢

转载自blog.csdn.net/zemprogram/article/details/81266656