bom实现方块移动_从0开始实现一个俄罗斯方块

a780c4276c77909f15b1e699f9975239.png

写在前面得话:

这篇文章主要记录了我是怎么一步一步写出俄罗斯方块,整个代码用的函数编程,主要是为了让一些不熟悉es6, 面向对象写法得 新手能更容易看明白,全部得代码中都是一些js的基础知识,很容易理解。要说有点麻烦的,那就是游戏过程中的各种检测。但是只要你多思考,你就能理解代码为什么要那样写,你也可以实现这个游戏。(当然也许你有更好的实现方法)。

预览地址:

俄罗斯方块​blog.cwlserver.top

1,先理清游戏逻辑

  • 游戏场景:场景大小为 10*18,
  • 下落时间:初始方块每隔1秒,会下落一格。随着游戏进行时间得增加,方块下落时间间隔会缩短。
  • 操作方法:方向键得 上下左右 分别控制方块得, 变形,加速下落,左移,右移。
  • 方块类型:一共7种类型得方块。每次随机出现一种, 每种方块由数个 1*1大小得小方块组成
  • 方块下落:当方块落到底, 或者下一格已经被占,方块停止下落,然后会有一个新的方块出现
  • 方块左右移动:方块左右移动时,如果左,右是墙或者是已经被占,方块将不能移动。
  • 方块变形:方块逆时针旋转90°,变形时需要判断方块是否可以变形。
  • 游戏会有下一个方块得提示
  • 消行:当一行被填满时,这一行将被消除
  • 计分规则: 消1行得2分,2行4分,3行8分,4行16分
  • 游戏结束: 当方块下落到底,并且方块超出游戏场景时,判定游戏结束

2,分步实现游戏中得功能

html结构

    <div id="box"><canvas id="canvas" width="300" height="540"></canvas><div class="scorebox"><p>游戏已进行: <span id="game-time">00:00:00</span></p><br><p>当前得分: <span id="score">0</span></p><br><p>下一个方块:</p><br><canvas id="next" width="120" height="120"></canvas><br><p class="btns"><button id="pause">暂停</button><button id="restart">重新开始</button></p></div></div>

构建场景

因为场景大小是10x18,所以我决定用一个 10x18得二维数组来模拟场景,这样方便和方块做碰撞检测。

//定义列数
var ROW = 10;
//定义行数
var COL = 18;
//游戏得分
var SCORE = 0;
//游戏场景
var area = new Array(COL);
for(var i=0; i<area.length; i++){area[i] = new Array(ROW).fill(0);
}
/*
最终得到得area是这样得
area = [[0,0,0,0....][0,0,0,0....][0,0,0,0....]...
]
*/

构建小方块

小方块我同样使用二维数组来构建

//定义各种方块得数组, 一共7种不同得方块,数组中的1,2,3,4..这些数字主要是为了每个方块设置不同的颜色
var data = {'o':[[1, 1],[1, 1]],'s':[[2, 0, 0],[2, 2, 0],[0, 2, 0]],'5':[[0, 0, 3],[0, 3, 3],[0, 3, 0]],            'l':[[4, 0, 0],[4, 0, 0],[4, 4, 0]],'t':[[5, 5, 5],[0, 5, 0],[0, 0, 0]],'j':[[0, 0, 6],[0, 0, 6],[0, 6, 6]],'|':[[0, 7, 0, 0],[0, 7, 0, 0],[0, 7, 0, 0],[0, 7, 0, 0]]
};
//定义方块得颜色,每个数字对应一种颜色
var aColor = ['', '#fff', '#0000FF', '#00FF00', '#CC00FF', '#CCFFFF','#FFFF33','#99FFFF'];
//将data中得key放到一个字符串中 方便随机调用
var sKey = 'os5ltj|';
//定义当前方块, 当前方块默认null;
var cur = null;
//因为游戏中会有下一个方块得提示, 所以这里要提前声明一下
var next = null;
//定义一个生成方块得函数
function createBox(){//首先创建提示方块if(!next){//从skey中随机取出一个键名var rnd = Math.floor(Math.random()*sKey.length);//根据key取得方块数组var box = data[sKey[rnd]];//每一个方块都有,x, y, box 这三个属性next = {//方块初始在场景中间位置,方块左移 x--, 右移 x++;x: Math.floor((ROW-box[0].length)/2), //方块在垂直方向得位置,刚好在场景外, y++ 方块下落y: -box[0].length,//方块得数组box: box};}//当前方块不存在时, 创建当前方块if(!cur){//直接下一个方块变成当前这个cur = next;//然后再重新生成下一个next = {x: Math.floor((ROW-box[0].length)/2),y: -box[0].length,box: data[sKey[Math.floor(Math.random()*sKey.length)]]}}
}

现在想一个问题,有了场景和方块的数据之后,如何把他们联系起来?

我的处理方式是这样的,在方块下落的过程中,方块和场景是分开的,方块的位置和场景是分开刷新的。在下落的过程中我会 检测方块和场景是否发生碰撞,如果发生了碰撞,将当前方块的数组合并到场景的数组中,使方块变成场景的一部分,同时生成一个新的方块。看下代码如何实现

//将当前方块合并到场景
function mergeBoxArea(){//循环当前方块for(var i=0; i<cur.box.length; i++){//这里的判断是为了当方块的一部分在场景外的时候,将那一部分跳过,只计算在场景中的部分if(i+cur.y>=0){for(var j=0; j<cur.box[i].length){//将当前方块数组中不为0的项,和 场景中当前位置为0的项合并if(cur.box[i][j] !== 0 && area[i+cur.y][j+cur.x] == 0){//合并的结果, 将场景中当前位置的值设置为方块对应位置的值area[i+cur.y][j+cur.x] = cur.box[i][j];}}}}//将方块合并入场景的同时要尝试 消行var arr = isRemove(area);if(arr.length !== 0){for(var i=0; i<arr.length; i++){area.splice(arr[i], 1)area.unshift(new Array(ROW).fill(0))}//更新得分SCORE+=Math.pow(2, arr.length)scoreEle.innerHTML = SCORE;};
}
//碰撞检测
//垂直方向的碰撞检测, 需要接受当前方块做为参数,
//作用:检测方块下落一格之后和场景的碰撞情况,如果会碰撞返回true,否则返回false;
function collide(cur){var box = cur.box;var len = box.length;var x = box.x;//因为是检测下一个位置,所以要+1;var y = box.y + 1;for(var i=0; i<len; i++){//做碰撞检测同样需要将场景外的方块部分排除掉if(i+y>=0){//方块的数组都是n*n的所以都用lenfor(var j=0; j<len; j++){//将方块为0的项不检测if(box[i][j] !== 0){//第一种碰撞情况:当i+y大于等于场景的高度时,说明方块出界//第二种碰撞情况:方块没有出界,但是场景中的这个位置,被占用了if(i+y>=area.length || (i+y<area.length && area[i+y][j+x] !== 0)){//碰撞了返回 truereturn true;}}}}}//代码执行到这里时说明没有碰撞,返回false;return false;
}
//水平方向的移动限制
//当用键盘控制方块左右移动的时候,需要检测左右是否是墙,或者方块,这里检测的也是下一个位置的碰撞情况
//如果没有墙或者方块(不碰撞),返回true
//如果碰撞, 返回 false;
//接受参数: 当前方块:cur, 移动方向: dir -1|0|1
function bMove(cur, dir){//当前位置加上方向 就是 下一个位置var x = cur.x+dir;for(var i=0; i<cur.box.length; i++){for(var j=0; j<cur.box[i].length; j++){if(cur[i][j] !== 0){//这里发生碰撞的情况有3中//1.方块在左边出界了, 这时 j+x<0//2.方块在右边出界了, j+x>= ROW//3.方块没有出界,但是场景中的这个位置被占用 area[i+cur.y][j+x]!==0// 加上 i+cur.y>=0 && j+x>=0 && area[i+cur.y] 是为了防止报错if(j+x<0 || j+x==ROW || ( i+cur.y>=0 && j+x>=0 && area[i+cur.y] && area[i+cur.y][j+x]!==0)){return false;}}}}return true;
}

如何处理方块旋转?

方块的旋转比较容易处理,就把二维数组旋转一下就可以了。但是要注意方块旋转的时候也是需要检测 旋转的合理性, 可以想象一下,一个长条下落的过程中,如果他的左右两边都是方块,这种情况肯定是不能旋转的(其它方块同理)。还有一种情况就是,方块靠墙下落的时候,旋转一下之后,有一部分转到墙里面去了,这种也是不合理的,但是玩游戏的时候,这种情况也能旋转,所以出现这种情况的时候,我们需要修正一下方块的位置。 下面看代码怎么写

//此函数用于检测方块是否能够旋转
/*
参数: 当前方块 cur
返回值: true        //方块可以直接旋转false        //方块不能旋转,即使是在尝试修正位置之后,就是上面说到的左右都是方块的情况cur.x        //当返回 一个数值的时候,说明 将方块水平移动到这个位置后,可以旋转, 即上面说的修正位置
*/
function bRotate(cur){//在这里复制一个旋转后的方块出来,用于检测var _cur = {x: cur.x, y:cur.y, box: rotateBox(cur.box)};//检测方块旋转之后,水平和垂直方向的碰撞情况, 如果在任意方向会发生碰撞if( collide(_cur) === true || bMove(_cur, 0) === false ){//尝试水平移动方块,移动方向是分别向左,向右移动2格for(var i=0; i<2; i++){//方块靠近左边的时候,尝试向右移动,并且检测移动的合理性if(_cur.x<4 && bMove(_cur, 1)){_cur.x++;}//靠近右侧的时候,向左移动,并且检测移动的合理性if(_cur.x>6 && bMove(_cur, -1)){_cur.x--;}//移动之后再检测是否碰撞, 如果不会发生碰撞, 返回移动后的位置if(collide(_cur) === false && bMove(_cur, 0)){return _cur.x;}}//代码执行到这里的时候说明,移动了之后仍会碰撞return false;//如果旋转之后不会发生碰撞,直接返回true;}else{return true;}
}
//旋转数组的函数
function rotateBox(arr){var res = [];for(var i=0; i<arr.length; i++){res.push([]);}//旋转for(var i=0; i<arr.length; i++){for(var j=0; j<arr[i].length; j++){res[arr.length-1-y][x] = arr[x][y];}}return res;
}

现在开始处理游戏的刷新, 计算游戏的时间

游戏用 requestAnimationFrame 更新

var timer = null;
//记录一个旧的时间,这里用于辅助计算, 每次刷新的间隔时间
var oldTime = Date.now();
//n 用于累加 raf 的间隔时间
var n = 0;
//游戏运行时间 单位 毫秒
var gameTime = 0;
//方块下落的间隔时间
var step = 1000;
//游戏是否暂停
var bPause = false;
//获取dom元素
//主场景canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//提示下一个方块 canvas
var nextCanvas = document.getElementById('next');
var nextctx = nextCanvas.getContext('2d');
//游戏得分
var scoreEle = document.getElementById('score');
//暂停按钮
var pauseEle = document.getElementById('pause');
//重新开始按钮
var restartEle = document.getElementById('restart')
//显示游戏时间
var gameTimeEle = document.getElementById('game-time');
//开启主循环
timer = requestAnimationFrame(animate);
//主循环函数
function animate(){//累加 raf 的间隔时间n+=Date.now()-oldTime;//累加游戏运行时间gameTime+=Date.now()-oldTime;oldTime = Date.now();//方块要开始下落了if(n>=step){n = 0;//每秒钟更新一次游戏时间updateGameTime()//根据游戏进行时间提高游戏难度changeDifficulty();//方块下落之前要先检测是否会发生碰撞//会发生碰撞if(collide(cur)){//会碰撞,并且此时,如果方块有一部分在外面,说明游戏结束if(cur.y<0){gameover()//正常的碰撞}else{//将方块合并入游戏场景mergeBoxArea()//并将cur 设置为nullcur = null;//产生一个新的方块createBox()}//不会碰撞}else{cur.y++;}}//更新游戏场景drawArea();//画提示方块drawNextBox();    timer = requestAnimationFrame(animate);
}
//更新游戏场景
function drawArea(){ctx.clearRect(0, 0, 300, 540)ctx.save()ctx.scale(30, 30)//ctx.fillStyle = '#fff';//画游戏场景drawcube(ctx, area)    //画当前方块drawcube(ctx, cur.box, cur.x, cur.y)                ctx.restore();
} 
//更新提示
function drawNextBox(){nextctx.clearRect(0, 0, 120, 120)nextctx.save()nextctx.scale(30, 30)//画下一个方块next&&drawcube(nextctx, next.box)    nextctx.restore();        
}
//画方块,接受一个ctx对象,一个数组, 数组的偏移值
function drawcube(ctx, arr, x, y){x = x || 0;y = y || 0;for(var i=0; i<arr.length; i++){for(var j=0; j<arr[i].length; j++){if(arr[i][j] !== 0){//设置方块的颜色ctx.fillStyle = aColor[arr[i][j]];ctx.fillRect(j+x, i+y, 1, 1)}}}    
}

监听键盘事件,移动方块

//监听键盘事件
document.addEventListener('keydown', function(ev){if(bPause || !cur){return false;}var keycode = ev.keyCode;switch(keycode){//左case 37://是否能向左移动if(bMove(cur, -1)){cur.x--;}break;//右case 39://是否能向右移动if(bMove(cur, 1)){cur.x++;}break;//下case 40://如果触底或者落到其它方块上面if(collide(cur)){if(cur.y<0){gameover()}else{mergeBoxArea()cur = null;createBox()}}else{cur.y++;}break;//上case 38://是否能旋转 当n为true时可以直接旋转,当n为数值时需要将方块x位置移动到此处才能旋转var rotateRes = bRotate(cur);//可以直接旋转if(rotateRes === true){cur.box = rotateBox(cur.box);//不能旋转}else if(rotateRes === false){console.log('不能旋转')//需要移动之后才能旋转}else{cur.x = rotateRes;cur.box = rotateBox(cur.box);}break;}
})

处理游戏结束, 游戏暂停, 游戏重新开始, 消行, 更新游戏得分, 更新游戏运行时间等等

//点击暂停按钮
pauseEle.addEventListener('click', function(){var html = this.innerHTML;if(html === '暂停'){pause();this.innerHTML = '继续';}else{start();this.innerHTML = '暂停';}
})
//点击重新开始
restartEle.addEventListener('click', function(){restart();
})
//暂停游戏
function pause(){cancelAnimationFrame(timer);bPause = true;
}
//继续
function start(){timer = requestAnimationFrame(animate);bPause = false;
}
//重新开始
function restart(){//重置场景for(var i=0; i<area.length; i++){for(var j=0; j<area[i].length; j++){area[i][j] = 0;}}cancelAnimationFrame(timer);timer = requestAnimationFrame(animate);bPause = false;pauseEle.innerHTML = '暂停';//重置游戏时间gameTime = 0;//更新游戏时间updateGameTime();cur = null;//创建第一个方块createBox();
}
//游戏结束
function gameover(){    cancelAnimationFrame(timer);alert('游戏结束, 您一共获得:'+SCORE+"分")restart()
}
//检测是否可以消行,并将可以消除得行 加入结果数组返回出去
function isRemove(area){var arr = [];for(var i=0; i<area.length; i++){var remove = true;//如果数组的一行的每一项都不为0说明可以消除for(var j=0; j<area[i].length; j++){if(area[i][j] == 0){remove = false;}}//储存消除行的索引if(remove){arr.push(i)}}return arr;
}
//更新游戏运行时间
function updateGameTime(){var n = gameTime/1000;var h = Math.floor(n/(60*60));n%=60*60;var m = Math.floor(n/60);n%=60;var s = Math.floor(n);h = h<9?'0'+h:''+h;m = m<9?'0'+m:''+m;s = s<9?'0'+s:''+s;gameTimeEle.innerHTML = h+':'+m+':'+s;
}
//根据游戏时间修改难度(方块下落间隔时间)
function changeDifficulty(){//游戏进行5分钟 方块下落间隔为300msif(gameTime>=1000*60*5){step = 300;//游戏运行3分钟 方块下落间隔500ms}else if(gameTime>=1000*60*3){step = 500;//游戏运行2分钟 方块下落间隔700ms}else if(gameTime>=1000*60*2){step = 700;}
}

到这里整个游戏差不多就算完了,最后 发一个预览地址, 最终的预览demo和现在的代码并某些细节不是完全一样

俄罗斯方块​blog.cwlserver.top

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/355141.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cli3解决 ie11语法错误 vue_基于 Vue + Koa2 + MongoDB + Redis 实现一个完整的登录注册...

项目地址&#xff1a;https://github.com/caochangkui/vue-element-responsive-demo/tree/login-register通过 vue-cli3.0 Element 构建项目前端&#xff0c;Node.js Koa2 MongoDB Redis 实现数据库和接口设计&#xff0c;包括邮箱验证码、用户注册、用户登录、查看删除用户…

gwt 嵌入html_GWT和HTML5画布演示

gwt 嵌入html这是我对GWT和HTML5 Canvas的第一个实验。 我的第一个尝试是创建矩形&#xff0c;仅用几行代码就得出了这样的内容&#xff1a; 码&#xff1a; public class GwtHtml5 implements EntryPoint {static final String canvasHolderId "canvasholder";sta…

tez什么意思_传统数仓和大数据数仓的区别是什么?

概念与容器为什么先说这个&#xff0c;其实很简单&#xff1a;因为绝大多数人都把这两个概念混为一谈。然后就会出现各种各样的问题&#xff1a;oracle不是数据库么&#xff0c;怎么又是数据仓库&#xff1f;Hive不是数据仓库么&#xff1f;怎么又是数据库&#xff1f;数据仓库…

华为M2平板打不开云课堂_能运行PC应用的安卓生产力平板:华为MatePad Pro 5G登场...

当手机屏幕无法承担更复杂的工作任务&#xff0c;当PC重量不能满足更随性的移动办公&#xff0c;拥有全面屏和便携优势的平板电脑或是中间值&#xff0c;但前提是其必须拥有足够强大的生产力。5月27日华为在国内上市的其首款5G平板——华为MatePad Pro 5G&#xff0c;就是5G时代…

soa示例_SOA示例应用程序

soa示例SOA描述了一组用于创建松散耦合的&#xff0c;基于标准的&#xff0c;与业务相关的服务的模式&#xff0c;由于描述&#xff0c;实现和绑定之间的关注点分离&#xff0c;因此提供了新的灵活性。 近年来&#xff0c;至少在参与大多数信息技术活动的人们中&#xff0c;面向…

body curl 设置post_curl 命令详解

常用参数常用参数分类# 调试类-v, --verbose 输出信息-q, --disable 在第一个参数位置设置后 .curlrc 的设置直接失效&#xff0c;这个参数会影响到 -K, --config -A, --user-agent -e, --referer-K, --config FILE …

NetBeans Java EE技巧7:忽略的Java类和XHTML编辑器快捷方式

有时&#xff0c;最被忽略的是IDE最有用的功能。 在本文中&#xff0c;我将概述在开发Java EE应用程序时可以使用的五个NetBeans Java和XHTML编辑器快捷方式。 &#xff03;1 –轻松修复命名空间和类 也许您已经向尚未声明名称空间的视图中添加了新的JSF标记&#xff0c;或者…

python学习第三十二节(进程间通信、进程池、协程)

当多线程创建完毕之后&#xff0c;start并没有了立刻运行&#xff0c;依旧需要和其他线程抢CPU的资格&#xff0c;只是时间很短。进程之间的通信分为两种&#xff0c;queue和pipe 1 import multiprocessing2 def foo(q):3 q.put([1,hello,True])4 if __name____main__:5 …

备份ad_IT管理公开课——备份恢复解决方案

时间&#xff1a;2020年5月28日 14:00内容&#xff1a;调查显示&#xff0c;44&#xff05;的客户面临意外删除或修改数据&#xff0c;如果没有提前备份&#xff0c;这些数据是很难恢复的。RecoveryManager Plus是一款针对AD域&#xff0c;Exchange&#xff0c;Sharepoint以及O…

Ajax中的url使用规则

Ajax中的url使用规则Ajax中的url使用规则如下&#xff1a; 先封装项目访问地址&#xff1a; String basePath request.getScheme() "://" request.getServerName() ":" request.getServerPort() request.getContextPath();然后在js中定义&#xff1…

[网络管理]全双工与半双工的差别

[网络管理]全双工与半双工的差别 同事说新办公室的网络一直不稳定&#xff0c;常常掉线延迟。检查进口线路和更换转接网线。都无法解决这个问题。预计是不是进口网线中一根或者2根短路&#xff0c;那就改动下网卡属性吧。 把自适应改成全双工10M模式&#xff0c;測试OK。全双工…

echarts大屏模板_完整的可视化大屏分享,科技感十足,各行业直接就能用

你的老板有没有要求过你做一个可视化大屏&#xff1f;或许在你看来&#xff0c;这就是一个无理的需求&#xff0c;很简单啊&#xff0c;做几个动态图表&#xff0c;直接投影到屏幕上不就行了&#xff1f;就算做出来能用数据增长业务吗&#xff1f;不懂为什么要拍脑袋做大屏&…

Java面试基础知识(1)

1、final, finally, finalize的区别final&#xff1a;修饰符&#xff08;关键字&#xff09;如果一个类被声明为final&#xff0c;没有子类也不能被继承。因此一个类不能既被声明为 abstract的&#xff0c;又被声明为final的。将变量或方法声明为final&#xff0c;可以保证它们…

bird 报表_轻松完成Birt报告

bird 报表这是使用Birt插件在Eclipse中构建报告的完整指南。 Birt或Business Intelligence and Reporting工具是一种无需编写太多Java代码即可生成报告的工具。 如果您使用的是ireport&#xff0c;那么您知道我在说什么&#xff1a;&#xff09;&#xff08;晶体报告..毫无意义…

通过kubeadm安装kubernetes 1.7文档记录[docker容器方式]

参照了网上N多文档&#xff0c;不一一列表&#xff0c;共享精神永存&#xff01;&#xff01;&#xff01;&#xff01; 获取所有安装包 安装包分为两类&#xff0c;rpm安装包和docker镜像 rpm安装包 rpm为以下四个 kubeadm-1.7.0-0.x86_64.rpm kubectl-1.7.0-0.x86_64.rpm ku…

san框架计数的textarea

san框架计数的textarea<template><div style"height: 100%;width:100%;"><div style"border-bottom: 1px solid #e7e7e7"><div style"height:230px;overflow: hidden;"><t-textarea id"cxfssy" value"…

京东五星电器送扫地机器人_京东五星电器联手打造互联网小家电“孵化器”

出新迭代迅速、件均价较低的生活小家电复购率高&#xff0c;易“种草”&#xff0c;是最具活力的家电品类。11月24日&#xff0c;京东小家电事业部联合京东五星电器召开“小家电发展战略沟通会”&#xff0c;美的、苏泊尔、九阳、小熊、雀巢、德龙、戴森、飞利浦、科沃斯等众多…

异或运算性质

异或性质**异或运算的性质**

星痕 轻松实现大屏数据可视化_数据美的历程有多难?大屏可视化轻松帮你实现...

看到这个数据可视化大屏&#xff0c;我们如何实现美感呢&#xff1f;正确的姿势必不可少&#xff01;当我们满怀激动地开始数据可视化时&#xff0c;请不要马上钻入某个细节里&#xff0c;不要急着考虑用什么酷炫的图表来展现&#xff0c;也不要纠结于用什么颜色、什么字体。而…

卡方检验检验水准矫正_【2008.】趋势性卡方检验专题讨论

学员提问学员老师&#xff0c;在SPSS中怎样实现线性趋势卡方检验呢&#xff1f;很多学员都为这个问题疑惑&#xff0c;下面我来详细介绍一下。老师1、线性趋势卡方检验的常用方法&#xff1a;线性趋势检验最常用的方法是&#xff1a;Cochran-Armitage Test for Trend(也就是the…