实现思路

MVC思路。三个端分工合作。可以仔细看。整个俄罗斯方块为一个二维数组。C控制M, M控制二维数组,V只要把二维数组渲染出来就行 。我下面的实现思路可以应用到任何一个这种二维数组感觉的小游戏。扩展性还算不错。

视图层

html部分

<canvas id="canvas"></canvas>

js部分

这里就是画背景和画全部方框的方法。

// 画背景
function drawBG(){CTX.lineWidth = 1;CTX.fillStyle = "#000";CTX.fillRect(0,0, CANVAS.width, CANVAS.height);CTX.strokeStyle = "#eee";// 画横线for(let i = BLOCK_SIZE; i < CANVAS.height; i +=BLOCK_SIZE){CTX.beginPath(); //新建一条pathCTX.moveTo(0, i); CTX.lineTo(CANVAS.width, i); CTX.closePath();CTX.stroke(); //绘制路径。}// 画竖线for(let i = BLOCK_SIZE; i < CANVAS.width; i += BLOCK_SIZE){CTX.beginPath(); //新建一条pathCTX.moveTo(i, 0); CTX.lineTo(i, CANVAS.height); CTX.closePath();CTX.stroke(); //绘制路径。}}// 绘制全部格子
function drawAllBlock(){    for(let i = 0; i < Y_MAX; i++){for(let j = 0; j < X_MAX; j ++){CTX.lineWidth = 8;let cell = BLOCK_LIST[i][j];if(!cell){continue}CTX.strokeStyle = cell.color;CTX.strokeRect( cell.x * BLOCK_SIZE + 4, cell.y*BLOCK_SIZE + 4, BLOCK_SIZE - 8, BLOCK_SIZE-8);}}
} 

模型层

Cell。 我以一个单元格为一个对象。这个格子有自己的颜色,坐标。当然还可以扩展,例如图片之类的。在这个游戏里面,格子还会移动。所以还包含移动的方法。

// 单元类(就是一个一个的格子)
class Cell{// 位置x = 0;y = 0;//颜色color = "#000";// 单元格的id,区别不同的单元格(谁叫 js 没罚判断对象是否同一个)id = "";/*** 构造器* @param * {Number} x: x轴位置* {Number} y: y轴位置*/constructor({x, y, color}) {this.x = x || 0;this.y = y || 0;this.color = color || "#000";this.id = `${new Date().getTime()}${x}${y}`;//生成唯一 id 瞎写逻辑,不重复就行if(this.y >= 0){BLOCK_LIST[this.y][this.x] = this;}}/*** 移动方法* * @params {String} type: 类型,LEFT、LEFT、DOWN 对应的移动的方向* * @returns 返回移动结果坐标*/getMovePositionByType(type){let position = {x: this.x,y: this.y}switch(type){case "LEFT":position.x -= 1;break;case "RIGHT":position.x += 1;break;case "DOWN":position.y += 1break;}return position;} /*** 设定位置* @params { int } x: x坐标* @params { int } y: Y坐标*/setCellPoisition({x, y}){// 记住上一次位置let beforX = this.x;let beforY = this.y;// 如果上一个格子没有被征用就回收if(beforY >= 0 && BLOCK_LIST[beforY][beforX].id == this.id){BLOCK_LIST[beforY][beforX] = null;}if( y >= 0 ){BLOCK_LIST[y][x] = this;}// 赋值给 x ythis.x = x;this.y = y;}
}

Block类

        这个就是这个游戏的主角,所有形状的格子都可以理解为多个 Cell 的集合。所以这个类强调的是集合,并不是块本身。所以这个类没有坐标,用一个数组表示自己。这个块主要拥有下落,和旋转方法。

碰撞检测

        整体设计清楚后,这个反而很简单,只要判断当前坐标在二维数组里面是否有对象就行了。

/*** 移动碰撞检测方法* * @arg type: 移动类型* @returns true 撞了*/
isCollision(type){// 碰撞检测let result = false;for(let i = 0, len = this.body.length; i < len; i ++ ){let position = {};// 移动类型if( type != "ROTATION"){// 拿到移动的下一个坐标position = this.body[i].getMovePositionByType(type);}else{// 旋转类型position = this.getOffsetPosition(i);}// 边界判断// 看看x有没有撞if(position.x < 0 || position.x >= X_MAX){result = truebreak;}// 看看y有么有撞if( position.y >= Y_MAX){result = truebreak;}// 如果y为负数,不用验证,直接过if(position.y < 0){break;}// 看看有没有碰到格子// 排除自身碰撞 !this.body.some( e => e.id == targetBlock.id) 只要有任意一个是自己body里面的cell就不算碰撞let targetBlock = BLOCK_LIST[position.y][position.x];if(targetBlock && !this.body.some( e => e.id == targetBlock.id)){result = truebreak;}}// 如果是下落的移动方式且也碰到了东西,我们认为这个块已经结束使命了。if( type == "DOWN" && result){this.stopActive = true;}return result
}

旋转    这个方法比较有意思。旋转的实现我自己是这么理解的。他其实也是一个新的块。只是他们之间的位置一样。所以这个类在创建之初,就需要输入对应旋转的各个状态。以及旋转中心。

//旋转方法
rotationBlock(){// 没有旋转中心的不转if(this.centerPoint == -1){return}// 撞了就打断,不转了if(this.isCollision("ROTATION")){return}// 偏移值this.body.forEach( (e, index) => {e.setCellPoisition(this.getOffsetPosition(index))})this.rotate += 1;if( this.rotate == this.rotationList.length){this.rotate = 0;}
}// 偏移值计算方法
getOffsetPosition(cellIndex){let index = this.rotate + 1;if(this.rotate ==  this.rotationList.length -1){index = 0;}let targetPoisition = this.rotationList[index][cellIndex];let centerPoisition = this.rotationList[index][this.centerPoint];return {x: this.body[this.centerPoint].x + (targetPoisition.x - centerPoisition.x),y: this.body[this.centerPoint].y + (targetPoisition.y  - centerPoisition.y)};
}

下落    方法就是定时器,每隔个几秒执行一次移动,碰撞就停止

// 下落方法
down(){this.downTimer = setInterval(()=>{// 是否已经停止活动if(this.stopActive){clearInterval(this.downTimer);return}// 游戏运行状态才可以移动 if(GAME_STATE == 0){this.move("DOWN");}}, this.downSpeed)
}


基础结束后,我们可以开始扩展了。比如我们添加一个L字格子

L字格子

我这里是用继承类来设置,其实可以不用。因为到 Block 类哪里,已经代表全部了,可以不用在扩展类了

// L形格子
class LBlock extends Block {/*** 构造器*/constructor({x, y, color}) {super()this.color = color || "#00FFFF";this.centerPoint = 2; // 第几个块是旋转中心this.rotate = 0; // 当前旋转角度this.rotationList = [[{x: 0, y: 0}, {x: 0, y: 1}, {x: 1, y: 1}, {x: 2, y: 1}], // 原来形状[{x: 2, y: 0}, {x: 1, y: 0}, {x: 1, y: 1}, {x: 1, y: 2}], // 旋转 90[{x: 2, y: 2}, {x: 2, y: 1}, {x: 1, y: 1}, {x: 0, y: 1}], // 旋转 180[{x: 0, y: 2}, {x: 1, y: 2}, {x: 1, y: 1}, {x: 1, y: 0}]  // 旋转 270]this.initBody(x || 4, y || -1); // 初始化位置(就是你希望他一开始在哪里出现)}
}

运行

终于到最后一步了采用 requestAnimationFrame 来刷新故方法如下

// 运行
function run(){// 使用 AnimationFrame 刷新window.requestAnimationFrame(run);// 游戏非暂停运行阶段都停止渲染if(GAME_STATE != 0){return}// 绘制背景drawBG();// 绘制二维表格drawAllBlock();// 如果活动格子停止活动了,就开始算分和创建新的格子if(ACTIVE_BLOCK.stopActive){// 算分computeScore();// 更新最顶的格子setTopCellPoisition()// 是否游戏结束gameOver();// 创建格子ACTIVE_BLOCK = getRandomBlock();}}// 启动方法
function start(){// 设置为运行状态GAME_STATE = 0;OVER_DOM.style.display = "none";// 初始化二维数组initBodyList();// 创建正在活动的格子ACTIVE_BLOCK = getRandomBlock();// 启动渲染window.requestAnimationFrame(run);
}// 启动
start()

这里游戏就跑起来了。你可能会疑问,菜鸟教程有一篇文章这么提到

原文链接

其实仔细观察代码就可以发现,我是把画背景的方法在画全部二维数组格子前执行。背景直接覆盖掉前面的所有东西,就实现了动画的效果。

下面是全部源代码

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>俄罗斯方块</title><style>.game-tip{position: absolute;z-index: 1;top: 100px;left: 100px;display: none;color: #fff;font-size: 55px;font-weight: bold;}</style>
</head>
<body><h3>按 W 旋转, ASD移动, 空格暂停</h3><span>分数:</span><span id="score"></span><br><div class="canvas-wrap"><canvas id="canvas"></canvas><div id="game-over" class="game-tip"> 游戏结束 </div><div id="game-pause" class="game-tip"> 游戏暂停 </div></div><script>// X 轴上有几个格子const X_MAX = 10;// Y 轴上有几个格子const Y_MAX = 20;// 格子大小const BLOCK_SIZE = 40;// 全部格子的集合const BLOCK_LIST = [];// 正在活动的块var ACTIVE_BLOCK = null;// 下一个活动的块(待扩展)var NEXT_ACTIVE_BLOCK = null;// 分数var SCORE = 0;// 显示分数的 domconst SCORE_DOM = document.getElementById("score");// 暂停的 domconst PAUSE_DOM = document.getElementById("game-pause");// 游戏结束的 domconst OVER_DOM = document.getElementById("game-over");// 最顶部的块var TOP_CELL_POISITION = Y_MAX;// canvas 两兄弟const CANVAS = document.getElementById("canvas");CANVAS.width = BLOCK_SIZE * X_MAX;CANVAS.height = BLOCK_SIZE * Y_MAX;const CTX = CANVAS.getContext('2d');var GAME_STATE = -1; //0 游戏运行状态, 1,游戏暂停, -1游戏结束/******************  渲染层  ***********************/// 画背景function drawBG(){CTX.lineWidth = 1;CTX.fillStyle = "#000";CTX.fillRect(0,0, CANVAS.width, CANVAS.height);CTX.strokeStyle = "#eee";// 画横线for(let i = BLOCK_SIZE; i < CANVAS.height; i +=BLOCK_SIZE){CTX.beginPath(); //新建一条pathCTX.moveTo(0, i); CTX.lineTo(CANVAS.width, i); CTX.closePath();CTX.stroke(); //绘制路径。}// 画竖线for(let i = BLOCK_SIZE; i < CANVAS.width; i += BLOCK_SIZE){CTX.beginPath(); //新建一条pathCTX.moveTo(i, 0); CTX.lineTo(i, CANVAS.height); CTX.closePath();CTX.stroke(); //绘制路径。}}// 绘制全部格子function drawAllBlock(){    for(let i = 0; i < Y_MAX; i++){for(let j = 0; j < X_MAX; j ++){CTX.lineWidth = 8;let cell = BLOCK_LIST[i][j];if(!cell){continue}CTX.strokeStyle = cell.color;CTX.strokeRect( cell.x * BLOCK_SIZE + 4, cell.y*BLOCK_SIZE + 4, BLOCK_SIZE - 8, BLOCK_SIZE-8);}}} /*****************  model 层   *********************/// 单元类(就是一个一个的格子)class Cell{// 位置x = 0;y = 0;//颜色color = "#000";// 单元格的id,区别不同的单元格(谁叫 js 没罚判断对象是否同一个)id = "";/*** 构造器* @param * {Number} x: x轴位置* {Number} y: y轴位置*/constructor({x, y, color}) {this.x = x || 0;this.y = y || 0;this.color = color || "#000";this.id = `${new Date().getTime()}${x}${y}`;//生成唯一 id 瞎写逻辑,不重复就行if(this.y >= 0){BLOCK_LIST[this.y][this.x] = this;}}/*** 移动方法* * @params {String} type: 类型,LEFT、LEFT、DOWN 对应的移动的方向* * @returns 返回移动结果坐标*/getMovePositionByType(type){let position = {x: this.x,y: this.y}switch(type){case "LEFT":position.x -= 1;break;case "RIGHT":position.x += 1;break;case "DOWN":position.y += 1break;}return position;} /*** 设定位置* @params { int } x: x坐标* @params { int } y: Y坐标*/setCellPoisition({x, y}){// 记住上一次位置let beforX = this.x;let beforY = this.y;// 如果上一个格子没有被征用就回收if(beforY >= 0 && BLOCK_LIST[beforY][beforX].id == this.id){BLOCK_LIST[beforY][beforX] = null;}if( y >= 0 ){BLOCK_LIST[y][x] = this;}// 赋值给 x ythis.x = x;this.y = y;}}// 块类。 单元格的集合状态class Block{// 身体body = [];// 旋转中心centerPoint = -1; // 指旋转不变的点//各个角度的样子rotationList = [];// 旋转角度rotate = -1; // 指旋转到了 rotationList 第几个数组// 下落定时器downTimer = null;// 下落时间downSpeed = 500;// 是否停止活动, true是stopActive = false;// 初始化 bodyinitBody(x, y){if(this.rotate == -1 || !this.rotationList.length ){return}// 如果有旋转中心,就用旋转中心,没有就用第一个。即0let contrastPoint = this.centerPoint != -1 ? this.centerPoint : 0;let centerPoisition = this.rotationList[this.rotate][contrastPoint];for(let i = 0; i < this.rotationList[this.rotate].length; i ++ ){let position = this.rotationList[this.rotate][i];this.body.push(new Cell({x: x + (position.x - centerPoisition.x),y: y + (position.y - centerPoisition.y),color: this.color}))}// 启用下落方法this.down();}//旋转方法rotationBlock(){// 没有旋转中心的不转if(this.centerPoint == -1){return}// 撞了就打断,不转了if(this.isCollision("ROTATION")){return}// 偏移值this.body.forEach( (e, index) => {e.setCellPoisition(this.getOffsetPosition(index))})this.rotate += 1;if( this.rotate == this.rotationList.length){this.rotate = 0;}}// 下落方法down(){this.downTimer = setInterval(()=>{// 是否已经停止活动if(this.stopActive){clearInterval(this.downTimer);return}// 游戏运行状态才可以移动 if(GAME_STATE == 0){this.move("DOWN");}}, this.downSpeed)}// 移动方法move( type ){// 撞了就打断,不动了if(this.isCollision(type)){return}// 移动for(let i = 0, len = this.body.length; i < len; i ++ ){// 拿到移动的下一个坐标let position = this.body[i].getMovePositionByType(type);// 设进去this.body[i].setCellPoisition(position);}}/*** 移动碰撞检测方法* * @arg type: 移动类型* @returns true 撞了*/isCollision(type){// 碰撞检测let result = false;for(let i = 0, len = this.body.length; i < len; i ++ ){let position = {};// 移动类型if( type != "ROTATION"){// 拿到移动的下一个坐标position = this.body[i].getMovePositionByType(type);}else{// 旋转类型position = this.getOffsetPosition(i);}// 边界判断// 看看x有没有撞if(position.x < 0 || position.x >= X_MAX){result = truebreak;}// 看看y有么有撞if( position.y >= Y_MAX){result = truebreak;}// 如果y为负数,不用验证,直接过if(position.y < 0){break;}// 看看有没有碰到格子// 排除自身碰撞 !this.body.some( e => e.id == targetBlock.id) 只要有任意一个是自己body里面的cell就不算碰撞let targetBlock = BLOCK_LIST[position.y][position.x];if(targetBlock && !this.body.some( e => e.id == targetBlock.id)){result = truebreak;}}// 如果是下落的移动方式且也碰到了东西,我们认为这个块已经结束使命了。if( type == "DOWN" && result){this.stopActive = true;}return result}// 偏移值计算方法getOffsetPosition(cellIndex){let index = this.rotate + 1;if(this.rotate ==  this.rotationList.length -1){index = 0;}let targetPoisition = this.rotationList[index][cellIndex];let centerPoisition = this.rotationList[index][this.centerPoint];return {x: this.body[this.centerPoint].x + (targetPoisition.x - centerPoisition.x),y: this.body[this.centerPoint].y + (targetPoisition.y  - centerPoisition.y)};}// 获取最顶端格子的位置getTopCellY(){return Math.min(...this.body.map( e => e.y))}}// 田字格class BigBlock extends Block {/*** 构造器*/constructor({x, y, color}) {super()this.color = color || "#0FF";this.rotate = 0;this.rotationList = [[{x: 0, y: 0}, {x: 1, y: 0}, {x: 0, y: 1}, {x: 1, y: 1}],]this.initBody(x || 4, y || -1);}}// 一字格子class LongBlock extends Block {/*** 构造器*/constructor({x, y, color}) {super()this.color = color || "#FF7F00";this.rotate = 0;this.centerPoint = 1;this.rotationList = [[{x: 0, y: 0}, {x: 1, y: 0}, {x: 2, y: 0}, {x: 3, y: 0}],[{x: 1, y: -1}, {x: 1, y: 0}, {x: 1, y: 1}, {x: 1, y: 2}],]this.initBody(x || 4, y || -1);}}// L形格子class LBlock extends Block {/*** 构造器*/constructor({x, y, color}) {super()this.color = color || "#00FFFF";this.centerPoint = 2; // 第几个块是旋转中心this.rotate = 0; // 当前旋转角度this.rotationList = [[{x: 0, y: 0}, {x: 0, y: 1}, {x: 1, y: 1}, {x: 2, y: 1}], // 原来形状[{x: 2, y: 0}, {x: 1, y: 0}, {x: 1, y: 1}, {x: 1, y: 2}], // 旋转 90[{x: 2, y: 2}, {x: 2, y: 1}, {x: 1, y: 1}, {x: 0, y: 1}], // 旋转 180[{x: 0, y: 2}, {x: 1, y: 2}, {x: 1, y: 1}, {x: 1, y: 0}]  // 旋转 270]this.initBody(x || 4, y || -1); // 初始化位置(就是你希望他一开始在哪里出现)}}// 反L形格子class RLBlock extends Block {/*** 构造器*/constructor({x, y, color}) {super()this.color = color || "#00F";this.centerPoint = 1;this.rotate = 0;this.rotationList = [[{x: 0, y: 1}, {x: 1, y: 1}, {x: 2, y: 1}, {x: 2, y: 0}],[{x: 1, y: 0}, {x: 1, y: 1}, {x: 1, y: 2}, {x: 2, y: 2}],[{x: 2, y: 1}, {x: 1, y: 1}, {x: 0, y: 1}, {x: 0, y: 2}],[{x: 1, y: 2}, {x: 1, y: 1}, {x: 1, y: 0}, {x: 0, y: 0}]]this.initBody(x || 4, y || -1);}}// Z格子class ZBlock extends Block {/*** 构造器*/constructor({x, y, color}) {super()this.color = color || "#0F0";this.centerPoint = 2;this.rotate = 0;this.rotationList = [[{x: 0, y: 0}, {x: 1, y: 0}, {x: 1, y: 1}, {x: 2, y: 1}],[{x: 2, y: 0}, {x: 2, y: 1}, {x: 1, y: 1}, {x: 1, y: 2}],[{x: 2, y: 2}, {x: 1, y: 2}, {x: 1, y: 1}, {x: 0, y: 1}],[{x: 0, y: 2}, {x: 0, y: 1}, {x: 1, y: 1}, {x: 1, y: 0}]]this.initBody(x || 4, y || -1);}}// 反Z格子class RZBlock extends Block {/*** 构造器*/constructor({x, y, color}) {super()this.color = color || "#8B00FF";this.centerPoint = 1;this.rotate = 0;this.rotationList = [[{x: 0, y: 1}, {x: 1, y: 1}, {x: 1, y: 0}, {x: 2, y: 0}],[{x: 1, y: 0}, {x: 1, y: 1}, {x: 2, y: 1}, {x: 2, y: 2}],[{x: 2, y: 1}, {x: 1, y: 1}, {x: 1, y: 2}, {x: 0, y: 2}],[{x: 1, y: 2}, {x: 1, y: 1}, {x: 0, y: 1}, {x: 0, y: 0}]]this.initBody(x || 4, y || -1);}}// T格子class TBlock extends Block {/*** 构造器*/constructor({x, y, color}) {super()this.color = color || "#F00";this.centerPoint = 2;this.rotate = 0;this.rotationList = [[{x: 1, y: 0}, {x: 0, y: 1}, {x: 1, y: 1}, {x: 2, y: 1}],[{x: 2, y: 1}, {x: 1, y: 0}, {x: 1, y: 1}, {x: 1, y: 2}],[{x: 1, y: 2}, {x: 2, y: 1}, {x: 1, y: 1}, {x: 0, y: 1}],[{x: 0, y: 1}, {x: 1, y: 2}, {x: 1, y: 1}, {x: 1, y: 0}]]this.initBody(x || 4, y || -1);}}/******************  控制层  *************************/document.onkeydown = function(event) {let e = event || window.event ;switch( e.keyCode ){//Wcase 87:ACTIVE_BLOCK.rotationBlock();break;//Scase 83:ACTIVE_BLOCK.move("DOWN");break;//Acase 65:ACTIVE_BLOCK.move("LEFT");break;//Dcase 68:ACTIVE_BLOCK.move("RIGHT");break;// 空格case 32:pause();break;}};// 初始化 BODY_LISTfunction initBodyList(){// 初始化 BLOCK_LISTfor(let i = 0; i < Y_MAX; i++){let list = []for(let j = 0; j < X_MAX; j++){list.push(null)}BLOCK_LIST.push(list)}}// 算分方法function computeScore(){let clearRowIndexList = [];for(let i = Y_MAX - 1; i >= 0; i --){// 如果每一行都有格子,消除这一行,并把上面的格子往下移动,并+10分if(BLOCK_LIST[i].every( e => e)){SCORE += 10;// 记录需要消除的行clearRowIndexList.unshift(i);}}// 消除行clearRow(clearRowIndexList)SCORE_DOM.innerText = SCORE;}// 消除行// 消除 index 行, 并把 index 行上面的全部格子下移function clearRow( list ){while(list.length){let index = list.pop();// 清空for(let i = 0; i < X_MAX; i ++){BLOCK_LIST[index][i] = null;}// 把上面的行移动下来for(let i = index - 1; i >= 0; i --){BLOCK_LIST[i].forEach( e => {if(e){let poisition = e.getMovePositionByType("DOWN");e.setCellPoisition(poisition);}});}// 把剩余的index += 1for(let i = 0, len = list.length; i < len; i ++){list[i] += 1;}}}// 生成随机格子function getRandomBlock(){let block = null;let randomType = Math.floor(Math.random()*7);switch(randomType){case 0: // z格子block = new ZBlock({});break;case 1: // 田字格子block = new BigBlock({});break;case 2: // 一字格子block = new LongBlock({});break;case 3: // L格子block = new LBlock({});break;case 4: // 反L格子block = new RLBlock({});break;case 5: // 反Z格子block = new RZBlock({});break;case 6: // T格子block = new TBlock({});break;}return block;}// 设置顶的位置function setTopCellPoisition(){if(ACTIVE_BLOCK.getTopCellY() < TOP_CELL_POISITION){TOP_CELL_POISITION = ACTIVE_BLOCK.getTopCellY();}};// 游戏结束function gameOver(){if(TOP_CELL_POISITION <= 0){GAME_STATE = -1;OVER_DOM.style.display = "block";}}   // 游戏暂停 //0 游戏运行状态, 1,游戏暂停, -1游戏结束function pause(){if (GAME_STATE == 1) {// 开始GAME_STATE = 0;PAUSE_DOM.style.display = "none";}else if(GAME_STATE != -1){// 暂停GAME_STATE = 1;PAUSE_DOM.style.display = "block";}}/********************  引用的地方  **********************/// 运行function run(){// 使用 AnimationFrame 刷新window.requestAnimationFrame(run);// 游戏非暂停运行阶段都停止渲染if(GAME_STATE != 0){return}// 绘制背景drawBG();// 绘制二维表格drawAllBlock();// 如果活动格子停止活动了,就开始算分和创建新的格子if(ACTIVE_BLOCK.stopActive){// 算分computeScore();// 更新最顶的格子setTopCellPoisition()// 是否游戏结束gameOver();// 创建格子ACTIVE_BLOCK = getRandomBlock();}}// 启动方法function start(){// 设置为运行状态GAME_STATE = 0;OVER_DOM.style.display = "none";// 初始化二维数组initBodyList();// 创建正在活动的格子ACTIVE_BLOCK = getRandomBlock();// 启动渲染window.requestAnimationFrame(run);}// 启动start();</script>
</body>
</html>

js 实现俄罗斯方块相关推荐

  1. 纯js实现俄罗斯方块详解与源码

    对于小白来说用js实现俄罗斯方块还是有难度的,网上找了很多代码看,有的很长难懂,有的短小精悍,但不只用到了js还用到了框架,对于还未接触框架的小白宝宝,也只能无奈自己是小白了,自己写不出来那就找一篇纯 ...

  2. 使用JS实现俄罗斯方块游戏

    使用JS实现俄罗斯方块游戏 简单的JS俄罗斯方块游戏源码 效果图: 代码如下,复制即可使用: <!DOCTYPE html> <html> <head> <m ...

  3. 用纯JS做俄罗斯方块 - 简要思路介绍(1)

    大家都知道俄罗斯方块是一款大众化的游戏了,我很小的时候就玩过,今年已经25岁了,可以说俄罗斯方块确实是历史悠久,做俄罗斯方块是我上个星期开始的想法.也许是由于自己从来没有写过这种东西吧,所以有生疏.代 ...

  4. js版俄罗斯方块(二)

    之前曾发过一个js版的俄罗斯方块,界面比较简单,Bug也不少.抽空重构了一下,加入了javaScript面向对象的知识,修复了一些明显的BUG. 斌斌 (给我写信) 原创博文(http://blog. ...

  5. php开发俄罗斯方块,HTML5+JS实现俄罗斯方块原理及具体步骤_html5教程技巧

    本游戏实现的基本原理:游戏区域是限定大小的区域,本游戏的游戏区域有21×25个矩形,每个矩形width为10单位,heght为6个单位(canvas 的绝对单位是固定的,非像素). 创建RusBloc ...

  6. JS实现俄罗斯方块有声游戏Tetris Game JavaScript/TypeScript AudioContext

    Tetris Game / 俄罗斯方块 扫码体验 https://capricorncd.github.io/tetris/dist 游戏截图 键盘操作 Left: ←, Right: →, Rota ...

  7. JS实现——俄罗斯方块

    把以下代码保存成Tetris.html文件,使用Google或360浏览器打开 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 4.0 Transit ...

  8. 原生JS实现俄罗斯方块

    <!DOCTYPE html> <body> <script>//面向对象function Game () {//虚拟的节点对象var frag = documen ...

  9. 俄罗斯方块源码分享 html+css+js

    效果: [html+css+js]俄罗斯方块源码分享 这是在网上跟着黑马的视频做的,然后也加了些自己的想法. 在线试玩:http://www.beijiguang.site/game/index.ht ...

最新文章

  1. 为提高能量利用效率,大脑会对感官进行预测
  2. 017_html图像
  3. Java递归实现二分法
  4. 第3章 Python的数据结构、函数和文件
  5. 鸿蒙开发版智慧生活,华为发布全新分布式鸿蒙OS,打造全场景智慧生活新体验...
  6. 参数数组(params)的用法
  7. [项目回顾]基于Annotation与SpringAOP的缓存简单解决方案
  8. 第2节 storm实时看板案例:12、实时看板综合案例代码完善;13、今日课程总结...
  9. C++_实现一个简单的智能指针shared_ptr
  10. 计算机建表格,电脑文档怎么做表格
  11. python提取图片中的文字
  12. 一张图看懂苹果MacBook所有屏幕分辨率
  13. 没看错!用游戏测试人工智能。
  14. 华为鸿蒙战略发布会视频,华为公布鸿蒙手机操作系统开机画面视频
  15. 【墨者学院writeup】浏览器信息伪造之User-Agent及NetType微信网络检测破解
  16. 二次规划算法学习笔记
  17. Q-learning学习的一个小例子
  18. 视频加密选择在线加密还是软件加密好?
  19. 朋友合作怎么迈过利益这道坎
  20. 鸟哥的linux私房菜第七章

热门文章

  1. 高清屏下canvas绘制文字模糊
  2. vant框架底部导航栏固定滑动抖动解决
  3. 漫城漫画小说CMS系统源码/小说CMS系统源码
  4. v-show使用三元运算符
  5. 专业应用软件随身带,一招助您轻松搞定客户
  6. c语言检测状态是否变化,C语言数组状态研究
  7. 洛谷 P1010 递归
  8. JAVA 异步通过微信返回的url获取到用户头像并保存到指定目录
  9. 只有偏执狂才能生存!
  10. windows7性能优化