JavaScript实现俄罗斯方块

刷完了《JavaScript权威指南》后,我决定用原生js实现一个简单的俄罗斯方块游戏。JavaScript这门语言发展了这么多年,已经有很多优秀的代码库、框架可以帮我们快速实现一些功能,但是抱着学习的态度,还是有必要了解原生的语言特性,而深入了解一门语言最好的办法就是不断的使用它,这个道理不只适用于JavaScript,也同样适用于Python、Java或者自然语言如英语、法语。

设计思路

在web应用中,显示功能依赖于HTML和CSS,前者控制文档的结构,后者控制文档的颜色、位置、边框、布局。JavaScript在客户端浏览器中控制交互、处理事件、 实现动画。因此,我们用HTML和CSS完成显示方块的功能,然后用JavaScript改变文档块的CSS属性实现动画。因为JavaScript是一门面向对象编程的脚本语言,因此可以用类和对象来实现。但JavaScript中的“类”比较特殊,和Java不同,它是通过原型继承的方式来实现。按面向对象的思路,我定义了两个类来实现,一个类控制下落得方块,再需要一个类来控制游戏的运行、场景打印。此外,还需要定义一个全局变量config,用来保存游戏运行配置。必要的时候通过修改配置参数改变游戏运行参数。

  • config对象
    • 保存游戏配置参数,方便修改。
  • Cube类
    • 控制方块下落、移动、旋转
    • 处理和方块有关的事件
  • Game类
    • 绘制游戏区域
    • 控制游戏运行
    • 判断游戏结束
tetris.html

因为重点在JavaScript的实现,因此只写了些基本的样式,并将样式放在html文件的style标签中。以下是代码文件:

<!DOCTYPE html>
<html>
<head>
	<title>俄罗斯方块</title>
	<meta charset="utf-8">
	<style type="text/css">
		.cube{
			/*border:rder: solid 1px #ff0000;*/
			height: 20px;
			width: 20px;
			position: absolute;
			background-color: #CC6633;
			/*padding: 5px;*/
		}
		.over{
			border:solid 5px #FFCC66;
			position: relative;
			top:150px;
			margin: auto;
			height: 30px;
			width:150px;
			display: -webkit-flex;
			display: flex;
			-webkit-align-items:center;
			align-items:center;
			-webkit-justify-content:center;
			justify-content:center;
			background-color:blue;
		}

		body{
			background-color: #C0C0C0;
		}
	</style>
</head>
<body>
	<script type="text/javascript" src="tetris.js"></script>
	<script type="text/javascript">
		window.onload = function(){			
			var game = new Game();
			game.startGame();
		};		
	</script>
</body>
</html>

在script标签中引入了js文件,通过Game对象运行游戏。

tetris.js文件
//全局变量config,保存游戏配置
var config = {
	"box_width":400,      //游戏框宽度,单位px
	"box_height":600,     //游戏框高度,单位px
	"cube_size":20,       //小方块的尺寸,单位px
	"interval":500,       //下落速度;刷新位置时间间隔,单位ms
	"half_cube_size":10,  //半边长度,单位px
	"color":["#FFCC66","#C0C0C0","#FF0000","#FF33CC"],//颜色
	center:{     //投放中心点
		x:210,
		y:50,
	},
	"cubes_site":[ //方块初始坐标
		[{"x":0,"y":0}],//一个方块
		[{"x":0,"y":0},{"x":0,"y":-20}],//两个方块
		[{"x":0,"y":0},{"x":0,"y":-20},
		{"x":0,"y":-40},{"x":0,"y":20}],//四个方块,长条形
		[{"x":0,"y":-40},{"x":0,"y":-20},
		{"x":0,"y":0},{"x":20,"y":0}],//四个方块,L字型
		[{"x":0,"y":-40},{"x":0,"y":-20},
		{"x":0,"y":0},{"x":-20,"y":0}],//四个方块,L字型
		[{"x":0,"y":-20},{"x":-20,"y":0},
		{"x":0,"y":0},{"x":20,"y":0}],//四个方块,品字型
		[{"x":0,"y":-20},{"x":0,"y":0},
		{"x":20,"y":0},{"x":20,"y":20}],//四个方块,Z字型
		[{"x":0,"y":0},{"x":-20,"y":0},
		{"x":0,"y":20},{"x":-20,"y":20}],//四个方块,田字型
	],

};

/*
*定义一个Cube类,用Cube类来实现下落的方块
*/
//cubes_site数组保存每个小方块的坐标,
//game是Game对象
function Cube(cubes_site,game) {
	this.cubes_site = [];//小方块坐标
	for (var i = 0; i < cubes_site.length; i++) {
		var c = {};
		c.x = cubes_site[i].x;
		c.y = cubes_site[i].y;
		this.cubes_site.push(c);
	}
	this.cubes = []; //小方块div对象
	this.center = {};//初始化旋转中心点
	this.center.x = config.center.x;
	this.center.y = config.center.y;
	//console.log(config.center);
	this.box = game.box; //指向box 框
	this.game = game;    //指向游戏类
	this.distance = 0;    //下落距离
	var init_rotate = randomNum(0,3); //初始化旋转
	for (var i = 0; i <init_rotate; i++) {
		this.rotate(1);
	}
	this.draw();
	// this.bindEvent();
}
//定义类原型
Cube.prototype = {
	//下落
	fall:function(){
		this.distance += config["cube_size"];//增加行进距离
		this.center.y += config["cube_size"];//改变中心点坐标
		for (var i = 0; i < this.cubes_site.length; i++) {			
			this.cubes[i].top += config["cube_size"];//改变实时坐标
			this.cubes[i].style.top = this.cubes[i].top+"px";//设置位置属性			
		}
		// console.log(this.distance);
	},
	//左右移动,1:右移,-1:左移
	move:function(direction){
		if(this.moveCollision(direction))
			return;
		var d = config["cube_size"]*direction;
		//移动中心点
		this.center.x += d;
		//移动小方块
		for (var i = 0; i < this.cubes_site.length; i++) {
			this.cubes[i].left += d;
			this.cubes[i].style.left = this.cubes[i].left+"px";
		}
	},

	//移动碰撞
	moveCollision:function(direction){
		for (var i = 0; i < this.cubes.length; i++) {
			//不能移出游戏框,触碰到边界则返回true
			if(this.cubes[i].left == 0 && direction==-1 ){
				return true;
			}else if(this.cubes[i].left == config.box_width-config.cube_size 
				&& direction==1
				){
				return true;
			}else{//检测和落地的方块是否相撞
				var x = this.cubes[i].left + 
					direction*config.cube_size;
				var y = this.cubes[i].top;
				if(this.game.parallel_sites[x].some(function(u){
					if(u==undefined)return false;
					return Math.abs(u.top-y)<20;
				})){
					return true;
				}
			}
				
		}

		return false;
			
	
	},

	// 绕center中心点旋转旋转,direction=1,顺时针旋转九十度,
	// direction=-1逆时针旋转九十度
	rotate:function (direction) {	
		//改变相对于中心点的坐标	
		for (var i = 0; i < this.cubes_site.length; i++) {
			var tmp = 0;
			tmp = this.cubes_site[i]["x"];
			this.cubes_site[i]["x"] =
			this.cubes_site[i]["y"]*direction;
			this.cubes_site[i]["y"] = -tmp*direction;
			
		}
		
	},

	//响应旋转事件
	rotateEvent:function(){
		this.rotate();
		for (var i = 0; i < cubes.length; i++) {
			var site = this.siteConvert(this.cubes_site[i]["x"],
				this.cubes_site[i]["y"]);
			cubes[i].top = site.y;
			cubes[i].left = site.x;
			cubes[i].style.top = site.y+"px";
			cubes[i].style.left = site.x+"px";
		}
	},

	
	//在box中显示方块
	draw:function(){
		for (var i = 0; i < this.cubes_site.length; i++) {
			var div = document.createElement("div");
			//console.log(this.center["x"]);
			var site = this.siteConvert(this.cubes_site[i]["x"],
				this.cubes_site[i]["y"]);
			div.top = site.y;   //相对于box元素的纵坐标
			div.left = site.x;  //相对于box元素的横坐标
			div.style.top = site.y+"px"; //设置style属性,进行定位
			div.style.left = site.x+"px";
			//给小方块添加边框要考虑到边框占据的像素,
			//这里用随机颜色加以区分
			// div.style.backgroundColor = config.color[randomNum(0,3)];
			div.className = "cube";
			this.cubes.push(div);
			this.box.appendChild(div);
		}
	},
	
	//坐标转换,把相对于旋转中心的坐标转换为box中的坐标
	siteConvert:function (x, y) {
		var site = {};
		site.x = x-config.half_cube_size+this.center["x"];
		site.y = y-config.half_cube_size+this.center["y"];

		return site;
	},

};

//定义Game类,控制游戏运行
function Game() {
	this.box_width = config.box_width;
	this.box_height = config.box_height;
	this.parallel_sites = []; // 纵向记录坐标
	this.cross_sites = [];    //横向记录坐标
	//每行最多方块数
	this.cross_cube_num = parseInt(config["box_width"]/config["cube_size"]);
	//初始化
	for (var i = 0; i < config.box_width; i+=config.cube_size) { 
		this.parallel_sites[i] = [];
	}
	//初始化
	for (var i = config.box_height; i >=0; i-=config.cube_size) {
		this.cross_sites[i] = [];
	}
	//最大纵坐标
	this.max_y = config.box_height - config.cube_size;
	//最大横坐标
	this.max_x = config.box_width - config.cube_size;
	//时间间隔
	this.interval = config.interval;
	//绑定事件
	this.bindEvent();
	// console.log(this.parallel_sites);
}
//Game类原型
Game.prototype = {
	//绘制游戏区域
	draw:function(){
		//清空屏幕
		document.body.innerHTML = '';
		//创建游戏框
		var div = document.createElement("div");
		div.id = "#box";
		div.style.width  =  this.box_width+"px";
		div.style.height =  this.box_height+"px";
		div.style.borderStyle = "solid";
		div.style.borderWidth = "10px";
		div.style.borderColor = "#33FF33";
		div.style.position = "fixed";
		div.style.backgroundColor = "#00CCFF";
		div.style.left = (window.innerWidth-this.box_width)/2+"px";
		div.style.top = "30px";
		document.body.appendChild(div);
		
		this.box = div;
				
	},

	//绑定事件
	bindEvent:function(){
		var game = this;
		document.onkeydown = function (event) {
			if(game.game_state == 0){
				return false;
			}
			if(event.keyCode == 39){     //右箭头,右移方块
				game.blocks.move(1);
			}else if(event.keyCode == 37){ //左移方块
				game.blocks.move(-1);
			}else if(event.keyCode == 40){//向下加速下落
				if(game.speed_up == 0){
					game.interval = 50;
					clearTimeout(game.fall);
					game.run();
					game.speed_up = 1;
				}
			}
		};

		document.onkeyup = function(){
			if(game.game_state == 0){
				return false;
			}
			if(event.keyCode==40){//停止加速
				game.interval = config.interval;
				clearTimeout(game.fall);
				game.run();
				game.speed_up = 0;
			}
		};
	},
	//div块向下滑动
	slideCube:function(div, distance){

		// slide();
		div.top += distance;
		div.style.top = div.top+"px";
	},
	
	//开始游戏
	startGame:function(){
			
		//绘制游戏区域	
		this.draw();
		
		//随机生成一组方块,投放方块到box
		var blocks = new Cube(
			config.cubes_site[randomNum(0,config["cubes_site"].length-1)],this);
		//在game对象中保存对blocks的引用
		this.blocks = blocks;
		//定义游戏状态
		this.game_state = 1;
		//加速
		this.speed_up = 0;
		//保存game对象
		var game = this;

		console.log(game.cross_sites);
		console.log(game.parallel_sites);

		var run = function() {			
			//下落是碰撞检测
			if(game.fallCollision(blocks)){
				if(blocks.distance == 0){
					game.game_state = 0;//结束游戏
				}else{ //处理落下的方块并且生成新的方块
					game.addNewBlocks(blocks);
					blocks = new Cube(
						config.cubes_site[randomNum(0,config["cubes_site"].length-1)],game);
					game.blocks = blocks;
				}
			}else{
				blocks.fall(); //go
			}

			if(game.game_state==1){
				//继续下落
				game.fall = setTimeout(run, game.interval);
			}else{
				game.endGame();
			}
		};

		this.run = run;

		this.run();
		
	},

	//“吸收”新的方块
	addNewBlocks:function(blocks){
		var y = [];
		for (var i = 0; i < blocks.cubes.length; i++) {
			this.parallel_sites[blocks.cubes[i].left].push(
				blocks.cubes[i]);
			this.cross_sites[blocks.cubes[i].top].push(
				blocks.cubes[i]);
			if(y.indexOf(blocks.cubes[i].top)<0)
				y.push(blocks.cubes[i].top);
		}
		y = y.sort(function(a,b){
			return b-a;
		});
		// console.log(y);
		var fall = 0;
		for (var i = 0; i < y.length; i++) {			
			y[i] += fall;
			// console.log(y[i]);
			//判断该行是否可消除
			if(this.cross_sites[y[i]].length == this.cross_cube_num){
				//移除该行方块
				for (var j = 0; j < this.cross_cube_num; j++) {
					var cube = this.cross_sites[y[i]][j];
					//移除方块
					this.box.removeChild(cube);
					var index = this.parallel_sites[cube.left].indexOf(
						cube);
					this.parallel_sites[cube.left].splice(index,1);
					// index = this.cross_sites[cube.top].indexOf(
					// 	cube);
					// this.cross_sites[cube.top].splice(index,1);
				}
				this.cross_sites[y[i]] = [];
				var init = y[i] - config.cube_size;
				//让上面的方块下落
				while(this.cross_sites[init].length != 0){
					//下落
					for (var j = 0; j < this.cross_sites[init].length; j++) {						
						this.slideCube(this.cross_sites[init][j],config.cube_size);
					}
					this.cross_sites[init+config.cube_size] = this.cross_sites[init];
					init -= config.cube_size;
				}
				this.cross_sites[init+config.cube_size] = [];
				//改变fall值
				fall += config.cube_size;
			}
		}

		
		
	},


	//碰撞检测,与已存在的方块或底部相撞
	fallCollision: function(blocks) {
		var x,y;
		// var bottom = config.box_height - config.cube_size;
		for (var i = 0; i < blocks.cubes.length; i++) {
			x = blocks.cubes[i].left;
			y = blocks.cubes[i].top;
			if( y == this.max_y){
				return true;
			}
			if(this.parallel_sites[x].some(function(u){
				if(u==undefined)return false;
				return u.top-y==config.cube_size;
			})){
				return true;
			}
			
		}
		return false;
	},

	
	//结束游戏
	endGame:function(){
		// console.log("Game Over");
		//打印一个游戏结束框
		var div = document.createElement("div");
		div.className = "over";
		var node = document.createTextNode("Game Over!");
		div.appendChild(node);

		//给div块绑定事件
		div.onmouseover = function(){
			this.textContent = "Click to restart.";
		};

		div.onmouseout = function(){
			this.textContent = "Game Over!";
		};

		div.onclick = function(){
			var game = new Game();
			game.startGame();
		};

		//添加到游戏框
		this.box.appendChild(div);
	},
};

//生成从minNum到maxNum的随机数
function randomNum(minNum,maxNum){ 
    switch(arguments.length){ 
        case 1: 
            return parseInt(Math.random()*minNum+1,10); 
        break; 
        case 2: 
            return parseInt(Math.random()*(maxNum-minNum+1)+minNum,10); 
        break; 
            default: 
                return 0; 
            break; 
    } 
} 
在浏览器中运行

最后在浏览器中打开tetris.html文件运行游戏:

俄罗斯方块

总结

将复杂的问题进行拆分;
采用面向对象的思想可以让思路更清晰

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值