Tetris game written in JavaScript (very simple, very detailed)

effect

Write external framework

<!DOCTYPE html>
<html>
	<head lang="en">
		<meta charset="UTF-8">
		<title></title>
		<style>
*{
    margin: 0;
    padding: 0;
}
#box{
	width:320px;
	height:450px;
	position:absolute;
	margin:0 auto;
	left:0;
	top:20px;
	right:0;
	bottom:0;
	background:gray;
	border-radius:10px;
}
#mainDiv{
	width:300px;
	height:400px;
	position:absolute;
	margin:0 auto;
	left:0;
	top:10px;
	right:0;
	bottom:0;
}
.bottom{
	width:600px;
    height:30px;
	position:absolute;
	bottom:1px;
	right:1px;
}
.bottom .button1{
	position: absolute;
    right: 55px;
    width: 50px;
    font-size: 14px;
}
.bottom .button2{
	position: absolute;
    right: 5px;
    width: 50px;
    font-size: 14px;
}
.bottom .span1{
	position: absolute;
    right: 155px;
    color: white;
    font-size: 8px;
}
.bottom .span2{
	position: absolute;
    right: 255px;
    color: white;
    font-size: 8px;
}
</style>
	</head>
	<body>
		<div id='box'>
			<div id='mainDiv'>
			</div>
			<div class='bottom'>
				<span id='score' class='span1'>分数:0</span>
				<span id='time' class='span2'>时间:0</span>
				<button onclick='start()' class='button1'>开始</button>
				<button onclick='stop()' class='button2'>结束</button>
			</div>
		</div>
		
	<script type="text/javascript" src='tetris.js'></script>
	<script type="text/javascript">

	
</script>
	</body>
</html>

effect:

Add an internal canvas, and draw a map

First create the line constructor Line

function Line(ctx,o){
		this.x=0,//x坐标
		this.y=0,//y坐标
		this.startX=0,//开始点x位置
		this.startY=0, //开始点y位置
		this.endX=0,//结束点x位置
		this.endY=0;//结束点y位置
		this.thin=false;//设置变细系数
		this.ctx=ctx;
		
		this.init(o);
	}
	Line.prototype.init=function(o){
		for(var key in o){
			this[key]=o[key];
		}
	}
	Line.prototype.render=function(){
		innerRender(this);
		
		function innerRender(obj){
			var ctx=obj.ctx;
			ctx.save()
			ctx.beginPath();
			ctx.translate(obj.x,obj.y);
			if(obj.thin){
				ctx.translate(0.5,0.5);
			}
			if(obj.lineWidth){//设定线宽
				ctx.lineWidth=obj.lineWidth;
			}
			if(obj.strokeStyle){
				ctx.strokeStyle=obj.strokeStyle;
			}
			//划线
		  	ctx.moveTo(obj.startX, obj.startY);
		  	ctx.lineTo(obj.endX, obj.endY);
		  	ctx.stroke();
		  	ctx.restore();
		}
	  	
	  	return this;
	}

Set parameters, execute drawing and other related methods

function Game(el){
		this.renderArr=[];//待渲染对象存储数组
		this.aliveModel=[];//用来存到底的model组合
		
		this.score=0;//分数
		this.time=0;//时间
		this.moveCount=1;//计时控制器
	}
	
	Game.prototype.init=function(el,score,time){
		if(!el) return ;
		this.el=el;
		this.scoreEL=score;
		this.timeEL=time;
		var canvas = document.createElement('canvas');//创建画布
		canvas.style.cssText="background:darkgrey;border:1px solid grey;";//设置样式
		var W = canvas.width = 300; //设置宽度
		var H = canvas.height = 400;//设置高度
		
		el.appendChild(canvas);//添加到指定的dom对象中
		
		this.ctx = canvas.getContext('2d');
		this.canvas=canvas;
		this.w=W;
		this.h=H;
		
		this.disX=20;//每个格子的x方向大小
		this.disY=20;//每个格子的y方向大小
		this.maxX=15;//x方向格子总数
		this.maxY=20;//y方向格子总数
		
		this.control();//
		this.draw();//绘制
	}
	
	//绘制地图
	Game.prototype.createMap=function(){
		var renderArr = this.renderArr;
		var disX = this.disX;
		var disY = this.disY;
		var maxX=this.maxX;
		var maxY=this.maxY;
		var rectW = this.w;
		var rectH = this.h;
		var rect=null;
		var color;
		
		for(var i=1;i<maxY;i++){//20行
			var line = new Line(this.ctx,{
				x:0,
				y:0,
			 	startX:0,
			 	startY:i*disY,
			 	endX:this.w,
			 	endY:i*disY,
			 	thin:true,
			 	strokeStyle:'white',
			 	lineWidth:0.2
			})
			renderArr.push(line);
		}
		
		for(var i=1;i<maxX;i++){//15列
			var line = new Line(this.ctx,{
				x:0,
				y:0,
			 	startX:i*disX,
			 	startY:0,
			 	endX:i*disX,
			 	endY:this.h,
			 	thin:true,
			 	strokeStyle:'white',
			 	lineWidth:0.2
			})
			renderArr.push(line);
		}
	}
	
	Game.prototype.draw=function(){
		this.createMap();//绘制地图
		
		this.render();//渲染
	}

At this time, the grid and drawing of the game area are as follows:

Let's draw the model again

Model definition: There are 7 types, including one-shape, field-shape, two-shape, two-shape, two-shape, embossed shape, etc.

Definition of deformation: 1 shape can be deformed in 2 types, Tian shape cannot be deformed, and other shapes can be deformed in 4 types.

The composition of the model: The model is composed of 4 small squares, and each model has an array of blocks to access the x and y coordinates of the 4 small squares, and then the model is drawn.

Model deformation: When deforming, you only need to switch the X\Y coordinates of each square to achieve the effect of deformation.

Let's create the constructor of the model

	//模型构造函数
	function Model(o){
		this.blocks=[],//存储方块的数组,绘制的时候根据数组来绘制
		this.type=1,//模型的形状,默认是一字形(共7种)
		this.dir=1,//方向默认为1,总共4种,其中一字形为2种,田字形为1种,其他为4种
		this.x=0,//x坐标(只传入第一个x,根据这个x来生成其他的x)
		this.y=0,//y坐标(只传入第一个y,根据这个y来生成其他的y)
		
		this.init(o);
	}
	//初始化
	Model.prototype.init=function(o){
		for(var key in o){
			this[key]=o[key];
		}
	}

For example

Add a method to create a seven-shaped shape (because there are 4 ways to place the seven-shaped shape, so there is a dir to distinguish it, how to place it)

//创建七字形1
	Model.prototype.createQi1=function(){
		var blocks=this.blocks,x=this.x,y=this.y;
		switch(this.dir){
			case 1://
				blocks.push({x:x,y:y});
				blocks.push({x:x,y:y-1});
				blocks.push({x:x,y:y-2});
				blocks.push({x:x+1,y:y-2});
				break;
			case 2://
				blocks.push({x:x+2,y:y});
				blocks.push({x:x+1,y:y});
				blocks.push({x:x,y:y});
				blocks.push({x:x,y:y-1});
				break;	
			case 3://
				blocks.push({x:x+1,y:y-2});
				blocks.push({x:x+1,y:y-1});
				blocks.push({x:x+1,y:y});
				blocks.push({x:x,y:y});
				break;	
			case 4://
				blocks.push({x:x-2,y:y-1});
				blocks.push({x:x-1,y:y-1});
				blocks.push({x:x,y:y-1});
				blocks.push({x:x,y:y});
				break;	
		}
	}

Try to create a seven-character shape (the incoming x and y are the first place to put it)

	var model = new Model({//创建1字
			x:6,y:6,fillStyle:'#0370BD',fill:true,game:this,type:5,dir:1
		});
		this.renderArr.push(model);
		//当前的模型
		this.currentModel=model;

Write the deformation method (each deformation is rotated 90 degrees counterclockwise according to the previous appearance, and modify each small square x, y to modify it)

   ------------------------ >>> 

How to change the figure on the left to change to the figure on the right?

   -------------------->>>   

It's easy to understand by marking the number, 1 corresponds to 1, 2 or corresponds to 2, and so on, except that X\Y has changed.

1 square: As long as x+2, you can move to the specified position;

2 squares: both x and y need to be increased by 1

3 squares: y+2 is fine

4 squares: x-1 and y+1 are enough

The others are the same, let’s write about the method of deformation

//七1变形
	Model.prototype.transformQi1=function(){
		var blocks = this.blocks,block2=blocks[1];
		switch(this.dir){
			case 1://竖着的
				tran1();
				this.dir=2;
				break;
			case 2://横着的
				tran2();
				this.dir=3;
				break;	
			case 3://竖着的
				tran3();
				this.dir=4;
				break;
			case 4://横着的
				tran4();
				this.dir=1;
				break;			
		}

		function tran1(){//变成横着的
			for(var i=0;i<blocks.length;i++){
				block=blocks[i];
				if(i==0){
					block.x+=2;
				}else if(i==1){
					block.x+=1;
					block.y+=1;
				}else if(i==2){
					block.y+=2;
				}else if(i==3){
					block.x-=1;
					block.y+=1
				}
			}
		}
		
		function tran2(){//竖着的
			for(var i=0;i<blocks.length;i++){
				block=blocks[i];
				if(i==0){
					block.x-=1;
					block.y-=2;
				}else if(i==1){
					block.y-=1;
				}else if(i==2){
					block.x+=1;
				}else if(i==3){
					block.y+=1
				}
			}
		}
		
		function tran3(){//变成横着的
			for(var i=0;i<blocks.length;i++){
				block=blocks[i];
				if(i==3){
					block.x+=2;
				}else if(i==2){
					block.x+=1;
					block.y-=1;
				}else if(i==1){
					
				}else if(i==0){
					block.x-=1;
					block.y+=1
				}
			}
		}
		
		function tran4(){//竖着的
			for(var i=0;i<blocks.length;i++){
				block=blocks[i];
				if(i==3){
					block.x-=1;
					block.y-=2;
				}else if(i==2){
					block.x-=2;
					block.y-=1;
				}else if(i==1){
					block.x-=1;
				}else if(i==0){
					block.y+=1;
				}
			}
		}
	}

Add w and up keys as deformation events, and also add events for left movement, right movement, and downward acceleration

//按键的控制
	Game.prototype.control=function(){
		var that=this;
		global.addEventListener('keydown',function(e){
			//if(!that.timmer) return ;
			switch (e.keyCode){
				case 87://w
				case 38://上
					that.currentModel.transform();//变形
					break;
				case 83://s
				case 40://下
					that.currentModel.move('d');//移动
					break;
				case 65://a
				case 37://左
					that.currentModel.move('l');//移动
					break;
				case 68://d
				case 39://右
					that.currentModel.move('r');//移动
					break;
			
			}
			//测试用,记得删除
			that.render();
		});
	}

Next, try deforming

 

Add mobile method

//移动
	Model.prototype.move=function(dir){
		var cur = this.game.currentModel,dis=1,blocks = this.blocks;
		if(dir=='r'||dir=='ld'){
			dis=1
		}else if(dir=='l'){
			dis=-1;
		}else if(dir=='d'){
			dis=3;
		}
		
		var stopMoveObj = this.stopMove(dir,dis),
			val=stopMoveObj.val,resDis=stopMoveObj.resDis;
		if(val) {
			if(dir=='d'||dir=='ld'){//到底了
				[].push.apply(this.game.aliveModel,cur.blocks);//放到已到底的组合中
				this.game.renderArr.pop();//当前模型弹出
				this.game.clearBlock();//消除
				this.game.createModel();//绘制一个新图形
			}
			
			return ;//如果返回true 则不能再往这个方向移动
		}

		if(resDis>0){
			dis=resDis;
		}
		//更新每一个block的位置
		for(var i=0;i<blocks.length;i++){
			block=blocks[i];
			if(dir=='d'||dir=='ld'){
				block.y+=dis;
			}else{
				block.x+=dis;
			}
		}
	}

Add boundary judgment and collision detection (the boundary detection is relatively simple, and the collision detection should be handled carefully when fast forward, see the code)

//停止移动
	Model.prototype.stopMove=function(dir,dis){
		var cur = this.game.currentModel,blocks = this.blocks;
		
		var maxX = this.game.maxX,maxY = this.game.maxY,res,temp;
		for(var i=0;i<blocks.length;i++){
			block=blocks[i];
			if(dir=='d'||dir=='ld'){
				if(block.y>=maxY-1){//到底了
					return {val:true};
				}
			}else if(dir=='r'){
				if(block.x>=maxX-1){//到右边界了
					return {val:true};
				}
			}else if(dir=='l'){
				if(block.x<=0){//到左边界了
					return {val:true};
				}
			}
			//碰撞检测
			temp=this.collide(block,dis,dir);
			if(temp.val){
				return temp;
			}
			if(!res || res.resDis==0 || (temp.resDis!=0 && temp.resDis<res.resDis)){
				res=temp;
			}
		}
		
		return res;
	}
	//检查当前模型是否与其他存底的模型相触碰
	Model.prototype.collide=function(block,dis,dir){
		var aliveModel = this.game.aliveModel,item;
		var res={},val=false,resDis=0,maxY = this.game.maxY;
		
		if(dir=='r'){//向右判断
			for(var i=0;i<aliveModel.length;i++){
				item = aliveModel[i];
				if(!item) continue;
				if(item.y==block.y && item.x==block.x+1){
					val= true;
					break;
				}
			}
		}else if(dir=='l'){//向左判断
			for(var i=0;i<aliveModel.length;i++){
				item = aliveModel[i];
				if(!item) continue;
				if(item.y==block.y && item.x==block.x-1){
					val= true;
					break;
				}
			}
		}else {//向下判断
			if(aliveModel.length>0){
				for(var i=0;i<aliveModel.length;i++){
					item = aliveModel[i];
					if(!item) continue;
					if(item.x==block.x){//下方有存在的方块
						if(item.y==block.y+1){
							val= true;
							break;
						}else if(item.y<=block.y+Math.abs(dis)){
							var temp=item.y-block.y-1;
							if(resDis==0 || temp<resDis){
								resDis = temp;
							}
						}
					}else{//下发不存在方块
						if(maxY<=block.y+Math.abs(dis)){
							var temp=maxY-block.y-1;
							if(resDis==0 || temp<resDis){
								resDis = temp;
							}
						}
					} 
				}
			}else{//第一个模型
				if(maxY<=block.y+Math.abs(dis)){
					var temp=maxY-block.y-1;
					if(resDis==0 || temp<resDis){
						resDis = temp;
					}
				}
			}
		}
		res.resDis=resDis;
		res.val=val;
		return res;
	}

 

Treatment after the end

1. Give the aliveModel array of the current game object, access the 4 small squares corresponding to the current model (it will be drawn when the render method is executed) 

2. This model needs to be deleted in the renderArr array

3. Every time you bottom out, you need to add an elimination judgment, and it will be eliminated if the conditions are met

4. Draw a new model

Draw the bottomed model block

//绘制存底的图形
	Game.prototype.aliveModelRender=function(){
		var context=this.ctx;
		var disX=this.disX,disY=this.disY;
		context.save();
		context.beginPath();
		_.each(this.aliveModel,function(item){
			if(item){
				context.rect(item.x*disX+1,item.y*disY+1,disX-1,disY-1);
				//context.fillStyle='';
				context.fill();
			}
		});
		context.restore();
	}

Eliminate lines, points, and drops

//消除行
	Game.prototype.clearBlock=function(){
		var maxX=this.maxX,aliveModel=this.aliveModel;
		//将y相同的放在一起
		var rowArr=[],rowObj={};
		_.each(aliveModel,function(item,index){
			if(item) {
				if(!rowObj[item.y]){
					rowObj[item.y]=[];
				}
				rowObj[item.y].push(index);
			}
		});
		
		var that=this;
		var keys = Object.keys(rowObj),row,num=0;
		_.each(keys,function(k){
			row = rowObj[k];
			if(row.length>=maxX){//消除这行
				_.each(row,function(r){
					aliveModel.splice(r,1,undefined);//先用undefined代替
				})
				
				num++;//行数计数器
				that.down(k,1);//清楚当前行
			}
		})
		
		//完成消除
		for(var i=0;i<aliveModel.length;i++){
			if(!aliveModel[i]) {
				console.log(123)
				aliveModel.splice(i,1);
			}
		}
		
		var score = 0;
		switch (num){
			case 1:
				score=100;//1行100分
				break;
			case 2:
				score=300;//2行300分
				break;
			case 3:
				score=600;//3行600分
				break;
			case 4:
				score=1000;//4行1000分
				break;
		}
		//积分
		this.calcuScore(score);
	}
	
	//消除后的下降
	Game.prototype.down=function(y,num){
		var aliveModel=this.aliveModel;
		_.each(aliveModel,function(item){
			if(item && item.y<y){
				item.y+=num;
			}
		});
	}

Automatically move down, update, display time, score

//显示分数
	Game.prototype.calcuScore=function(s){
		this.score+=s;
		this.scoreEL.innerText='分数:'+this.score;
	}
	//显示时间
	Game.prototype.calcuTime=function(){
		if(this.moveCount%4==0){
			this.time++;
			this.time_flag=false;
			this.timeEL.innerText='时间:'+this.time;
		}
		
		this.moveCount++;
	}
	//向下移动
	Game.prototype.move=function(dir){
		var curModel= this.currentModel;
		this.calcuTime();
		
		var endFlag = this.end();
		if(endFlag) {
			this.stop();
			this.hasEnd=true;
			return ;
		} 
		
		this.update();
		this.render();
	}
	//更新
	Game.prototype.update=function(){
		this.currentModel.move('ld');
	}

Add events to the start and end buttons

	var mainDiv = document.getElementById('mainDiv');
	var score= document.getElementById('score');
	var time= document.getElementById('time');
	
	game.init(mainDiv,score,time);
	
	function start(){
		game.start()
	}
	
	function stop(){
		game.stop()
	}
Game.prototype.start=function(){
		if(this.timmer) return ;
		if(this.hasEnd){//如果是结束则需要重新开始,暂停的话就继续游戏
			this.restart();
		}
		this.hasEnd=false;
		this.timmer = setInterval(this.move.bind(this),250);//开始定时任务
	}
	
	//重新开始
	Game.prototype.restart=function(){
		this.renderArr=[];//待渲染对象存储数组
		this.aliveModel=[];//用来存到底的model组合
		
		this.score=0;//分数
		this.time=0;//时间
		this.moveCount=1;//计时控制器
		
		this.clearCanvas();
		this.draw();
	}
	//停止任务	
	Game.prototype.stop=function(){
		if(!this.timmer) return ;
		clearInterval(this.timmer);//清除定时任务
		this.timmer=null;
	}
	//结束
	Game.prototype.end=function(){
		var aliveModel = this.aliveModel;
		for(var i=0;i<aliveModel.length;i++){
			item = aliveModel[i];
			if(item&&item.y==0){
				alert('结束了')
				return true;
			}
		}
		return false
	}

 

Source code download, no points required

Give it to three companies, brothers!

Guess you like

Origin blog.csdn.net/dkm123456/article/details/114103563