canvas版《俄罗斯方块》
试玩(没有考虑兼容低版本浏览器):
See the Pen Canvas俄罗斯方块 by 王美建 (@wangmeijian) on CodePen.
**********************************************************************
9月3日更新:
修复了隐藏的比较深的BUG
加上暂停、再来一次功能
速度随分数增高而递减
添加log日志
*********************************************************************
通过写这个游戏收获几点:
1、canvas的isPointInPath方法不支持fillRect、strokeRect的上下文。
2、canvas有内边距(padding)时,传入isPointInPath方法的坐标需要减去padding值。
3、通过json的方法JSON.parse(JSON.stringify(Object))可以快速克隆Object对象,但是要注意:当Object对象里有方法时,克隆过来依然是引用关系。
源码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>canvas版俄罗斯方块</title>
<style>
#canvas{
background: #272822;
display: block;
margin: 0 auto;
}
</style>
</head>
<body>
<p style="text-align: center;">操作:↑变形;↓下移;←左移;→右移</p>
<canvas id="canvas" width="640" height="600">
您的浏览器不支持canvas!
</canvas>
<script>
/****************************
作者:王美建 2015年9月3日20:30:35
*后续可添加怪异变形,类似于L可变成Z
*积分随关卡递增
*初始化部分historyBlock
****************************/
var tetris = {
canvas : document.getElementById("canvas"),
ctx : this.canvas.getContext("2d"),
width : 481,
height : 600,
logLen: 8,
mapColor: "#464741",
logColor: "green",
status: 'ready',
unit : 30,
curText : "开始",
blockData : function(index, row, col){
var r = row || 1,
c = col || Math.floor((this.col - 3)/2) + 2,
block = [
[
{color: 'red', status: 1, data: [{x: r, y:c-1}, {x: r+1, y:c-1}, {x: r+1, y:c}, {x: r+1, y:c+1}], center: {x: r, y: c}},
{color: 'red', status: 2, data: [{x: r-1, y:c-1}, {x: r-1, y:c}, {x: r, y:c-1}, {x: r+1, y:c-1}], center: {x: r, y: c}},
{color: 'red', status: 3, data: [{x: r-1, y:c-1}, {x: r-1, y:c}, {x: r-1, y:c+1}, {x: r, y:c+1}], center: {x: r, y: c}},
{color: 'red', status: 4, data: [{x: r-1, y:c+1}, {x: r, y:c+1}, {x: r+1, y:c+1}, {x: r+1, y:c}], center: {x: r, y: c}}
],
[
{color: 'green', status: 1, center: {x: r, y:c}, data: [{x: r, y:c+1}, {x: r+1, y:c-1}, {x: r+1, y:c}, {x: r+1, y:c+1}]},
{color: 'green', status: 2, center: {x: r, y:c}, data: [{x: r-1, y:c-1}, {x: r, y:c-1}, {x: r+1, y:c-1}, {x: r+1, y:c}]},
{color: 'green', status: 3, center: {x: r, y:c}, data: [{x: r, y:c-1}, {x: r-1, y:c-1}, {x: r-1, y:c}, {x: r-1, y:c+1}]},
{color: 'green', status: 4, center: {x: r, y:c}, data: [{x: r-1, y:c}, {x: r-1, y:c+1}, {x: r, y:c+1}, {x: r+1, y:c+1}]}
],
[
{color: 'blue', status: 1, center: {x: r, y:c}, data: [{x: r, y:c-1}, {x: r, y:c}, {x: r+1, y:c}, {x: r+1, y:c+1}]},
{color: 'blue', status: 2, center: {x: r, y:c}, data: [{x: r+1, y:c-1}, {x: r, y:c-1}, {x: r, y:c}, {x: r-1, y:c}]}
],
[
{color: 'orange', status: 1, center: {x: r, y:c}, data: [{x: r+1, y:c-1}, {x: r+1, y:c}, {x: r, y:c}, {x: r, y:c+1}]},
{color: 'orange', status: 2, center: {x: r, y:c}, data: [{x: r-1, y:c}, {x: r, y:c}, {x: r, y:c+1}, {x: r+1, y:c+1}]}
],
[
{color: 'yellow', status: 1, center: {x: r, y:c}, data: [{x: r, y:c-1}, {x: r, y:c}, {x: r+1, y:c-1}, {x: r+1, y:c}]}
],
[
{color: 'aqua', status: 1, center: {x: r, y:c}, data: [{x: r, y:c-1}, {x: r, y:c}, {x: r, y:c+1}, {x: r-1, y:c}]},
{color: 'aqua', status: 2, center: {x: r, y:c}, data: [{x: r+1, y:c}, {x: r, y:c}, {x: r, y:c+1}, {x: r-1, y:c}]},
{color: 'aqua', status: 3, center: {x: r, y:c}, data: [{x: r, y:c-1}, {x: r, y:c}, {x: r, y:c+1}, {x: r+1, y:c}]},
{color: 'aqua', status: 4, center: {x: r, y:c}, data: [{x: r, y:c-1}, {x: r, y:c}, {x: r+1, y:c}, {x: r-1, y:c}]}
],
[
{color: 'indigo', status: 1, center: {x: r, y:c}, data: [{x: r, y:c-1}, {x: r, y:c}, {x: r, y:c+1}, {x: r, y:c+2}]},
{color: 'indigo', status: 2, center: {x: r, y:c}, data: [{x: r-2, y:c}, {x: r-1, y:c}, {x: r, y:c}, {x: r+1, y:c}]}
]
]
return block[index];
},
init : function(){
var self = this;
self.reset();
self.addEvent("keydown", window, function(ev){
var ev = ev || window.event,
code = ev.keycode || ev.which;
if(self.handle[code] && self.status === "play"){
self.handle[code].call(self);
self.createMap();
ev.preventDefault();
}
})
self.addEvent("click", document, function(ev){
self.createMap(ev);
})
return this;
},
reset: function(){
var self = this;
self.score = 0;
self.speed = 1000;
self.log = [];
self.historyBlock = [];
self.row = Math.floor(self.height/self.unit);
self.col = Math.floor(self.width/self.unit);
self.curBlockIndex = Math.round(Math.random()*6);
self.curBlocks = self.blockData(self.curBlockIndex);
self.curBlock = self.curBlocks[0];
self.createNext().createMap();
return this;
},
createNext: function(){
var self = this;
self.nextBlockIndex = self.status === "ready" ? self.curBlockIndex : Math.round(Math.random()*6);
self.nextBlocks = self.blockData(self.nextBlockIndex, 4, self.col+3);
self.nextBlock = self.nextBlocks[0];
return this;
},
addEvent : function(ev, ele, callback){
if( ele.addEventListener ){
ele.addEventListener(ev,callback,false);
}else{
ele.attachEvent("on"+ev, callback);
}
},
createMap : function(ev){
var self = this,
ctx = self.ctx;
ctx.clearRect(0, 0, self.canvas.width, self.canvas.height);
for (var i = 0; i < self.col; i++) {
for (var j = 0; j < self.row; j++) {
ctx.save();
ctx.strokeStyle = self.mapColor;
ctx.strokeRect(i*self.unit, j*self.unit, self.unit, self.unit);
ctx.stroke();
ctx.restore();
};
};
self.showText(ev).createBlock().createLog();
if(self.status !== "ready"){
self.drawRect(self.curBlock.data);
}
return this;
},
createBlock : function(){
var self = this,
block = self.curBlock.data;
self.drawRect(self.historyBlock);
if(self.collide(40, true)){
block.map(function(val){
val.x--;
})
setTimeout(function(){
clearInterval(self.timer);
if(localStorage.getItem("score") === null){
localStorage.setItem("score", self.score);
}else if(localStorage.getItem("score") - self.score < 0 ){
localStorage.setItem("score", self.score);
alert("新纪录!"+self.score+"分!");
self.printLog({log:"新纪录!"+self.score+"分!", color: 'red'});
return;
}
self.status = "over";
self.curText = "重来";
self.showText();
self.printLog({log:"GAME OVER", color: 'red'});
},10)
}
return this;
},
drawRect : function(block){
var self = this;
for (var i = 0; i < block.length; i++) {
self.ctx.save();
self.ctx.fillStyle = block[i].color || self.curBlock.color;
self.ctx.strokeStyle = self.mapColor;
self.ctx.fillRect((block[i].y - 1)*self.unit, (block[i].x - 1)*self.unit, self.unit, self.unit );
self.ctx.strokeRect((block[i].y - 1)*self.unit, (block[i].x - 1)*self.unit, self.unit, self.unit );
self.ctx.restore();
};
},
move : function(){
var self = this;
clearInterval(self.timer);
self.timer = setInterval(function(){
// 实时刷新数据 大坑!
var curBlock = self.curBlock,
data = self.curBlock.data;
if( self.collide() || data.some(function(val){
return val.x + 1 > self.row;
}) ){
clearInterval(self.timer);
self.historyBlock.push.apply(self.historyBlock, data.map(function(val){
val.color = curBlock.color;
return val;
}));
self.remove();
self.curBlockIndex = self.nextBlockIndex;
self.curBlocks = self.blockData(self.curBlockIndex);
self.curBlock = self.curBlocks[0];
self.createNext().createMap().move(); return false;
}
for (var i = 0; i < data.length; i++) {
data[i].x++;
};
self.curBlock.center.x++;
self.createMap();
}, self.speed)
return this;
},
remove : function(){
var self = this,
count = {},
n = 0,
maxRow = 0,
delArr = [],
block = self.historyBlock;
for (var i = 0; i < block.length; i++) {
if(count[block[i].x]){
count[block[i].x].length += 1;
}else{
count[block[i].x] = [1];
}
};
console.log( count )
for (var attr in count) {
if(count[attr].length === self.col){
n++;
//maxRow = attr > maxRow ? attr : maxRow;
for (var i = 0; i < block.length; i++) {
if(block[i].x == attr){
delArr = block.splice(i, 1);
i--;
}
};
count[attr].length = 0;
block.forEach(function(val){
val.x < attr && (val.x += 1);
})
}
};
// 边消除边下降会死循环
if(n > 0){
self.score += n*100;
self.printLog("得分+"+n*100);
// 一次消除3行奖励100分
if(n === 3){
self.score += 100;
self.printLog("奖励"+100+"分~");
}
// 一次消除4行奖励200分
if(n === 4){
self.score += 200;
self.printLog("奖励"+200+"分~");
}
/*block.forEach(function(val){
val.x < maxRow && (val.x += n);
})*/
self.changeSpeed();
}
},
changeSpeed: function(){
var self = this;
if( self.score >= 3000 && self.score < 5000 ){
self.speed = 800;
}else if( self.score >= 5000 && self.score < 10000 ){
self.speed = 600;
self.score += 100;
}else if( self.score >= 10000 && self.score < 20000 ){
self.speed = 400;
self.score += 150;
}else if( self.score >= 20000 && self.score < 40000 ){
self.speed = 200;
self.score += 200;
}else if( self.score >= 40000 ){
self.speed = 100;
self.score += 300;
}
return this;
},
collide : function(direction, isCreate){
var block = JSON.parse(JSON.stringify(this.curBlock)),
result = false,
self = this;
direction = direction || 40;
// direction:碰撞方向,默认下方
if(direction === 37){
self.mLeft(block);
}else if(direction === 38){
block = self.distortion(block);
}else if(direction === 39){
self.mRight(block);
}else if(direction === 40 && !isCreate){
// 非新增方块则往下移动一格
block.data.forEach(function(val){
val.x++;
})
}
result = block.data.some(function(val){
return (val.x > self.row || val.y < 1 || val.y > self.col);
})
if(result){
return result;
}else{
return block.data.some(function(val){
return self.historyBlock.some(function(value){
return (value.x === val.x && value.y === val.y);
})
})
}
},
mLeft : function(block){
if(block.data.every(function(val){
return val.y - 1 >= 1;
})){
block.data.forEach(function(val){
val.y--;
})
block.center.y--;
}
},
mRight : function(block){
var self = this;
if(block.data.every(function(val){
return val.y + 1 <= self.col;
})){
block.data.forEach(function(val){
val.y++;
})
block.center.y++;
}
},
distortion : function(block){
var self = this,
curRow = block.center.x,
curCol = block.center.y,
status = block.status + 1 > self.curBlocks.length ? 1 : block.status + 1;
self.curBlocks = self.blockData(self.curBlockIndex, block.center.x, block.center.y);
return self.curBlocks[status-1];
},
// 控制:上(变形)、右(右移)、下(下移)、左(左移)
handle : {
// 左键 code 37
37: function(){
var self = this;
if(!self.collide(37)){
self.mLeft(self.curBlock);
}
},
// 上键 code 38
38: function(){
var self = this;
if(!self.collide(38)){
self.curBlock = self.distortion(self.curBlock);
}
},
// 右键 code 39
39: function(){
var self = this;
if(!self.collide(39)){
self.mRight(self.curBlock);
}
},
// 下键 code 40
40: function(){
var self = this;
if(!self.collide()){
self.curBlock.data.forEach(function(val){
val.x++;
})
self.curBlock.center.x++;
}
}
},
showText: function(ev){
var self = this,
ctx = self.ctx;
ctx.clearRect(self.width, 0, self.canvas.width - self.width, self.height);
ctx.save();
ctx.beginPath();
ctx.font = "20px Verdana";
ctx.fillStyle = "green";
ctx.fillText("下一个:", self.width+10, 30);
ctx.fillText("分数:", self.width+10, 200);
ctx.fillText("速度:", self.width+10, 260);
ctx.fillText("作者:王美建", self.width+10, 580);
ctx.fillStyle = "red";
ctx.fillText(self.score, self.width+10, 230);
ctx.fillText(self.speed + "毫秒", self.width+10, 290);
ctx.fillStyle = "green";
ctx.fillRect(self.width + 30, 320, 100, 40);
// isPointInPath对fillRect()兼容不好 又一个大坑!
ctx.rect(self.width + 30, 320, 100, 40);
if( ev && ctx.isPointInPath(ev.layerX, ev.layerY) ){
switch(self.status){
case "ready":
self.status = "play";
self.curText = "暂停";
self.log = ["开始游戏."];
self.createNext().move();
break;
case "play":
self.status = "paush";
self.curText = "继续";
clearInterval(self.timer);
self.printLog("暂停.");
break;
case "paush":
self.status = "play";
self.curText = "暂停";
self.printLog("继续游戏~");
self.move();
break;
case "over":
self.status = "play";
self.curText = "暂停";
self.reset().move();
self.printLog("开始游戏~");
break;
}
}
ctx.fillStyle = "black";
ctx.fillText(self.curText, self.width+60, 350);
ctx.restore();
ctx.closePath(); self.nextBlock.data.forEach(function(val){
val.color = self.nextBlock.color;
})
self.drawRect(self.nextBlock.data);
return this;
},
printLog: function(log){
var self = this;
if(log){
self.log.unshift(log);
self.log.length > self.logLen && (self.log.length = self.logLen);
}
self.createLog();
return this;
},
createLog: function(){
var self = this,
ctx = self.ctx;
// 清除log
ctx.clearRect(self.width+10, 380, 136, 170);
self.log.forEach(function(val, index){
if(val){
ctx.save();
ctx.font = "16px Verdana";
ctx.fillStyle = val.color || self.logColor,
ctx.fillText(val.log || val, self.width+10, 400+index*22);
ctx.restore();
}
})
return this;
}
} tetris.init(); </script>
</body>
</html>
持续优化中……
作者:古德God
出处:http://www.cnblogs.com/wangmeijian
本文版权归作者和博客园所有,欢迎转载,转载请标明出处。
如果您觉得本篇博文对您有所收获,请点击右下角的 [推荐],谢谢!
canvas版《俄罗斯方块》的更多相关文章
- H5版俄罗斯方块(2)---游戏的基本框架和实现
前言: 上文中谈到了H5版俄罗斯方块的需求和目标, 这次要实现一个可玩的版本. 但饭要一口一口吃, 很多东西并非一蹴而就. 本文将简单实现一个可玩的俄罗斯方块版本. 下一步会引入AI, 最终采用coc ...
- Android版俄罗斯方块的实现
学习Android的基本开发也有一段时间了,可是由于没有常常使用Android渐渐的也就忘记了. Android编程学的不深,不过为了对付逆向,可是有时还是会感到力不从心的.毕竟不是一个计算机专业毕业 ...
- H5版俄罗斯方块(4)---火拼对战的雏形
前言: 勿忘初心, 本系列的目标是实现一款类似QQ"火拼系列"的人机对战版俄罗斯方块. 在完成了基本游戏框架和AI的算法探索后, 让我们来尝试一下人机大战雏形编写. 本系列的文章链 ...
- canvas实现俄罗斯方块
好久没使用canvas了,于是通过写小游戏"俄罗斯方块"再次熟悉下canvas,如果有一定的canvas基础,要实现还是不难的.实际完成的Demo请看:canvas俄罗斯方块 . ...
- H5版俄罗斯方块(1)---需求分析和目标创新
前言: 俄罗斯方块和五子棋一样, 规则简单, 上手容易. 几乎每个开发者, 都会在其青春年华时, 签下"xx到此一游". 犹记得大一老师在布置大程作业的时候提过: "什么 ...
- H5版俄罗斯方块(3)---游戏的AI算法
前言: 算是"long long ago"的事了, 某著名互联网公司在我校举行了一次"lengend code"的比赛, 其中有一题就是"智能俄罗斯方 ...
- 基于 HTML5 的 WebGL 3D 版俄罗斯方块
前言 摘要:2D 的俄罗斯方块已经被人玩烂了,突发奇想就做了个 3D 的游戏机,用来玩俄罗斯方块...实现的基本想法是先在 2D 上实现俄罗斯方块小游戏,然后使用 3D 建模功能创建一个 3D 街机模 ...
- Delphi版俄罗斯方块-前奏
前言 基础知识讲了很多,但是并没有串联起来,所以我最近一直在准备个小项目,但是这个项目的要求不含有数据库部分,也就是数据持久存储的功能,此外不能含有网络功能,它只是对基础知识的一个总结,最后一点是这个 ...
- H5版俄罗斯方块(5)---需求演进和产品迭代
前言: 产品的形态是不断迭代的, 从粗糙到精致, 从简易到立体. 有了最初的技术积累和时间思考后, 终于明确了该游戏的方向. 我想说的是: 技术不是重点, 产品/用户体验才是核心议题. 结合朋友的游戏 ...
随机推荐
- 通用权限管理系统底层更换最新Oracle驱动的方法
通用权限管理系统底层先前访问Oracle数据库时需要客户端安装驱动软件,如下图: 安装完毕还需要一番配置,系统再引用其dll, 现在我们使用了最新的dll 该dll是Oracle出的最新的版本. 通用 ...
- Asp.net MVC3 CSS 模板
http://www.cnblogs.com/huyq2002/archive/2011/12/16/2289795.html Asp.net MVC3 CSS 模板 如果你现在正在用asp.net ...
- gdb调试遇到的问题
解决方法:http://stackoverflow.com/questions/31062010/ubuntu-14-04-gcc-4-8-4-gdb-pretty-printing-doesnt-w ...
- 流媒体技术学习笔记之(十二)Linux(Ubuntu)环境运行EasyDarwin
Debug问题??? ./easydarwin -c ./easydarwin.xml & //这样的话是80端口 ./easydarwin -c ./easydarwin.xml -d // ...
- Kafka 温故(四):Kafka的安装
Step 1: 下载Kafka > tar -xzf kafka_2.9.2-0.8.1.1.tgz> cd kafka_2.9.2-0.8.1.1 Step 2: 启动服务Kafka用到 ...
- 5个经典的javascript面试问题
问题1:Scope作用范围 考虑下面的代码: (function() { var a = b = 5;})(); console.log(b); 什么会被打印在控制台上? 回答 上面的代码会打印 ...
- JavaScript 删除 ASP.NET 设置的多值 Cookie 的方法
需要注意HttpOnly,Path等属性.完整的测试代码: ASPX 代码<%@ Page Language="C#" %> <!DOCTYPE html PUB ...
- 如何让你的.vue在sublime text 3 中变成彩色?
作者:青鲤链接:https://www.zhihu.com/question/52215834/answer/129495890来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...
- 『实践』Android之短信验证码(用的Mob短信验证)
1.参考资料 Mob网站:http://www.mob.com/ Mob在Github上的例子:https://github.com/MobClub/SMSSDK-for-Android 教程:htt ...
- 配置Eclipse编写HTML/JS/CSS/JSP页面的自动提示
我们平时用eclipse开发jsp页面时智能提示效果不太理想,今天用了两个小时发现了eclipse也可以像Visual Studio 2008那样完全智能提示HTML/JS/CSS代码,使用eclip ...