JavaScript实现俄罗斯方块游戏过程分析及源码分享


Posted in Javascript onMarch 23, 2015

观摩一下《编程之美》:“程序虽然很难写,却很美妙。要想把程序写好,需要写好一定的基础知识,包括编程语言、数据结构与算法。程序写得好,需要缜密的逻辑思维能力和良好的梳理基础,而且熟悉编程环境和编程工具。”

学了几年的计算机,你有没有爱上编程。话说,没有尝试自己写过一个游戏,算不上热爱编程。

俄罗斯方块曾经造成的轰动与造成的经济价值可以说是游戏史上的一件大事,它看似简单但却变化无穷,令人上瘾。相信大多数同学,曾经为它痴迷得茶不思饭不想。

游戏规则

1、一个用于摆放小型正方形的平面虚拟场地,其标准大小:行宽为10,列高为20,以每个小正方形为单位。

2、一组由4个小型正方形组成的规则图形,英文称为Tetromino,中文通称为方块共有7种,分别以S、Z、L、J、I、O、T这7个字母的形状来命名。

JavaScript实现俄罗斯方块游戏过程分析及源码分享

I:一次最多消除四层

J(左右):最多消除三层,或消除二层

L:最多消除三层,或消除二层

O:消除一至二层

S(左右):最多二层,容易造成孔洞

Z (左右):最多二层,容易造成孔洞

T:最多二层

方块会从区域上方开始缓慢继续落下。玩家可以以90度为单位旋转方块,以格子为单位左右移动方块,让方块加速落下。方块移到区域最下方或是着地到其他方块上无法移动时,就会固定在该处,而新的方块出现在区域上方开始落下。当区域中某一列横向格子全部由方块填满,则该列会消失并成为玩家的得分。同时删除的列数越多,得分指数上升。

分析与解法

每块方块落下的过程中,我们可以做:

1)旋转到合适的方向

2)水平移动到某一列

3)垂直下落到底部

首先,需要用一个二维数组,area[18][10]表示18*10的游戏区域。其中,数组中值为0表示空,1表示有方块。

方块一共7种,每种有4种方向。定义activeBlock[4],在编译之前这个数组的值预定算好,在程序中直接使用。

难点

 1)边界检查。

//检查左边界,尝试着朝左边移动一个,看是否合法。
function checkLeftBorder(){ 
  for(var i=0; i<activeBlock.length; i++){ 
    if(activeBlock[i].y==0){ 
      return false; 
    } 
    if(!isCellValid(activeBlock[i].x, activeBlock[i].y-1)){ 
      return false; 
    } 
  } 
  return true; 
} //同理,需要检测右边界和底边界

 2)旋转, 需要数理逻辑, 一个点相对另外一个点旋转90度的问题。
 3)定时和监听键盘事件机制让游戏自动运行下去。

//开始 
function begin(e){ 
  e.disabled = true; 
  status = 1; 
  tbl = document.getElementById("area"); 
  if(!generateBlock()){ 
    alert("Game over!"); 
    status = 2; 
    return; 
  } 
  paint(); 
  timer = setInterval(moveDown,1000); 
} 
document.onkeydown=keyControl;

程序过程

1)用户点开始->构造一个活动图形, 设置定时器。

//当前活动的方块, 它可以左右下移动, 变型。当它触底后, 将会更新area; 
var activeBlock; 
//生产方块形状, 有7种基本形状。 
function generateBlock(){ 
  activeBlock = null; 
  activeBlock = new Array(4); 
  //随机产生0-6数组,代表7种形态。
  var t = (Math.floor(Math.random()*20)+1)%7; 
  switch(t){ 
    case 0:{ 
      activeBlock[0] = {x:0, y:4}; 
      activeBlock[1] = {x:1, y:4}; 
      activeBlock[2] = {x:0, y:5}; 
      activeBlock[3] = {x:1, y:5}; 

      break; 
    } 
    //省略部分代码..............................
    case 6:{ 
      activeBlock[0] = {x:0, y:5}; 
      activeBlock[1] = {x:1, y:4}; 
      activeBlock[2] = {x:1, y:5}; 
      activeBlock[3] = {x:1, y:6}; 
      break; 
    } 
  } 
  //检查刚生产的四个小方格是否可以放在初始化的位置. 
  for(var i=0; i<4; i++){ 
    if(!isCellValid(activeBlock[i].x, activeBlock[i].y)){ 
        return false; 
      } 
    } 
  return true; 
}

2)每次向下移动后, 都检查是否触底, 如果触底了, 则尝试消行。

//消行 
function deleteLine(){ 
  var lines = 0; 
  for(var i=0; i<18; i++){ 
    var j=0; 
    for(; j<10; j++){ 
      if(area[i][j]==0){ 
        break; 
    } 
  } 
  if(j==10){ 
    lines++; 
    if(i!=0){ 
      for(var k=i-1; k>=0; k--){ 
        area[k+1] = area[k]; 
      } 
    } 
    area[0] = generateBlankLine(); 
    } 
  } 
  return lines; 
}

3)完了之后再构造一个活动图形, 再设置定时器。

效果图

JavaScript实现俄罗斯方块游戏过程分析及源码分享

JavaScript实现俄罗斯方块游戏过程分析及源码分享

JavaScript实现俄罗斯方块游戏过程分析及源码分享

有待优化

1)设置不同形状方块的颜色。

思路:在创建方块函数内,设定activeBlockColor颜色,七种不同形态方块颜色各异(除了修改generateBlock方法之外,还需要修改paintarea方法。因为一开始考虑不周全,消除一行后,重绘方块的同时将颜色统一,因此可以考虑移除表格n行,然后在顶部增添n行,以保证没消除方块的完整性)。

2)当当前方块下落时,可以提前查看下一个方块。

思路:将generateBlock方法拆分成两部分,一部分用于随机尝试下一个方块,一部分用于缓存当前所要描绘的方块。当当前方块碰到底部被固定后,下一方块开始描绘,同时又再次随机产生新方块。如此反复。

完整HTML源码:

<!DOCTYPE>
<html> 
<head> 
<title>Tetris</title> 
<meta charset="UTF-8">
<style> 
*{
	font-family: "微软雅黑";
}
.tetrisContainer{
	width: 230px;
	height: 400px;
	position: relative;
	left: 50%;
	margin-left: -115px;
	top: 40%;
	margin-top: -200px;
}
#area tr td{ 
	width: 20px; 
	height: 20px; 
	border:1px solid #ccc;
} 
</style> 
</head> 
	<body> 
		<div class = "tetrisContainer">
			<input type="button" value="开始游戏" onclick="begin(this);"/> 得分: <span id="score"> 0</span>
			<table id="area" cellspacing="0" cellpadding="0" border="1" style="border-collapse:collapse"><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr></table>
		</div>
	</body> 
	<script type="text/javascript" src="script/tetris.js"></script>
</html>

完整tetris.js源码:

/** 
* JS俄罗斯方块游戏 v 1.0
*/ 
//表示页面中的table, 这个table就是将要显示游戏的主面板 
var tbl; 
//游戏状态 0: 未开始;1 运行; 2 中止; 
var status = 0; 
//定时器, 定时器内将做moveDown操作 
var timer; 
//分数 
var score = 0; 

//area是一个18*10的数组,也和页面的table对应。初始时都为0, 如果被占据则为1 

var area = new Array(18); 
for(var i=0;i<18;i++){ 
	area[i] = new Array(10); 
} 
for(var i=0;i<18;i++){ 
	for(var j=0; j<10; j++){ 
		area[i][j] = 0; 
	} 
} 

//当前活动的方块, 它可以左右下移动, 变型。当它触底后, 将会更新area; 
var activeBlock; 
//生产方块形状, 有7种基本形状。 
function generateBlock(){ 
	activeBlock = null; 
	activeBlock = new Array(4); 
	//随机产生0-6数组,代表7种形态。
	var t = (Math.floor(Math.random()*20)+1)%7; 
	switch(t){ 
		case 0:{ 
			activeBlock[0] = {x:0, y:4}; 
			activeBlock[1] = {x:1, y:4}; 
			activeBlock[2] = {x:0, y:5}; 
			activeBlock[3] = {x:1, y:5}; 

			break; 
		} 
		case 1:{ 
			activeBlock[0] = {x:0, y:3}; 
			activeBlock[1] = {x:0, y:4}; 
			activeBlock[2] = {x:0, y:5}; 
			activeBlock[3] = {x:0, y:6}; 
			break; 
		} 
		case 2:{ 
			activeBlock[0] = {x:0, y:5}; 
			activeBlock[1] = {x:1, y:4}; 
			activeBlock[2] = {x:1, y:5}; 
			activeBlock[3] = {x:2, y:4}; 
			break; 
		} 
		case 3:{ 
			activeBlock[0] = {x:0, y:4}; 
			activeBlock[1] = {x:1, y:4}; 
			activeBlock[2] = {x:1, y:5}; 
			activeBlock[3] = {x:2, y:5}; 
			break; 
		} 
		case 4:{ 
			activeBlock[0] = {x:0, y:4}; 
			activeBlock[1] = {x:1, y:4}; 
			activeBlock[2] = {x:1, y:5}; 
			activeBlock[3] = {x:1, y:6}; 
			break; 
		} 
		case 5:{ 
			activeBlock[0] = {x:0, y:4}; 
			activeBlock[1] = {x:1, y:4}; 
			activeBlock[2] = {x:2, y:4}; 
			activeBlock[3] = {x:2, y:5}; 
			break; 
		} 
		case 6:{ 
			activeBlock[0] = {x:0, y:5}; 
			activeBlock[1] = {x:1, y:4}; 
			activeBlock[2] = {x:1, y:5}; 
			activeBlock[3] = {x:1, y:6}; 
			break; 
		} 
	} 
	//检查刚生产的四个小方格是否可以放在初始化的位置. 
	for(var i=0; i<4; i++){ 
		if(!isCellValid(activeBlock[i].x, activeBlock[i].y)){ 
				return false; 
			} 
		} 
	return true; 
} 
//向下移动 
function moveDown(){ 
	//检查底边界. 
	if(checkBottomBorder()){ 
		//没有触底, 则擦除当前图形, 
		erase(); 
		//更新当前图形坐标 
		for(var i=0; i<4; i++){ 
			activeBlock[i].x = activeBlock[i].x + 1; 
		} 
		//重画当前图形 
		paint(); 
	} 
	//触底, 
	else{ 
		//停止当前的定时器, 也就是停止自动向下移动. 
		clearInterval(timer); 
		//更新area数组. 
		updatearea(); 
		//消行 
		var lines = deleteLine(); 
		//如果有消行, 则 
		if(lines!=0){ 
			//更新分数 
			score = score + lines*10; 
			updateScore(); 
			//擦除整个面板 
			erasearea(); 
			//重绘面板 
			paintarea(); 
		} 
		//产生一个新图形并判断是否可以放在最初的位置. 
		if(!generateBlock()){ 
			alert("Game over!"); 
			status = 2; 
			return; 
		} 
		paint(); 
		//定时器, 每隔一秒执行一次moveDown 
		timer = setInterval(moveDown,1000) 
	} 
} 
//左移动 
function moveLeft(){ 
	if(checkLeftBorder()){ 
		erase(); 
		for(var i=0; i<4; i++){ 
			activeBlock[i].y = activeBlock[i].y - 1; 
		} 
		paint(); 
		} 
	} 
	//右移动 
function moveRight(){ 
	if(checkRightBorder()){ 
		erase(); 
		for(var i=0; i<4; i++){ 
			activeBlock[i].y = activeBlock[i].y + 1; 
		} 
		paint(); 
	} 
} 
//旋转, 因为旋转之后可能会有方格覆盖已有的方格. 
//先用一个tmpBlock,把activeBlock的内容都拷贝到tmpBlock, 
//对tmpBlock尝试旋转, 如果旋转后检测发现没有方格产生冲突,则 
//把旋转后的tmpBlock的值给activeBlock. 
function rotate(){ 
	var tmpBlock = new Array(4); 
	for(var i=0; i<4; i++){ 
		tmpBlock[i] = {x:0, y:0}; 
	} 
	for(var i=0; i<4; i++){ 
		tmpBlock[i].x = activeBlock[i].x; 
		tmpBlock[i].y = activeBlock[i].y; 
	} 
	//先算四个点的中心点,则这四个点围绕中心旋转90度。 
	var cx = Math.round((tmpBlock[0].x + tmpBlock[1].x + tmpBlock[2].x + tmpBlock[3].x)/4); 
	var cy = Math.round((tmpBlock[0].y + tmpBlock[1].y + tmpBlock[2].y + tmpBlock[3].y)/4); 
	//旋转的主要算法. 可以这样分解来理解。 
	//先假设围绕源点旋转。然后再加上中心点的坐标。 

	for(var i=0; i<4; i++){ 
		tmpBlock[i].x = cx+cy-activeBlock[i].y; 
		tmpBlock[i].y = cy-cx+activeBlock[i].x; 
	} 
	//检查旋转后方格是否合法. 
	for(var i=0; i<4; i++){ 
	if(!isCellValid(tmpBlock[i].x,tmpBlock[i].y)){ 
		return; 
	} 
} 
//如果合法, 擦除 
erase(); 
//对activeBlock重新赋值. 
for(var i=0; i<4; i++){ 
	activeBlock[i].x = tmpBlock[i].x; 
	activeBlock[i].y = tmpBlock[i].y; 
} 
//重画. 
paint(); 
} 
//检查左边界,尝试着朝左边移动一个,看是否合法。
function checkLeftBorder(){ 
	for(var i=0; i<activeBlock.length; i++){ 
		if(activeBlock[i].y==0){ 
			return false; 
		} 
		if(!isCellValid(activeBlock[i].x, activeBlock[i].y-1)){ 
			return false; 
		} 
	} 
	return true; 
} 
//检查右边界,尝试着朝右边移动一个,看是否合法。
function checkRightBorder(){ 
	for(var i=0; i<activeBlock.length; i++){ 
		if(activeBlock[i].y==9){ 
			return false; 
		} 
		if(!isCellValid(activeBlock[i].x, activeBlock[i].y+1)){ 
			return false; 
		} 
	} 
	return true; 
} 
//检查底边界,尝试着朝下边移动一个,看是否合法。
function checkBottomBorder(){ 
	for(var i=0; i<activeBlock.length; i++){ 
		if(activeBlock[i].x==17){ 
			return false; 
		} 
		if(!isCellValid(activeBlock[i].x+1, activeBlock[i].y)){ 
			return false; 
		} 
	} 
	return true; 
} 
//检查坐标为(x,y)的是否在area种已经存在, 存在说明这个方格不合法。
function isCellValid(x, y){ 
	if(x>17||x<0||y>9||y<0){ 
		return false; 
	} 
	if(area[x][y]==1){ 
		return false; 
	} 
	return true; 
} 
//擦除 
function erase(){ 
	for(var i=0; i<4; i++){ 
		tbl.rows[activeBlock[i].x].cells[activeBlock[i].y].style.backgroundColor="white"; 
	} 
} 
//绘活动图形 
function paint(){ 
	for(var i=0; i<4; i++){ 
		tbl.rows[activeBlock[i].x].cells[activeBlock[i].y].style.backgroundColor="#CC3333"; 
	} 
} 
//更新area数组 
function updatearea(){ 
	for(var i=0; i<4; i++){ 
		area[activeBlock[i].x][activeBlock[i].y]=1; 
	} 
} 
//消行 
function deleteLine(){ 
	var lines = 0; 
	for(var i=0; i<18; i++){ 
		var j=0; 
		for(; j<10; j++){ 
			if(area[i][j]==0){ 
				break; 
		} 
	} 
	if(j==10){ 
		lines++; 
		if(i!=0){ 
			for(var k=i-1; k>=0; k--){ 
				area[k+1] = area[k]; 
			} 
		} 
		area[0] = generateBlankLine(); 
		} 
	} 
	return lines; 
} 
//擦除整个面板 
function erasearea(){ 
	for(var i=0; i<18; i++){ 
		for(var j=0; j<10; j++){ 
			tbl.rows[i].cells[j].style.backgroundColor = "white"; 
		} 
	} 
} 
//重绘整个面板 
function paintarea(){ 
	for(var i=0;i<18;i++){ 
		for(var j=0; j<10; j++){ 
			if(area[i][j]==1){ 
				tbl.rows[i].cells[j].style.backgroundColor = "#CC3333"; 
			} 
		} 
	} 
} 
//产生一个空白行. 
function generateBlankLine(){ 
	var line = new Array(10); 
	for(var i=0; i<10; i++){ 
		line[i] = 0; 
	} 
	return line; 
} 
//更新分数 
function updateScore(){ 
	document.getElementById("score").innerText=" " + score; 
} 
//键盘控制 
function keyControl(){ 
	if(status!=1){ 
		return; 
	} 
	var code = event.keyCode; 
	switch(code){ 
		case 37:{ 
			moveLeft(); 
			break; 
		} 
		case 38:{ 
			rotate(); 
			break; 
		} 
		case 39:{ 
			moveRight(); 
			break; 
		} 
		case 40:{ 
			moveDown(); 
			break; 
		} 
	} 
} 
//开始 
function begin(e){ 
	e.disabled = true; 
	status = 1; 
	tbl = document.getElementById("area"); 
	if(!generateBlock()){ 
		alert("Game over!"); 
		status = 2; 
		return; 
	} 
	paint(); 
	timer = setInterval(moveDown,1000); 
} 
document.onkeydown=keyControl;
Javascript 相关文章推荐
学习ExtJS border布局
Oct 08 Javascript
JavaScript快速检测浏览器对CSS3特性的支持情况
Sep 26 Javascript
js和jquery对dom节点的操作(创建/追加)
Apr 21 Javascript
window.location.reload()方法刷新页面弹出要再次显示该网页对话框
Apr 24 Javascript
jquery.cookie用法详细解析
Dec 18 Javascript
JavaScript将数字转换成大写中文的方法
Mar 23 Javascript
使用AngularJS编写较为优美的JavaScript代码指南
Jun 19 Javascript
vue下跨域设置的相关介绍
Aug 26 Javascript
在vue中,v-for的索引index在html中的使用方法
Mar 06 Javascript
vue实现移动端触屏拖拽功能
Aug 21 Javascript
解决nuxt页面中mounted、created、watch执行两遍的问题
Nov 05 Javascript
前端JS获取URL参数的4种方法总结
Apr 05 Javascript
JavaScript判断表单提交时哪个radio按钮被选中的方法
Mar 21 #Javascript
JavaScript动态修改网页元素内容的方法
Mar 21 #Javascript
JavaScript实现向OL列表内动态添加LI元素的方法
Mar 21 #Javascript
JavaScript实现当网页加载完成后执行指定函数的方法
Mar 21 #Javascript
JavaScript动态加载样式表的方法
Mar 21 #Javascript
JavaScript获得url所有参数键值表的方法
Mar 21 #Javascript
JavaScript删除数组元素的方法
Mar 20 #Javascript
You might like
smarty+adodb+部分自定义类的php开发模式
2006/12/31 PHP
解析PHP将对象转换成数组的方法(兼容多维数组类型)
2013/06/21 PHP
解析Ubuntu下crontab命令的用法
2013/06/24 PHP
php递归获取目录内文件(包含子目录)封装类分享
2013/12/25 PHP
PHP 微信支付类 demo
2015/11/30 PHP
如何打开php的gd2库
2017/02/09 PHP
php实现用户登陆简单实例
2017/04/04 PHP
php微信公众号开发之秒杀
2018/10/20 PHP
jquery的trigger和triggerHandler的区别示例介绍
2014/04/20 Javascript
javascript控制在光标位置插入文字适合表情的插入
2014/06/09 Javascript
jquery操作checked属性以及disabled属性的多种方法
2014/06/20 Javascript
使用JavaScript开发IE浏览器本地插件实例
2015/02/18 Javascript
Ajax跨域实现代码(后台jsp)
2017/01/21 Javascript
Javascript刷新页面的实例
2017/09/23 Javascript
Grunt针对静态文件的压缩,版本控制打包的实例讲解
2017/09/29 Javascript
jQuery访问浏览器本地存储cookie、localStorage和sessionStorage的基本用法
2017/10/20 jQuery
JS基于for语句编写的九九乘法表示例
2018/01/04 Javascript
浅谈redux, koa, express 中间件实现对比解析
2019/05/23 Javascript
jQuery实现弹出层效果
2019/12/10 jQuery
2019最新21个MySQL高频面试题介绍
2020/02/06 Javascript
Python脚本实现12306火车票查询系统
2016/09/30 Python
5个很好的Python面试题问题答案及分析
2018/01/19 Python
在Python中实现替换字符串中的子串的示例
2018/10/31 Python
如何使用Python标准库进行性能测试
2019/06/25 Python
Django如何使用第三方服务发送电子邮件
2019/08/14 Python
python 利用pywifi模块实现连接网络破解wifi密码实时监控网络
2019/09/16 Python
大专生自我评价
2014/01/28 职场文书
会展策划与管理专业大学生职业生涯规划
2014/02/07 职场文书
主持词开场白
2014/03/17 职场文书
小学生放飞梦想演讲稿
2014/08/26 职场文书
学习三严三实对照检查材料思想汇报
2014/09/22 职场文书
学校捐款活动总结
2015/05/09 职场文书
诚信考试主题班会
2015/08/17 职场文书
运动会广播稿300字
2015/08/19 职场文书
导游词之青城山景区
2019/09/27 职场文书
Golang二维切片初始化的实现
2021/04/08 Golang