用纯JS做俄罗斯方块 - 简要思路介绍(1)
大家都知道俄罗斯方块是一款大众化的游戏了,我很小的时候就玩过,今年已经25岁了,可以说俄罗斯方块确实是历史悠久,做俄罗斯方块是我上个星期开始的想法。也许是由于自己从来没有写过这种东西吧,所以有生疏。代码的话,只完成了一小部分,大概1/5左右吧。今天还是决定先写一部分思路。
至于俄罗斯方块的话,有很多的难点,如果有JS去写的话,要考虑到碰撞啊,边界啊,下落等问题,本文这些问题大部分不会考虑到,只是提供一部分思路而已,开始已经说了,因为自己还没写完这个游戏,但是又出于想写博客记录,所以才有了这一系列的博客。
回到正题,我们首先想一想,俄罗斯方块需要什么?我做一个简单的归纳。如果我简单点说的话,就是一个俄罗斯方块对象,那么这个对象里又有些什么东西呢?我们可以想象一下,有一个平面直角坐标系,这个平面直角坐标系有X轴,有Y轴,也有“每一等分”的距离(unit),而俄罗斯方块就是一个一个的“格子”,这些格子从某一个地方开始下落,某一个地方停止下落,于是我们就规定了俄罗斯方块的“下落区域”(Area)。但是下落是一个“动作”,所以我们还要有一个类(这里定义为operate),来控制动作的下落。
好了,先就介绍到这里,我们再来做一段代码性质的归纳,表示对上面的代码做一段归纳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /*俄罗斯方块实体类*/ function Tetris() { var self = this ; //自身 this .area= null ; //区域 this .operate= null ; //操作 /*初始化X,Y,单元为5或者20*/ this .x=5; this .y=5; this .unit=20; this .running= null ; //是否在运行中 //俄罗斯方块实体ID this .id= "tempid" ; //开始游戏 this .start= function () { this .area= new getArea( this .x, this .y, this .unit, "tempid" ); //获得Area对象 ,其中TEMPID是俄罗斯方块实体类ID this .operate= new OperateTetris( this .area,self); //是否替换俄罗斯方块 if ( this .operate.mayPlace()) { //alert(1); this .operate.place(); } } //开始游戏 document.getElementById( "startGame" ).onclick= function (){self.start()}; } |
那么,当我们点击StartGame的时候,开始游戏,即运行start()方法。好,我们现在开始考虑Area对象里面到底需要什么东西 function getArea(x,y,unit,id)参数需要带入4个,前面3个刚才已经说了,第四个参数就是Area的ID。我们需要area这个对象,所以通过HTML代码来设置ID。大家玩过俄罗斯方块的都知道,每一次触底,都会新加一个元素,而新的元素是“随机”的,每当一行是满的(这里不考虑颜色不同的情况),就会消掉一行,当然我们一次形成了多行可消掉的方块的时候,那么我们就可以消掉多行。下面的是代码,算是对上面的文字的一个小小的总结,还没有完成的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | //获得区域的横坐标和纵坐标 function getArea(x,y,unit,id) { this .x=x; this .y=y; this .unit=unit; //每个单元的大小,单位为像素 this .el=document.getElementById(id); //得到ID对象 this .board=[]; //面板,即在区域范围内的元素(俄罗斯方块) //添加元素 this .addElement= function () { //得到起始元素的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) { board[yBegin][xBegin]=el; //确定元素的位置 } } //消掉所有的行 this .removeFullLines= function () { var lines=0; for ( var i= this .y-1;y>0;y--) { if ( this .linesRelated(y)) { lines++; this .y++; } } } //和线性有关的东西(判断是否满了) this .linesRelated= function (y) { for ( var x= this .x;x>0;x--) { this .removeLines(y); if ( this .board[y][x]){ return 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--) { /*今天暂时写到这里*/ } }; } |
需要注意的一点是,俄罗斯方块是“二维性质”的,所以我这里定义了一个board类型的二维数组,即board[行][列](board[y][x]).好了,这里我们当然还需要一个类,这个类就是控制元素下落的“动作”的类,那么这个下落“动作“的类里应该有一些什么东西呢?我们需要考虑边界,于是有了(区域),我们要考虑俄罗斯方块于是有了俄罗斯方块对象(tetris),因为方块的种类不同,有各种不同的形状于是我们必须考虑方块的类别(types),还有下一个类别(NEXTTYPE),因为方块有下一个提示;我们需要考虑方块在AREA中的位置于是有了(position),我们需要判断游戏是否暂停于是有了running,当然了,方块下落的速度SPEED肯定也是要考虑到的,如果GAME OVER了那么就要判断游戏是否停止stopped,当然了,方块是一个一个的元素于是我们要考虑elements,当然了,最重要的还是下落(falldown).下面是定义的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 .running= null ; //是否在运行中 this .stopped= null ; //是否停止 this .fallDownId= null ; //往下掉落的 this .speed= null ; //速度 |
这么一说有点头晕,我们选一个切入点吧,我们的切入点就是如何构造方块。大家应该知道俄罗斯方块的几种形状吧,比如T形,L形,口形等等,那么我们可以想象一下,把俄罗斯方块定义成一个二维数组,然后有元素的地方为1,没元素的地方为0来构造形状,如下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | /*方块的组合方式,用数组进行组合(二维数组) 用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 ] ]; |
好了,形状构造好之后,我们当然需要考虑程序的性能方面的问题,于是我创建了如下的GETTER方法,来判断是游戏是否在运行中等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | /*一连串的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; } |
当然了,我们如果要”重新开始游戏“,肯定是要建立一个方法reset(),说白一点,就是恢复游戏开始的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 | //重置(初始化) this .reset= function () { this .nextType=random( this .blockComplex.length); this .types= this .nextType; this .position=0; this .board=[]; this .elements=[]; this .x= null ; this .y= null ; } |
如果这个俄罗斯方法触底的话,那么肯定是会触发下一个俄罗斯方块的开始于是我们这里肯定要有一个方法, 内容我还没想好,就给一个架子吧。我直接返回TRUE了。
1 2 3 4 5 | this .mayPlace= function () { return true ; } |
下面的是最重要的方法,就是我们的替换方块的方法。先来简单做一个介绍,我也不知道自己能不能讲好,大家想想在一个坐标系中,方块如果下落了,肯定是Y--,毕竟方块是向下方下落的,当然,我们还需要有线条,假设我们一直在堆方块的话,这个线肯定是会增加的,还有我们的方块本身就是DIV,肯定是一个掉落DIV的过程,而这些DIV,肯定是在AREA范围内的。我们不妨想一想,第一步,我们来创建一个空的BOARD,就是面板,然后往这个面板里面填充东西呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //创建空对象,即所有的都为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; } |
我们如果想下落元素的话,肯定是要知道开始下落的坐标,当然Y轴肯定是0,X轴可以依据自己的喜好来设定。当然了,下落的DIV肯定是属于这个AREA下面的子元素的,所以我们等下肯定要把这个APPENDCHILD到这里面去。下面是代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | /*替换*/ 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= this .board.length-1;yAxis>=0;yAxis--) { for ( var xAxis=0;xAxis<= this .blockComplex[yAxis].length;xAxis++) { if ( this .blockComplex[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++; } } |
需要注意的是,当下一个俄罗斯方块(随机)的形成是随机的,所以我们需要定义一个RANDOM方法。其实每次下落都是一个RESET的循环,只是游戏还没有结束而已。
1 2 3 4 5 | //随机数,产生1~6的 function random(i) { return Math.floor(Math.random()*i); } |
好了,今天只介绍一个思路,当然了,我也没写出来这个游戏,等下一篇出来的时候应该游戏会有一个大的架子了,还有一些代码我都不好意思放出来了,写得太差了。其实这个俄罗斯方块不完全是我自己写的,我也参考了下别人的东西,但不是抄袭,我想通过自己的努力,做一个游戏出来,这是我多年的梦想,努力!
至于全部的代码我就不贴了,因为还没写完,只是对这几天写代码的一个总结而已,高手可以无视我写的代码。
__EOF__
作 者:ღKawaii
出 处:https://www.cnblogs.com/kmsfan/p/4163060.html
关于博主:一个普通的小码农,为了梦想奋斗
版权声明:署名 - 非商业性使用 - 禁止演绎,协议普通文本 | 协议法律文本。
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!

出处:http://www.cnblogs.com/kmsfan
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
欢迎大家加入KMSFan之家,以及访问我的优酷空间!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?