运用HTML5/CSS3/JavaScript开发一个小游戏:俄罗斯方块。
三个文件:tetris.html、tetris.css、tetris.js
(完整的js代码在文末)
程序思路框架&相关函数调用
1、window.onload();//文档加载完成之后就会触发该事件
(类似一个入口)
2、 window.onkeydown();// 为窗口的按键事件绑定事件监听器
3、 moveDown();//处理掉落方块组合
一、 开发游戏界面
-
html页面代码:
tetris.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html"; charset="utf-8">
<title>俄罗斯方块</title>
<script type="text/javascript" src="tetris.js"></script>
<link rel="stylesheet" type="text/css" href="tetris.css">
</head>
<body id="body">
<h3 >俄罗斯方块</h3>
<div class="blue">
<div class="speedscore">
速度:<span id="curSpeedEle"></span>
当前积分:<span id="curScoreEle"></span>
</div>
<div class="maxscore">
最高积分:<span id="maxScoreEle"></span>
</div>
</div>
</body>
</html>
2 . css页面代码:
tetris.css
@font-face{
font-family:tmb;
src:url("DS-DIGIB.TTF") format("TrueType");
}
div{
font-size: 15pt;
padding-bottom: 4px;
}
span{
font-family: tmb;
font-size: 15pt;
color:green;
}
body{
background-color: #F0B4DB;
}
.blue{
width: 400px;
border: 1px solid black;
background-color: #99FCFF;
}
.speedscore{
float: left;
}
.maxscore{
float: right;
}
3 . js页面代码:tetris.js
//界面开发
var TETRIS_ROWS = 20; //20行
var TETRIS_COLS = 14; //14列
var CELL_SIZE = 25; //一个格子大小
var tetris_canvas;
var tetris_ctx;
//定义了一个函数,需要四个参数
var createCanvas = function(rows ,cols ,cellWidth ,cellHeight) //定义了一个函数,需要四个参数
{
//新建canvas元素的tetris_canvas对象
//document.createElement()与document.appendChild()联用
//在这里不用tetris_canvas = document.getElementById("canvas");
//getElementById方法刷新页面不能继续游戏而是重新开始游戏
tetris_canvas = document.createElement("canvas");
//设置canvas组件的高度、宽度
tetris_canvas.width = cols * cellWidth;
tetris_canvas.height = rows * cellHeight;
//设置组件边框
tetris_canvas.style.border="1px solid black";
//获取canvas上的绘图API
//获取canvas上绘图的CanvasRenderingContext2D对象
tetris_ctx=tetris_canvas.getContext('2d');
//开始创建路径
tetris_ctx.beginPath();
//绘制横向网格对应的路径,画网格里面的横线
for (var i=1; i < TETRIS_ROWS ; i++)
{
tetris_ctx.moveTo(0 , i * CELL_SIZE);
//把当前路径结束点移动到(0 , i * CELL_SIZE),比如刚开始(0,a)
tetris_ctx.lineTo(TETRIS_COLS * CELL_SIZE , i * CELL_SIZE);
//从当前结束点连接到(TETRIS_COLS * CELL_SIZE , i * CELL_SIZE),比如从(0,a)到(14*a,0)
}
//绘制竖向网格对应的路径
for (var i=1; i < TETRIS_COLS ; i++)
{
tetris_ctx.moveTo(i * CELL_SIZE , 0);
tetris_ctx.lineTo(i * CELL_SIZE , TETRIS_ROWS * CELL_SIZE);
}
tetris_ctx.closePath();//关闭前面定义的路径
//设置笔触颜色,绘制路径填充风格,当前为纯色;fillStyle是填充路径填充风格
tetris_ctx.strokeStyle="#314478";
//设置线条粗细
tetris_ctx.lineWidth=1;
//绘制线条
tetris_ctx.stroke();
}
二、数据模型
//数据模型
var NO_BLOCK = 0;
var currentFall;
colors = ["#fff", "#f00" , "#0f0" , "#00f"
, "#c60" , "#f0f" , "#0ff" , "#609"];
//该数组用于记录底下已经固定的方块,初始化为没有方块
var tetris_status=[];
for (var i = 0; i < TETRIS_ROWS; i++)
{
tetris_status[i]=[];
for (var j = 0; j < TETRIS_COLS; j++)
{
tetris_status[i][j] = NO_BLOCK;
}
}
//定义几种可能出现的方块组合,相当于一个二维数组blockArr[7][4]
var blockArr = [
[ {x:TETRIS_COLS/2-1,y:0,color:1}, //1. Z形方块组合
{x:TETRIS_COLS/2,y:0,color:1},
{x:TETRIS_COLS/2,y:1,color:1},
{x:TETRIS_COLS/2+1,y:1,color:1}
],
[ {x:TETRIS_COLS/2+1,y:0,color:2}, //2. 反Z形方块组合
{x:TETRIS_COLS/2,y:0,color:2},
{x:TETRIS_COLS/2,y:1,color:2},
{x:TETRIS_COLS/2-1,y:1,color:2}
],
[ {x:TETRIS_COLS/2-1,y:0,color:3}, //3. 田形方块组合
{x:TETRIS_COLS/2,y:0,color:3},
{x:TETRIS_COLS/2-1,y:1,color:3},
{x:TETRIS_COLS/2,y:1,color:3}
],
[ {x:TETRIS_COLS/2,y:0,color:4}, //4. L形方块组合
{x:TETRIS_COLS/2,y:1,color:4},
{x:TETRIS_COLS/2,y:2,color:4},
{x:TETRIS_COLS/2+1,y:2,color:4}
],
[ {x:TETRIS_COLS/2,y:0,color:5}, //5. J形方块组合
{x:TETRIS_COLS/2,y:1,color:5},
{x:TETRIS_COLS/2,y:2,color:5},
{x:TETRIS_COLS/2-1,y:2,color:5}
],
[ {x:TETRIS_COLS/2,y:0,color:6}, //6. 竖形方块组合
{x:TETRIS_COLS/2,y:1,color:6},
{x:TETRIS_COLS/2,y:2,color:6},
{x:TETRIS_COLS/2,y:3,color:6}
],
[ {x:TETRIS_COLS/2,y:0,color:7}, //7. 凸形方块组合
{x:TETRIS_COLS/2-1,y:1,color:7},
{x:TETRIS_COLS/2,y:1,color:7},
{x:TETRIS_COLS/2+1,y:1,color:7}
]
]
//定义正在掉落的方块
var initBlock =function()
{
//随机生成正在掉落的方块(0-6)
//math.floor(x)返回小于参数x的最大整数,即对浮点数向下取整.
//Math.random()是令系统随机选取大于等于 0.0 且小于 1.0 的伪随机 double 值
var rand = Math.floor (Math.random() * blockArr.length);
//第rand+1种方块组合,每种组合四个方块组成,
currentFall = [
{x;blockArr[rand][0].x, y: blockArr[rand][0].y, color: blockArr[rand][0].color},
{x;blockArr[rand][1].x, y: blockArr[rand][1].y, color: blockArr[rand][1].color},
{x;blockArr[rand][2].x, y: blockArr[rand][2].y, color: blockArr[rand][2].color},
{x;blockArr[rand][3].x, y: blockArr[rand][3].y, color: blockArr[rand][3].color}
];
}
//注意:tetris_status[y][x],因为二维数组竖直方向为i(行数)=纵坐标y,横向为j(列数)=横坐标x
三、游戏逻辑
//游戏逻辑
var curScore = 0; //当前积分值
var curSpeed = 1; //当前速度
var maxScore = 0; //最大积分值
var curScoreEle , curSpeedEle , maxScoreEle;
var curTimer;//setInterval() 返回的ID值,curTimer = setInterval("moveDown();" , 500 / curSpeed);
var isPlaying = true; //判断游戏是否结束
//处理掉落
var moveDown = function()
{
// 判断是否能掉落的标志
var canDown = true;
// 遍历正在掉落的组合的每一个方块,判断是否能掉落
for (var i = 0 ; i < currentFall.length ; i++)
{
// 已经到了底部
if(currentFall[i].y >= TETRIS_ROWS - 1)
{
canDown = false;
break;
}
// 下一格已经有方块
if(tetris_status[currentFall[i].y + 1][currentFall[i].x] != NO_BLOCK)
{
canDown = false;
break;
}
}
// 如果可以掉落
if(canDown)
{
// 首先将正在掉落的组合的每一格子涂成白色
for (var i = 0 ; i < currentFall.length ; i++)
{
//cur代表组合的每一个格子(0 1 2 3)
var cur = currentFall[i];
// 填充白色
tetris_ctx.fillStyle = 'white';
// 绘制矩形 fillRect(x ,y ,width, height)
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2 , CELL_SIZE - 2);
}
// 遍历组合每一个格子,下落一格,y坐标+1
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
cur.y ++;
}
// 下落后涂色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充颜色,colors[]在数据模型定义了数组
tetris_ctx.fillStyle = colors[cur.color];
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2 , CELL_SIZE - 2);
}
}
// 若不能下落
else
{
// ±éÀúÿ¸ö·½¿é, °Ñÿ¸ö·½¿éµÄÖµ¼Ç¼µ½tetris_statusÊý×éÖÐ
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 如果有方块已经到了最上面,表明输了
if(cur.y < 2)
{
localStorage.removeItem("curScore"); // 清空Local Storage当前积分值
localStorage.removeItem("tetris_status"); // 清空Local Storage当前游戏状态
localStorage.removeItem("curSpeed"); // 清空Local Storage当前速度
if(confirm("您已经输了,是否参与排名")) //confirm对话框提示游戏结束
{
// 读取Local Storage里面maxsScore记录
maxScore = localStorage.getItem("maxScore");
maxScore = maxScore == null ? 0 : maxScore ;
// 如果当前积分大于localStorage最高积分
if(curScore >= maxScore)
{
// 记录最高分
localStorage.setItem("maxScore" , curScore);
}
}
// 游戏结束
isPlaying = false;
// 清楚计时器——该控制器控制方块组合不断向下掉落
clearInterval(curTimer);
return;
}
// 还没有输,把把每个方块当前所在位置赋为当前方块颜色值
tetris_status[cur.y][cur.x] = cur.color;
}
lineFull(); // 判断是否有可消除的行
// 使用Local Storage记录游戏状态
localStorage.setItem("tetris_status" , JSON.stringify(tetris_status));
//开始新组块
initBlock();
}
}
//判断是否有一行已满
var lineFull =function()
{
// 遍历每一行
for (var i = 0; i < TETRIS_ROWS ; i++ )
{
var flag = true; // 用flag标志是否有行已经满了
// 遍历当前行每个单元格
for (var j = 0 ; j < TETRIS_COLS ; j++ )
{
if(tetris_status[i][j] == NO_BLOCK)
{
flag = false; //有空格,跳出该行循环
break;
}
}
// flag = true,该行已满
if(flag)
{
// 当前积分+100
curScoreEle.innerHTML = curScore+= 100;
// 记录当前积分
localStorage.setItem("curScore" , curScore);
// 如果当前积分到达升级的极限
if( curScore >= curSpeed * curSpeed * 500)
{
curSpeedEle.innerHTML = curSpeed += 1;
// 记录当前速度
localStorage.setItem("curSpeed" , curSpeed);
clearInterval(curTimer);
curTimer = setInterval("moveDown();" , 500 / curSpeed);
/*setInterval() 方法会不停地调用函数,
直到 clearInterval() 被调用或窗口被关闭。
由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。*/
}
// 当前行的所有方块下移一行
for (var k = i ; k > 0 ; k--)
{
for (var l = 0; l < TETRIS_COLS ; l++ )
{
tetris_status[k][l] =tetris_status[k-1][l];
}
}
// 重新绘制一遍方块
drawBlock();
}
}
}
// 绘制俄罗斯方块的状态
var drawBlock = function()
{
for (var i = 0; i < TETRIS_ROWS ; i++ )
{
for (var j = 0; j < TETRIS_COLS ; j++ )
{
// 有方块的地方绘制颜色
if(tetris_status[i][j] != NO_BLOCK)
{
// 填充颜色
tetris_ctx.fillStyle = colors[tetris_status[i][j]];
// 绘制矩形
tetris_ctx.fillRect(j * CELL_SIZE + 1
, i * CELL_SIZE + 1, CELL_SIZE - 2 , CELL_SIZE - 2);
}
// 没有方块地方绘制白色
else
{
// 填充颜色
tetris_ctx.fillStyle = 'white';
// 绘制矩形
tetris_ctx.fillRect(j * CELL_SIZE + 1
, i * CELL_SIZE + 1 , CELL_SIZE - 2 , CELL_SIZE - 2);
}
}
}
}
var moveLeft = function()
{
// 是否能左移标志
var canLeft = true;
for (var i = 0 ; i < currentFall.length ; i++)
{
// 如果到了最左边不能移动
if(currentFall[i].x <= 0)
{
canLeft = false;
break;
}
// 左边位置已经有方块
if (tetris_status[currentFall[i].y][currentFall[i].x - 1] != NO_BLOCK)
{
canLeft = false;
break;
}
}
// 如果能左移
if(canLeft)
{
// 左移前每个方块背景涂成白色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充颜色
tetris_ctx.fillStyle = 'white';
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE +1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2, CELL_SIZE - 2);
}
// 左移组合
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
cur.x --;
}
// 左移后方块涂成相应颜色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充颜色
tetris_ctx.fillStyle = colors[cur.color];
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1, CELL_SIZE - 2 , CELL_SIZE - 2);
}
}
}
// 右移功能
var moveRight = function()
{
// 是否能右移标志
var canRight = true;
for (var i = 0 ; i < currentFall.length ; i++)
{
// 到达最右边,不能右移
if(currentFall[i].x >= TETRIS_COLS - 1)
{
canRight = false;
break;
}
// 右边位置已经有方块,不能右移
if (tetris_status[currentFall[i].y][currentFall[i].x + 1] != NO_BLOCK)
{
canRight = false;
break;
}
}
// 如果能右移
if(canRight)
{
// 右移前每个方块的背景涂成白色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充白色
tetris_ctx.fillStyle = 'white';
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2 , CELL_SIZE - 2);
}
// 右移正在掉落的方块组合坐标
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
cur.x ++;
}
// ½«ÓÒÒƺóµÄÿ¸ö·½¿éµÄ±³¾°É«Í¿³É¸÷·½¿é¶ÔÓ¦µÄÑÕÉ«
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充方块
tetris_ctx.fillStyle = colors[cur.color];
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2, CELL_SIZE -2);
}
}
}
// 旋转方块函数
var rotate = function()
{
// 定义是否能旋转的标志
var canRotate = true;
for (var i = 0 ; i < currentFall.length ; i++)
{
var preX = currentFall[i].x;
var preY = currentFall[i].y;
// 始终以第三个方块作为旋转中心
// i == 2说明是旋转中心
if(i != 2)
{
// 计算旋转后方块坐标
var afterRotateX = currentFall[2].x + preY - currentFall[2].y;
var afterRotateY = currentFall[2].y + currentFall[2].x - preX;
// 如果旋转后的位置已经有方块,不能旋转
if(tetris_status[afterRotateY][afterRotateX + 1] != NO_BLOCK)
{
canRotate = false;
break;
}
// 旋转后坐标超出最左边界
if(afterRotateX < 0 || tetris_status[afterRotateY - 1][afterRotateX] != NO_BLOCK)
{
moveRight();
afterRotateX = currentFall[2].x + preY - currentFall[2].y;
afterRotateY = currentFall[2].y + currentFall[2].x - preX;
break;
}
if(afterRotateX < 0 || tetris_status[afterRotateY-1][afterRotateX] != NO_BLOCK)
{
moveRight();
break;
}
// 旋转后坐标超出最右边界
if(afterRotateX >= TETRIS_COLS - 1 ||
tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)
{
moveLeft();
afterRotateX = currentFall[2].x + preY - currentFall[2].y;
afterRotateY = currentFall[2].y + currentFall[2].x - preX;
break;
}
if(afterRotateX >= TETRIS_COLS - 1 ||
tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)
{
moveLeft();
break;
}
}
}
// 能旋转
if(canRotate)
{
// 旋转前背景涂成白色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 设置填充颜色
tetris_ctx.fillStyle = 'white';
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2, CELL_SIZE - 2);
}
for (var i = 0 ; i < currentFall.length ; i++)
{
var preX = currentFall[i].x;
var preY = currentFall[i].y;
// 始终以第三个方块作为旋转中心
// i == 2说明是旋转中心
if(i != 2)
{
currentFall[i].x = currentFall[2].x +
preY - currentFall[2].y;
currentFall[i].y = currentFall[2].y +
currentFall[2].x - preX;
}
}
// 旋转后每个方块的背景涂成各方块对应的颜色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充颜色
tetris_ctx.fillStyle = colors[cur.color];
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2, CELL_SIZE - 2);
}
}
}
四、给键盘事件绑定事件监听器
window.focus();
// 为窗口的按键事件绑定事件监听器
window.onkeydown = function(evt)
{
//evt.keyCode 是指触发这个键盘事件的键盘码
switch(evt.keyCode)
{
// 按了“向下”箭头
case 40:
if(!isPlaying)
return;
moveDown();
break;
// 按了“向左”箭头
case 37:
if(!isPlaying)
return;
moveLeft();
break;
// 按了“向右”箭头
case 39:
if(!isPlaying)
return;
moveRight();
break;
// 按了“向上”箭头
case 38:
if(!isPlaying)
return;
rotate();
break;
}
}
五、初始化游戏状态
//初始化游戏状态
window.onload = function()
{
//创建canvas组件
createCanvas(TETRIS_ROWS , TETRIS_COLS , CELL_SIZE , CELL_SIZE);
document.body.appendChild(tetris_canvas);
curScoreEle = document.getElementById("curScoreEle");
curSpeedEle = document.getElementById("curSpeedEle");
maxScoreEle = document.getElementById("maxScoreEle");
// 读取Local Storage里的tetris_status记录
var tmpStatus = localStorage.getItem("tetris_status");
tetris_status = tmpStatus == null ? tetris_status : JSON.parse(tmpStatus);
// 把方块绘制出来
drawBlock();
// 读取Local Storage里的curScore记录
curScore = localStorage.getItem("curScore");
curScore = curScore == null ? 0 : parseInt(curScore);
curScoreEle.innerHTML = curScore;
// 读取Local Storage里的maxScore记录
maxScore = localStorage.getItem("maxScore");
maxScore = maxScore == null ? 0 : parseInt(maxScore);
maxScoreEle.innerHTML = maxScore;
// 读取Local Storage里的curSpeed记录
curSpeed = localStorage.getItem("curSpeed");
curSpeed = curSpeed == null ? 1 : parseInt(curSpeed);
curSpeedEle.innerHTML = curSpeed;
// 初始化正在掉落的方块
initBlock();
// 控制每隔一段时间执行一次“下落”
curTimer = setInterval("moveDown();" , 500 / curSpeed);
}
从上面代码可以看出,俄罗斯方块游戏开始时需要完成如下事情:
1、 调用createCanvas创建canvas组件
2、 读取Local Storage 记录的已有方块的状态
3、 读取Local Storage 记录的当前积分数据
4、 读取Local Storage 记录的当前速度数据
5、 读取Local Storage 记录的最高积分数据
6、 初始化正在掉落的方块组合
7、 启动计时器,控制方块掉落
※js完整代码
var TETRIS_ROWS = 20; //20行
var TETRIS_COLS = 14; //14列
var CELL_SIZE = 25; //一个格子大小
var tetris_canvas; //canvas元素的tetris_canvas对象,在createCanvas
var tetris_ctx; //CanvasRenderingContext2D对象,在createCanvas
var curScore = 0; //当前积分值
var curSpeed = 1; //当前速度
var maxScore = 0; //最大积分值
var curScoreEle , curSpeedEle , maxScoreEle;
//引用拥有制定ID的第一个对象,在window.onload
var curTimer;//setInterval() 返回的ID值,curTimer = setInterval("moveDown();" , 500 / curSpeed);
var isPlaying = true; //判断游戏是否结束
var NO_BLOCK = 0;
var currentFall;
colors = ["#fff", "#f00" , "#0f0" , "#00f"
, "#c60" , "#f0f" , "#0ff" , "#609"];
//该数组用于记录底下已经固定的方块,初始化为没有方块
var tetris_status=[];
for (var i = 0; i < TETRIS_ROWS; i++)
{
tetris_status[i]=[];
for (var j = 0; j < TETRIS_COLS; j++)
{
tetris_status[i][j] = NO_BLOCK;
}
}
//定义几种可能出现的方块组合,相当于一个二维数组blockArr[7][4]
var blockArr = [
[ {x:TETRIS_COLS/2-1,y:0,color:1}, //1. Z形方块组合
{x:TETRIS_COLS/2,y:0,color:1},
{x:TETRIS_COLS/2,y:1,color:1},
{x:TETRIS_COLS/2+1,y:1,color:1}
],
[ {x:TETRIS_COLS/2+1,y:0,color:2}, //2. 反Z形方块组合
{x:TETRIS_COLS/2,y:0,color:2},
{x:TETRIS_COLS/2,y:1,color:2},
{x:TETRIS_COLS/2-1,y:1,color:2}
],
[ {x:TETRIS_COLS/2-1,y:0,color:3}, //3. 田形方块组合
{x:TETRIS_COLS/2,y:0,color:3},
{x:TETRIS_COLS/2-1,y:1,color:3},
{x:TETRIS_COLS/2,y:1,color:3}
],
[ {x:TETRIS_COLS/2,y:0,color:4}, //4. L形方块组合
{x:TETRIS_COLS/2,y:1,color:4},
{x:TETRIS_COLS/2,y:2,color:4},
{x:TETRIS_COLS/2+1,y:2,color:4}
],
[ {x:TETRIS_COLS/2,y:0,color:5}, //5. J形方块组合
{x:TETRIS_COLS/2,y:1,color:5},
{x:TETRIS_COLS/2,y:2,color:5},
{x:TETRIS_COLS/2-1,y:2,color:5}
],
[ {x:TETRIS_COLS/2,y:0,color:6}, //6. 竖形方块组合
{x:TETRIS_COLS/2,y:1,color:6},
{x:TETRIS_COLS/2,y:2,color:6},
{x:TETRIS_COLS/2,y:3,color:6}
],
[ {x:TETRIS_COLS/2,y:0,color:7}, //7. 凸形方块组合
{x:TETRIS_COLS/2-1,y:1,color:7},
{x:TETRIS_COLS/2,y:1,color:7},
{x:TETRIS_COLS/2+1,y:1,color:7}
]
]
//定义正在掉落的方块
var initBlock =function()
{
//随机生成正在掉落的方块(0-6)
//math.floor(x)返回小于参数x的最大整数,即对浮点数向下取整.
//Math.random()是令系统随机选取大于等于 0.0 且小于 1.0 的伪随机 double 值
var rand = Math.floor (Math.random() * blockArr.length);
//第rand+1种方块组合,每种组合四个方块组成,
currentFall = [
{x:blockArr[rand][0].x, y: blockArr[rand][0].y, color: blockArr[rand][0].color},
{x:blockArr[rand][1].x, y: blockArr[rand][1].y, color: blockArr[rand][1].color},
{x:blockArr[rand][2].x, y: blockArr[rand][2].y, color: blockArr[rand][2].color},
{x:blockArr[rand][3].x, y: blockArr[rand][3].y, color: blockArr[rand][3].color}
];
}
//注意:tetris_status[y][x],因为二维数组竖直方向为i(行数)=纵坐标y,横向为j(列数)=横坐标x
var createCanvas = function(rows ,cols ,cellWidth ,cellHeight) //定义了一个函数,需要四个参数
{
//新建canvas元素的tetris_canvas对象
//document.createElement()与document.appendChild()联用
//tetris_canvas = document.getElementById("canvas");这种方法刷新页面不能继续游戏而是重新开始游戏
tetris_canvas = document.createElement("canvas");
//设置canvas组件的高度、宽度
tetris_canvas.width = cols * cellWidth;
tetris_canvas.height = rows * cellHeight;
//设置组件边框
tetris_canvas.style.border="1px solid black";
//获取canvas上的绘图API
//获取canvas上绘图的CanvasRenderingContext2D对象
tetris_ctx=tetris_canvas.getContext('2d');
//开始创建路径
tetris_ctx.beginPath();
//绘制横向网格对应的路径,画网格里面的横线
for (var i=1; i < TETRIS_ROWS ; i++)
{
tetris_ctx.moveTo(0 , i * CELL_SIZE);
//把当前路径结束点移动到(0 , i * CELL_SIZE),比如刚开始(0,a)
tetris_ctx.lineTo(TETRIS_COLS * CELL_SIZE , i * CELL_SIZE);
//从当前结束点连接到(TETRIS_COLS * CELL_SIZE , i * CELL_SIZE),比如从(0,0)到(14*a,0);(0,a)-(14a,a)
}
//绘制竖向网格对应的路径
for (var i=1; i < TETRIS_COLS ; i++)
{
tetris_ctx.moveTo(i * CELL_SIZE , 0);
tetris_ctx.lineTo(i * CELL_SIZE , TETRIS_ROWS * CELL_SIZE);
}
tetris_ctx.closePath();//关闭前面定义的路径
//设置笔触颜色,绘制路径填充风格,当前为纯色;fillStyle是填充路径填充风格
tetris_ctx.strokeStyle="#314478";
//设置线条粗细
tetris_ctx.lineWidth=1;
//绘制线条
tetris_ctx.stroke();
}
//判断是否有一行已满
var lineFull =function()
{
// 遍历每一行
for (var i = 0; i < TETRIS_ROWS ; i++ )
{
var flag = true; // 用flag标志是否有行已经满了
// 遍历当前行每个单元格
for (var j = 0 ; j < TETRIS_COLS ; j++ )
{
if(tetris_status[i][j] == NO_BLOCK)
{
flag = false; //有空格,跳出该行循环
break;
}
}
// flag = true,该行已满
if(flag)
{
// 当前积分+100
curScoreEle.innerHTML = curScore+= 100;
// 记录当前积分
localStorage.setItem("curScore" , curScore);
// 如果当前积分到达升级的极限
if( curScore >= curSpeed * curSpeed * 500)
{
curSpeedEle.innerHTML = curSpeed += 1;
// 记录当前速度
localStorage.setItem("curSpeed" , curSpeed);
clearInterval(curTimer);
curTimer = setInterval("moveDown();" , 500 / curSpeed);
//etInterval() 方法会不停地调用函数,
//直到 clearInterval() 被调用或窗口被关闭。
//由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
}
// 当前行的所有方块下移一行
for (var k = i ; k > 0 ; k--)
{
for (var l = 0; l < TETRIS_COLS ; l++ )
{
tetris_status[k][l] =tetris_status[k-1][l];
}
}
// 重新绘制一遍方块
drawBlock();
}
}
}
//处理掉落方块组合
var moveDown = function()
{
// 判断是否能掉落的标志
var canDown = true;
// 遍历正在掉落的组合的每一个方块,判断是否能掉落
for (var i = 0 ; i < currentFall.length ; i++)
{
// 已经到了底部
if(currentFall[i].y >= TETRIS_ROWS - 1)
{
canDown = false;
break;
}
// 下一格已经有方块
if(tetris_status[currentFall[i].y + 1][currentFall[i].x] != NO_BLOCK)
{
canDown = false;
break;
}
}
// 如果可以掉落
if(canDown)
{
// 首先将正在掉落的组合的每一格子涂成白色
for (var i = 0 ; i < currentFall.length ; i++)
{
//cur代表组合的每一个格子(0 1 2 3)
var cur = currentFall[i];
// 填充白色
tetris_ctx.fillStyle = 'white';
// 绘制矩形 fillRect(x ,y ,width, height)
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2 , CELL_SIZE - 2);
}
// 遍历组合每一个格子,下落一格,y坐标+1
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
cur.y ++;
}
// 下落后涂色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充颜色,colors[]在数据模型定义了数组
tetris_ctx.fillStyle = colors[cur.color];
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2 , CELL_SIZE - 2);
}
}
// 若不能下落
else
{
// ±éÀúÿ¸ö·½¿é, °Ñÿ¸ö·½¿éµÄÖµ¼Ç¼µ½tetris_statusÊý×éÖÐ
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 如果有方块已经到了最上面,表明输了
if(cur.y < 2)
{
localStorage.removeItem("curScore"); // 清空Local Storage当前积分值
localStorage.removeItem("tetris_status"); // 清空Local Storage当前游戏状态
localStorage.removeItem("curSpeed"); // 清空Local Storage当前速度
if(confirm("您已经输了,是否参与排名")) //confirm对话框提示游戏结束
{
// 读取Local Storage里面maxsScore记录
maxScore = localStorage.getItem("maxScore");
maxScore = maxScore == null ? 0 : maxScore ;
// 如果当前积分大于localStorage最高积分
if(curScore >= maxScore)
{
// 记录最高分
localStorage.setItem("maxScore" , curScore);
}
}
// 游戏结束
isPlaying = false;
// 清楚计时器——该控制器控制方块组合不断向下掉落
clearInterval(curTimer);
return;
}
// 还没有输,把把每个方块当前所在位置赋为当前方块颜色值
tetris_status[cur.y][cur.x] = cur.color;
}
lineFull(); // 判断是否有可消除的行
// 使用Local Storage记录游戏状态
localStorage.setItem("tetris_status" , JSON.stringify(tetris_status));
//开始新组块
initBlock();
}
}
// 绘制俄罗斯方块的状态
var drawBlock = function()
{
for (var i = 0; i < TETRIS_ROWS ; i++ )
{
for (var j = 0; j < TETRIS_COLS ; j++ )
{
// 有方块的地方绘制颜色
if(tetris_status[i][j] != NO_BLOCK)
{
// 填充颜色
tetris_ctx.fillStyle = colors[tetris_status[i][j]];
// 绘制矩形
tetris_ctx.fillRect(j * CELL_SIZE + 1
, i * CELL_SIZE + 1, CELL_SIZE - 2 , CELL_SIZE - 2);
}
// 没有方块地方绘制白色
else
{
// 填充颜色
tetris_ctx.fillStyle = 'white';
// 绘制矩形
tetris_ctx.fillRect(j * CELL_SIZE + 1
, i * CELL_SIZE + 1 , CELL_SIZE - 2 , CELL_SIZE - 2);
}
}
}
}
// 左移方块函数
var moveLeft = function()
{
// 是否能左移标志
var canLeft = true;
for (var i = 0 ; i < currentFall.length ; i++)
{
// 如果到了最左边不能移动
if(currentFall[i].x <= 0)
{
canLeft = false;
break;
}
// 左边位置已经有方块
if (tetris_status[currentFall[i].y][currentFall[i].x - 1] != NO_BLOCK)
{
canLeft = false;
break;
}
}
// 如果能左移
if(canLeft)
{
// 左移前每个方块背景涂成白色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充颜色
tetris_ctx.fillStyle = 'white';
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE +1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2, CELL_SIZE - 2);
}
// 左移组合
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
cur.x --;
}
// 左移后方块涂成相应颜色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充颜色
tetris_ctx.fillStyle = colors[cur.color];
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1, CELL_SIZE - 2 , CELL_SIZE - 2);
}
}
}
// 右移方块函数
var moveRight = function()
{
// 是否能右移标志
var canRight = true;
for (var i = 0 ; i < currentFall.length ; i++)
{
// 到达最右边,不能右移
if(currentFall[i].x >= TETRIS_COLS - 1)
{
canRight = false;
break;
}
// 右边位置已经有方块,不能右移
if (tetris_status[currentFall[i].y][currentFall[i].x + 1] != NO_BLOCK)
{
canRight = false;
break;
}
}
// 如果能右移
if(canRight)
{
// 右移前每个方块的背景涂成白色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充白色
tetris_ctx.fillStyle = 'white';
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2 , CELL_SIZE - 2);
}
// 右移正在掉落的方块组合坐标
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
cur.x ++;
}
// ½«ÓÒÒƺóµÄÿ¸ö·½¿éµÄ±³¾°É«Í¿³É¸÷·½¿é¶ÔÓ¦µÄÑÕÉ«
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充方块
tetris_ctx.fillStyle = colors[cur.color];
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2, CELL_SIZE -2);
}
}
}
// 旋转方块函数
var rotate = function()
{
// 定义是否能旋转的标志
var canRotate = true;
for (var i = 0 ; i < currentFall.length ; i++)
{
var preX = currentFall[i].x;
var preY = currentFall[i].y;
// 始终以第三个方块作为旋转中心
// i == 2说明是旋转中心
if(i != 2)
{
// 计算旋转后方块坐标
var afterRotateX = currentFall[2].x + preY - currentFall[2].y;
var afterRotateY = currentFall[2].y + currentFall[2].x - preX;
// 如果旋转后的位置已经有方块,不能旋转
if(tetris_status[afterRotateY][afterRotateX + 1] != NO_BLOCK)
{
canRotate = false;
break;
}
// 旋转后坐标超出最左边界
if(afterRotateX < 0 || tetris_status[afterRotateY - 1][afterRotateX] != NO_BLOCK)
{
moveRight();
afterRotateX = currentFall[2].x + preY - currentFall[2].y;
afterRotateY = currentFall[2].y + currentFall[2].x - preX;
break;
}
if(afterRotateX < 0 || tetris_status[afterRotateY-1][afterRotateX] != NO_BLOCK)
{
moveRight();
break;
}
// 旋转后坐标超出最右边界
if(afterRotateX >= TETRIS_COLS - 1 ||
tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)
{
moveLeft();
afterRotateX = currentFall[2].x + preY - currentFall[2].y;
afterRotateY = currentFall[2].y + currentFall[2].x - preX;
break;
}
if(afterRotateX >= TETRIS_COLS - 1 ||
tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)
{
moveLeft();
break;
}
}
}
// 能旋转
if(canRotate)
{
// 旋转前背景涂成白色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 设置填充颜色
tetris_ctx.fillStyle = 'white';
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2, CELL_SIZE - 2);
}
for (var i = 0 ; i < currentFall.length ; i++)
{
var preX = currentFall[i].x;
var preY = currentFall[i].y;
// 始终以第三个方块作为旋转中心
// i == 2说明是旋转中心
if(i != 2)
{
currentFall[i].x = currentFall[2].x +
preY - currentFall[2].y;
currentFall[i].y = currentFall[2].y +
currentFall[2].x - preX;
}
}
// 旋转后每个方块的背景涂成各方块对应的颜色
for (var i = 0 ; i < currentFall.length ; i++)
{
var cur = currentFall[i];
// 填充颜色
tetris_ctx.fillStyle = colors[cur.color];
// 绘制矩形
tetris_ctx.fillRect(cur.x * CELL_SIZE + 1
, cur.y * CELL_SIZE + 1 , CELL_SIZE - 2, CELL_SIZE - 2);
}
}
}
//窗口获取焦点(玩网页游戏需要在页面点击一下)
window.focus();
// 为窗口的按键事件绑定事件监听器
window.onkeydown = function(evt)
{
//evt.keyCode 是指触发这个键盘事件的键盘码
switch(evt.keyCode)
{
// 按了“向下”箭头
case 40:
if(!isPlaying)
return;
moveDown();
break;
// 按了“向左”箭头
case 37:
if(!isPlaying)
return;
moveLeft();
break;
// 按了“向右”箭头
case 39:
if(!isPlaying)
return;
moveRight();
break;
// 按了“向上”箭头
case 38:
if(!isPlaying)
return;
rotate();
break;
}
}
//初始化游戏状态
window.onload = function()
{
//创建canvas组件
createCanvas(TETRIS_ROWS , TETRIS_COLS , CELL_SIZE , CELL_SIZE);
document.body.appendChild(tetris_canvas);
//getElementById() 方法可返回对拥有指定 ID 的第一个对象的引用。
curScoreEle = document.getElementById("curScoreEle");
curSpeedEle = document.getElementById("curSpeedEle");
maxScoreEle = document.getElementById("maxScoreEle");
// 读取Local Storage里的tetris_status记录
var tmpStatus = localStorage.getItem("tetris_status");
tetris_status = tmpStatus == null ? tetris_status : JSON.parse(tmpStatus);
// 把方块绘制出来
drawBlock();
// 读取Local Storage里的curScore记录
curScore = localStorage.getItem("curScore");
curScore = curScore == null ? 0 : parseInt(curScore);
curScoreEle.innerHTML = curScore;
// 读取Local Storage里的maxScore记录
maxScore = localStorage.getItem("maxScore");
maxScore = maxScore == null ? 0 : parseInt(maxScore);
maxScoreEle.innerHTML = maxScore;
// 读取Local Storage里的curSpeed记录
curSpeed = localStorage.getItem("curSpeed");
curSpeed = curSpeed == null ? 1 : parseInt(curSpeed);
curSpeedEle.innerHTML = curSpeed;
// 初始化正在掉落的方块
initBlock();
// 控制每隔一段时间执行一次“下落”
curTimer = setInterval("moveDown();" , 500 / curSpeed);
}