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

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

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

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

游戏规则

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

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

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)完了之后再构造一个活动图形, 再设置定时器。

效果图

有待优化

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; 
(0)

相关推荐

  • JS 俄罗斯方块完美注释版代码

    R #board tr td{ width: 20px; height: 20px; } /** * JS俄罗斯方块完美注释版 v 1.01 * 从学c语言那一会儿都想写一个俄罗斯方块,可是每次动起手总觉得难度太大. * 今天终于用了大约4个小时写出来了. 其中在涉及到方块变型的时候还咨询了 * 同学来帮忙; * * 个人觉得难点有这么几个: * 1: 边界检查, 不多说, 想通了就行 * 2: 旋转, 还是数学上的方法, 一个点相对另外一个点旋转90度的问题. * 4: 让整个程序在点开始之后

  • javascript实现俄罗斯方块游戏的思路和方法

    观摩一下<编程之美>:"程序虽然很难写,却很美妙.要想把程序写好,需要写好一定的基础知识,包括编程语言.数据结构与算法.程序写得好,需要缜密的逻辑思维能力和良好的梳理基础,而且熟悉编程环境和编程工具." 学了几年的计算机,你有没有爱上编程.话说,没有尝试自己写过一个游戏,算不上热爱编程. 俄罗斯方块曾经造成的轰动与造成的经济价值可以说是游戏史上的一件大事,它看似简单但却变化无穷,令人上瘾.相信大多数同学,曾经为它痴迷得茶不思饭不想. 游戏规则 1.一个用于摆放小型正方形的平

  • JS+Canvas实现的俄罗斯方块游戏完整实例

    本文实例讲述了JS+Canvas实现的俄罗斯方块游戏.分享给大家供大家参考,具体如下: 试玩(没有考虑兼容低版本浏览器): ********************************************************************** 9月3日更新: 修复了隐藏的比较深的BUG 加上暂停.再来一次功能 速度随分数增高而递减 添加log日志 ****************************************************************

  • JS和canvas实现俄罗斯方块

    效果图: 代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <p style="font-size: 20px;">键盘箭头A,D控制左右移动,S控制快速下滑.W控制变形,空格控制暂停,enter开始游戏</p> <

  • js实现俄罗斯方块小游戏分享

    复制代码 代码如下: <!doctype html><html><head><style type="text/css">body { background:#000; font:25px/25px 宋体;}#box { float:left;width:252px;border:#999 20px ridge;color:#9f9;text-shadow:2px 3px 1px #0f0; }#info { float:left;col

  • 60行js代码实现俄罗斯方块

    这是我之前网上看到的,很牛逼的一位大神写的,一直膜拜中 <!doctype html><html><head></head><body> <div id="box" style="width:252px;font:25px/25px 宋体;background:#000;color:#9f9;border:#999 20px ridge;text-shadow:2px 3px 1px #0f0;"&g

  • 原生JavaScript编写俄罗斯方块

    首先这里感谢@jdkleo  提出的宝贵建议! 说实在的吧,我这个俄罗斯方块大家玩起来别骂我就万岁了,还没完全完成的,只完成了50%,而且还有很多BUG. 可以实现的功能: 1.掉方块 2.随机生成新方块 3.方块移动. 目前BUG还很多,由于是第一次写这么"大"的游戏,有1000多行代码,所以还请高人指点,BUG太多了. 按START开始游戏. 大家提提建议,我第一次写JS游戏. 参考了一下网上其他人的代码,但是不是照抄. 代码可以直接运行,不用引用JQUERY. 希望大神们能给点建

  • Javascript编写俄罗斯方块思路及实例

    俄罗斯方块这个游戏也做了移动端的兼容, 这个游戏难点是怎么翻转方块, 自己实现的方式是把方块放到一个二维数组, 然后逆时针旋转二维数组. 也有别的方法,比如直接用一个全局变量代表一个方向, 翻转的时候根据这个变量转动方块, 但是代码要写更多. 在文库搜索到了一篇关于算法的文章, ....看着好心塞: 游戏截图PC端: 游戏截图移动端: 模板引擎用了HandlebarsJS,  为了更好的模块化,也用了requireJS....没用好: 运行下面代码 var cfg = { width:14, h

  • JAVASCRIPT代码编写俄罗斯方块网页版

    俄罗斯方块方块是小时候的一个回忆,从最开始的掌上的黑白游戏机,到电视游戏机,到电脑,无不有它的痕迹,今天我们来一起重温它的一种实现方法,也算是整理一下我的思路吧...... HTML+CSS+JS实现俄罗斯方块完整版,素材只有图片,想要的下载图片按提示名字保存,css中用的时候注意路径!!主要在JS中!JS附有详细注释 效果: 按键提示:[键盘按键] 素材:图片名字与代码里对应 1.背景图片:tetris.png 2.失败时候的弹出框图片:game-over.png 3.七种色彩小方块图片:  

  • JS俄罗斯方块,包含完整的设计理念

    下面是俄罗斯方块的截图: 请到这里下载源码: 俄罗斯方块 在线演示 下载地址下面是代码: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml&q

随机推荐