原生JavaScript编写俄罗斯方块
首先这里感谢@jdkleo 提出的宝贵建议!
说实在的吧,我这个俄罗斯方块大家玩起来别骂我就万岁了,还没完全完成的,只完成了50%,而且还有很多BUG。
可以实现的功能:
1.掉方块
2.随机生成新方块
3.方块移动。
目前BUG还很多,由于是第一次写这么“大”的游戏,有1000多行代码,所以还请高人指点,BUG太多了。
按START开始游戏。
大家提提建议,我第一次写JS游戏。
参考了一下网上其他人的代码,但是不是照抄。
代码可以直接运行,不用引用JQUERY。
希望大神们能给点建议!!!!不甚感激!!!
Ver 0.2版本已经出来了哦(2014-12-26),最新的代码:
此次修正的东西:
1.左边右边可以移动。
2.可以旋转
3.可以消除满行的方块。
代码:
<!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"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>无标题文档</title> <style type="text/css"> #test { /*width:25px;*/ } .t { width:10px; height:10px; border:1px solid black; float:left; } body { margin:0 auto; width:1000px; height:600px; } /*游戏相关*/ #startGame { } #lines { } #level { } #time { } /*俄罗斯方块实体类*/ #tetris-area { width:auto; height:auto; background:blue; } /*JS生成的CLASS,俄罗斯方块实体*/ #tetris .block0,#tetris .block1, #tetris .block2, #tetris .block3,#tetris .block4,#tetris .block5,#tetris .block6 { z-index:1000; font-size:10px; line-height:1em; position:absolute; width:13px; height:13px; border:0.5px solid red; background:#000; } </style> <script src="jquery.js"></script> <script type="text/javascript"> //2维数组,用来存放俄罗斯方块的坐标 var xYAxis=[]; xYAxis.push([1,2],[3,4]); //alert(xYAxis[1][0]); //复制节点 /*$(document).ready(function(e) { for(i=0;i<2;i++) { if(i==1) { // $("#test").append("<br>"); //加上换行符 } $(".t").clone().appendTo("#test"); } //动态得到test(container)的宽度,包含两边的边框BORDER $("#test").width(($(".t").width()+2)*2+1); }); */ //获得区域的横坐标和纵坐标 function getArea(x,y,unit,id) { this.x=x; this.y=y; this.unit=unit; //每个单元的大小,单位为像素 this.el=document.getElementById(id); //得到ID对象 this.board=[]; //面板,即在区域范围内的元素(俄罗斯方块) /*创建2D范围矩阵*/ for(var y=0;y<this.y;y++) { this.board.push(new Array()); for(var x=0;x<this.x;x++) { this.board[y].push(0); } } /*从2D矩阵中消除元素*/ this.destroy=function() { for(var y=0;y<this.board.length;y++) { for(var x=0;x<this.board[y].length;x++) { if(this.board[y][x]) { this.el.removeChild(this.board[y][x]); this.board[y][x]=0; } } } } //添加元素 this.addElement=function(el) { //得到起始元素的X开始坐标和Y开始坐标的位置(错误) //得到X坐标的下落次数,和Y轴的左右移动的次数 var xBegin=parseInt(el.offsetLeft/unit); var yBegin=parseInt(el.offsetTop/unit); if(xBegin>=0&&xBegin<=this.x&&yBegin>=0&&yBegin<=this.y) { this.board[yBegin][xBegin]=el; //确定元素的位置 } } //消掉所有的行 this.removeFullLines=function() { var lines=0; for(var i=this.y-1;i>0;i--) { if(this.linesRelated(i)) { this.removeLines(i); lines++; y++; } } return lines; //返回线条 } //和线性有关的东西(判断是否满了) this.linesRelated=function(y) { for(var x=0;x<this.x;x++) { if(!this.board[y][x]){return false;} //如果不为0的话,那么菜返回FALSE } return true; }; //去掉行 this.removeLines=function(y) { for(var x=0;x<this.x;x++) { this.el.removeChild(this.board[y][x]); this.board[y][x]=0; } y--; for(;y>0;y--) { /*今天暂时写到这里*/ /*继续于2014-12-21*/ for(var x=0;x<this.x;x++) { if(this.board[y][x]) { var el=this.board[y][x]; el.style.top=el.offsetTop+this.unit+"px"; this.board[y+1][x]=el; this.board[y][x]=0; } } } }; //活动区域 this.getBlock=function(y,x) { if(y<0){return 0;} if(y<this.y&&x<this.x) { return this.board[y][x]; } else { throw "Area get failed!"; } } } /*俄罗斯方块实体类*/ function Tetris() { var self =this; //自身 var operate=null; this.area=null; this.operate=null; //操作 this.status=new State(); //新建状态 /*初始化X,Y,单元为5或者20*/ this.x=20; this.y=20; this.unit=20; this.running=null; //是否在运行中 //俄罗斯方块实体ID this.id="tempid"; /*开始的时候暂停是FALSE的*/ this.paused=false; //开始游戏 this.start=function() { self.reset(); //重新开始游戏 self.status.start(); this.area=new getArea(this.x,this.y,this.unit,"tetris-area"); //获得Area对象 ,其中TEMPID是俄罗斯方块实体类ID this.operate=new OperateTetris(this.area,self); //是否可以替换 if(this.operate.mayPlace()) { //alert(1); this.operate.place(); } else { self.gameOver(); } } //游戏结束 this.gameOver=function() { self.status.stopGame(); //停止游戏 self.operate.stopGame(); //操作类停止游戏 } /*重置游戏*/ this.reset=function() { if(this.operate) { self.operate.destroy(); //重新开始 self.operate=null; } if(self.area) { self.area.destroy(); self.area=null; } //隐藏游戏结束 document.getElementById("game_over").style.display="none"; document.getElementById("next_operate").style.display="block"; //下一个操作 document.getElementById("keys_Press").style.display="block"; //显示按键 self.status.reset(); self.paused=false; document.getElementById("tetris-pause").style.display="block"; //暂停按钮 } /*暂停游戏*/ this.pause=function() { if(self.operate==null) { return ; } if(self.paused) { self.operate.running=true; /*这里还没写完2014-12-22*/ } else { } } //上 this.up=function() { if(self.operate&&self.operate.isRunning()&&!self.operate.isStopped()) { if(self.operate.mayRotate()) { self.operate.rotate(); self.status.setActions(self.status.getActions()+1); } } } //下 this.down=function() { if(self.operate&&self.operate.isRunning()&&!self.operate.isStopped()) { if(self.operate.mayMoveDown()) { self.operate.moveDown(); self.status.setActions(self.status.getActions()+1); } } } //左 this.left=function() { if(self.operate&&self.operate.isRunning()&&!self.operate.isStopped()) { if(self.operate.mayMoveLeft()) { self.operate.moveLeft(); self.status.setActions(self.status.getActions()+1); } } } //右 this.right=function() { if(self.operate&&self.operate.isRunning()&&!self.operate.isStopped()) { if(self.operate.mayMoveRight()) { self.operate.moveRight(); self.status.setActions(self.status.getActions()+1); } } } //开始游戏 document.getElementById("startGame").onclick=function(){self.start()}; //} //Tetris是一个整体类,里面包含了很多方法。 var keyboard=new Keyboard(); //创建键盘类实体 keyboard.set(keyboard.n,this.start); keyboard.set(keyboard.up,this.up); keyboard.set(keyboard.down,this.down); keyboard.set(keyboard.left,this.left); keyboard.set(keyboard.right,this.right); document.onkeydown=keyboard.event; //按下按键按钮的事件 /*键盘操作方法*/ function Keyboard() { this.up=38; //上 this.down=40; //下 this.left=37; //左 this.right=39; //右 this.n=78; this.p=80; this.r=82; this.space=32; //空格 this.f12=123; this.escape=27; //退格键 this.keys=[]; //键位集合 this.funcs=[]; var self=this; //设置键位 this.set=function(key,func) { this.keys.push(key); this.funcs.push(func); } this.event=function(e) { if(!e){e=window.event;} for(var i=0;i<self.keys.length;i++) { if(e.keyCode==self.keys[i]) { self.funcs[i](); } } } } //具体的操作类 function OperateTetris(area,tetris) { var self=this; //当前对象 this.area=area; this.tetris=tetris; this.types=null; //方块的类型; this.nextType=null; //下一个类型 //初始化X和Y this.x=null; this.y=null; this.position=0; //初始位置 this.board=[]; //用来填充HTML元素的 this.elements=[]; this.nextElements=[]; //下一个元素 this.running=null; //是否在运行中 this.stopped=null; //是否停止 this.fallDownId=null; //往下掉落的 this.speed=null; //速度 /*方块的组合方式,用数组进行组合(二维数组) 用0,1表示是否有方块存在,如果是0:不存在,1:存在, 以下的逻辑就可以非常的清楚了。*/ this.blockComplex=[ [ [0,0,1],[1,1,1],[0,0,0] //_| ], [ [1,0,0],[1,1,1],[0,0,0] //L ], [ [0,1,0],[1,1,1],[0,0,0] //T ], [ [0,0,0],[1,1,1],[0,0,0] //-- ], [ [0,0,0],[0,1,1],[0,1,1] //口 ], [ [0,1,1],[0,1,0],[1,1,0] //Z ] ]; this.stopGame=function() { this.running=false; } /*一连串的GETTER方法 分别是速度,X,Y轴,运行和停止的GETTER方法*/ this.getSpeed=function() { return this.speed; } this.getX=function() { return this.x; } this.getY=function() { return this.y; } this.isRunning=function() { return this.running; } this.isStopped=function() { return this.stopped; } //重置(初始化) this.reset=function() { if(this.fallDownId) { clearTimeout(this.fallDownId); //下落的时候去掉时间间隔 } this.types=this.nextType; this.nextType=random(this.blockComplex.length); this.position=0; this.board=[]; this.elements=[]; this.x=null; this.y=null; this.speed=200; //速度暂定为51 this.running=false; this.stopped=false; //移除下一个元素 for(var i=0;i<this.nextElements.length;i++) { document.getElementById("next_operate").removeChild(this.nextElements[i]); } this.nextElements=[]; //下一个元素 } //下一个类型,随机抽取 this.nextType=random(this.blockComplex.length); //重置 this.reset(); /*判断是否替换*/ this.mayPlace=function() { var isOperate=this.blockComplex[this.types]; /*area开始的坐标原点*/ var areaStartX=parseInt(this.area.x-isOperate[0].length); var areaStartY=1; var lineFound=false; var lines=0; for(var y=isOperate.length-1;y>=0;y--) { for(var x=0;x<isOperate[y].length;x++) { if(isOperate[y][x]) { lineFound=true; if(this.area.getBlock(areaStartY,areaStartX+x)) {return false;} } } if(lineFound) { lines++; } if(areaStartY-lines<0) { break; } } return true; } /*替换*/ this.place=function() { //初始化 var operate=this.blockComplex[this.types]; //区域开始X轴的位置 var AreaXStartPos=parseInt(this.area.x-operate[0].length); //区域开始Y轴的位置 //var AreaYStartPos=parseInt(this.area.y-operate[0]); var AreaYStartPos=1; //因为X轴的位置可能变化,而Y轴总是从最上面下来的,所以是1 this.x=AreaXStartPos; //把新的位置赋给X; this.y=AreaYStartPos; //把新的位置赋给y; //构建空对象,并存入BOARD /*y:行,x:列*/ //alert(operate[0].length+" "+operate.length); this.board=this.createEmpty(operate[0].length,operate.length); /*线条,往下掉落,初始化*/ var lines=0; var foundLines=false; //循环遍历,先遍历行,每一行再来遍历列 for(var yAxis=operate.length-1;yAxis>=0;yAxis--) { for(var xAxis=0;xAxis<=operate[yAxis].length;xAxis++) { if(operate[yAxis][xAxis]) { var el=document.createElement("div"); el.className="block"+this.types; //确定这个元素的CLASSNAME //确定左边距和上边距 el.style.left=(this.x+xAxis)*this.area.unit+"px"; el.style.top=(this.y+yAxis)*this.area.unit+"px"; this.area.el.appendChild(el); //这个EL去APPEND主要的EL。 this.board[yAxis][xAxis]=el; this.elements.push(el); //推入elements中 } } /*个人感觉这个功能应该是加速往下掉落的方法?不明觉厉*/ if(lines) { yAxis--; } if(foundLines) { lines++; } } this.running=true; this.fallDownId=setTimeout(this.fallDown,this.speed); //间隔时间,掉落下的 var nextOperate=this.blockComplex[this.nextType]; for(var y=0;y<nextOperate.length;y++) { for(var x=0;x<nextOperate[y].length;x++) { //创建元素 if(nextOperate[y][x]) { /*先写到这里:2014-12-22*/ var el=document.createElement("div"); el.className="block"+this.nextType; el.style.left=(x*this.area.unit)+"px"; el.style.top=(y*this.area.unit)+"px"; document.getElementById("next_operate").appendChild(el); this.nextElements.push(el); //下一个元素 } } } } //创建空对象,即所有的都为0的对象,并返回对象 this.createEmpty=function(x,y) { var elements=[]; for(var y2=0;y2<y;y2++) { elements.push(new Array()); for(var x2=0;x2<x;x2++) { elements[y2].push(0); } } return elements; } //下落(这是一个最关键的函数,决定了这个游戏的成败) this.fallDown=function() { if(self.isRunning()) { if(self.mayMoveDown()) { self.moveDown(); self.fallDownId=setTimeout(self.fallDown,self.speed); //下落的间隔时间 } else { for(var i=0;i<self.elements.length;i++) { self.area.addElement(self.elements[i]); } var lines=self.area.removeFullLines(); if(lines) { /*这里到时候再写*/ self.tetris.status.setLines(self.tetris.status.getLines()+lines); } self.reset(); if(self.mayPlace()) { self.place(); } else { self.tetris.gameOver(); } } } else { } } //是否可以旋转俄罗斯方块 this.mayRotate=function() { for(var y=0;y<this.board.length;y++) { for(var x=0;x<this.board[y].length;x++) { if(this.board[y][x]) { /新的X,Y的值/ var newY=this.getY()+this.board.length-1-x; var newX=this.getX()+y; if(newY>this.area.y){return false;} if(newX<0){return false;} if(newX>=this.area.x){return false;} if(this.area.getBlock(newY,newX)){return false;} //获得区域 } } } return true; } //旋转俄罗斯方块 this.rotate=function() { var puzzle=this.createEmpty(this.board.length,this.board[0].length); //创建一个空的矩阵 for(var y=0;y<this.board.length;y++) { for(var x=0;x<this.board[y].length;x++) { //旋转,X轴和Y轴的坐标互换 if(this.board[y][x]) { var newY=puzzle.length-1-x; var newX=y; var el=this.board[y][x]; //旋转前的对象 var moveY=newY-y; var moveX=newX-x; //长度是offsetTop或left加上偏移量 el.style.left=el.offsetLeft+(moveX*this.area.unit)+"px"; el.style.top=el.offsetTop+(moveY*this.area.unit)+"px"; puzzle[newY][newX]=el; } } } this.board=puzzle; } //下落方法(进行判断) this.mayMoveDown=function() { for(var y=0;y<this.board.length;y++) { for(var x=0;x<this.board[y].length;x++) { if(this.board[y][x]) { if(this.getY()+y+1>=this.area.y){this.stopGame=true;return false;} //如果触底,那么就停止游戏 if(this.area.getBlock(this.getY()+y+1,this.getX()+x)){this.stopGame=true;return false;} } } } return true; } //下落 this.moveDown=function() { //用一个循环去控制下落 for(var i=0;i<this.elements.length;i++) { this.elements[i].style.top=this.elements[i].offsetTop+this.area.unit+"px"; } this.y++; } this.mayMoveLeft=function() { /*可以向左移动(判断方法)*/ for(var y=0;y<this.board.length;y++) { for(var x=0;x<this.board[y].length;x++) { if(this.board[y][x]) { if(this.getX()-1+x<0) { return false; } if(this.area.getBlock(this.getY()+y,this.getX()+x-1)){return false;} } } } return true; } //向左移动 this.moveLeft=function() { /*向左移动(判断方法)*/ for(var i=0;i<this.elements.length;i++) { this.elements[i].style.left=this.elements[i].offsetLeft-this.area.unit+"px"; } this.x--; } /*是否可以向右移动*/ this.mayMoveRight=function() { for(var y=0;y<this.board.length;y++) { for(var x=0;x<this.board[y].length;x++) { if(this.board[y][x]) { if(this.getX()+1+x>=this.area.x){return false;} if(this.area.getBlock(this.getY()+y,this.getX()+x+1)){return false;} } } } return true; } /*向右移动*/ this.moveRight=function() { for(var i=0;i<this.elements.length;i++) { this.elements[i].style.left=this.elements[i].offsetLeft+this.area.unit+"px"; } this.x++; } /*摧毁方法*/ this.destroy=function() { for(var i=0;i<this.elements.length;i++) { this.area.el.removeChild(this.elements[i]); } this.elements=[]; this.board=[]; this.reset(); } } /*俄罗斯方块状态*/ function State() { /*初始化*/ this.level; this.time; this.score; this.opeate; this.lines; this.apm; //不明觉厉 this.actions; //动作 this.el= { "lines":document.getElementById("lines"), "level":document.getElementById("level"), "time":document.getElementById("time"), "apm":document.getElementById("apm"), "operate":document.getElementById("operate") } this.timeId=null; var self=this; //开始游戏 this.start=function() { this.reset(); //重置 this.timeId=setInterval(this.incTime,1500); } /*停止游戏*/ this.stopGame=function() { if(this.timeId) { clearInterval(this.timeId); } } //重置 this.reset=function() { this.stopGame(); this.level=1; this.time=0; this.score=0 this.opeate=0; this.lines=0; /*以后可能加INNERHTML*/ this.el.level=this.level; this.el.time=this.time; this.el.score=this.score; this.el.operate=this.opeate; this.el.lines=this.lines; } //和SetInterval有关 this.incTime=function() { self.time++; self.el.time.innerHTML=self.time; //设置时间 self.actions=parseInt(self.actions/self.time)*60; this.el.apm.innerHTML=self.apm; } /*设置分数*/ this.setScore=function(i) { this.score==i; this.el.score.innerHTML=this.score; } /*设置等级*/ this.setLevel=function(i) { this.level=i; this.el.level.innerHTML=this.level; //设置内部HTML }; this.getLevel=function() { return this.level; } //线条的SETTER方法 this.setLines=function(i) { this.lines=i; this.el.lines.innerHTML=this.lines; } this.getLines=function() { return this.lines; } //设置动作 this.setActions=function(i) { this.actions=i; //this.el.actions.innerHTML=this.actions; } this.getActions=function() { return this.actions; } //设置apm this.setApm=function(i) { this.apm=i; this.el.apm.innerHTML=this.apm; } this.getApm=function() { return this.apm; } //控制下落操作的类 this.setOperate=function(i) { this.opeate=i; this.el.operate=this.operate; } this.getOperate=function() { return this.opeate; } } //随机数,产生1~6的 function random(i) { return Math.floor(Math.random()*i); } } /*Tetris是一个整体类,里面包含了很多方法*/ </script> </head> <body> <div id="tetris"> <!--正方形 --> <div id="test"> <div class="t"></div> </div> <!--长方形--> <div id="rect"> <div class="r"> </div> </div> <!-- 俄罗斯方块实体类--> <div id="tetris-area"> </div> <!-- 下一个操作--> <div id="next_operate"></div> <!--游戏结束--> <div id="game_over">Game Over</div> <!-- 按键 --> <div id="keys_Press"> </div> <input type="button" id="startGame" value="Start" /> <!--线条 --> <div id="lines"></div> <!--等级 --> <div id="level"></div> <!--apm(不知道有什么作用) --> <div id="apm"></div> <!--时间 --> <div id="time"></div> <!--控制下落操作 --> <div id="operate"></div> <!-- 功能键(暂停)--> <div id="tetris-pause"> </div> <!-- 功能键(继续)--> <div id="tetris-resume" style="display:none"> </div> </div> <script> var tx=new Tetris(); tx.x=12; tx.y=22; tx.unit=14; //tx.start(); //开始游戏 </script> </body> </html>
演示图:
项目git地址:
http://git.oschina.net/KMSFan/Tetris_Yang
项目演示地址:http://runjs.cn/detail/ggo07ery
以上所述就是本文的全部内容了,希望能够对大家学习javascript有所帮助。
相关文章
JavaScript之appendChild、insertBefore和insertAfter使用说明
这几天需要用到对HTML节点元素的删/插操作,由于用到insertBefore方法的时候遇到了一些麻烦,现在作为知识的整理,分别对appendChild、insertBefore和insertAfter做个总结2010-12-12javascript中setTimeout和setInterval的unref()和ref()用法示例
本文通过一个小例子想大家讲解了setTimeout和setInterval的unref()和ref()用法和使用环境,代码很简洁,有需要的小伙伴自己参考下吧。2014-11-11ionic中的$ionicPlatform.ready事件中的通用设置
$ionicPlatform.ready事件是用于检测当前的平台是否就绪的事件,相当于基于document的deviceready事件, 在app中一些通用关于设备的设置必须在这个事件中处理2017-06-06一步一步的了解webpack4的splitChunk插件(小结)
这篇文章主要介绍了一步一步的了解webpack4的splitChunk插件(小结),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2018-09-09
最新评论