最近由于工作一直比较忙,所以没什么闲暇时间来打理博客,趁着中秋放假的时间,宅在家里实在不知道干啥,

于是还是干起了自己的老本行,写写小游戏,进了新公司,发现自己写的这些小游戏在别人大牛面前确实不值一提,

反正慢慢来嘛,总有一天我相信自己会写出一款非常有深度、高达上的游戏来的。

闲话少说,最近奉上一款经典的windows系统游戏——扫雷。在js的世界里,扫雷也就那样。

这个游戏仍然基于canvas编写,主要实现的技术细节如下:

1、二维矩阵的创建

首先创建一个二维数组,每个数组单元代表一个格子,每个格子有很多属性在里面,

比如是否未被揭开的,是否插旗、炸弹、问号、空白、数字等,初始化时都是隐藏的,

然后在此基础上随机布雷,布雷后开始统计每个方格周围的雷的数量,代码如下:

[code]
/**
* 创建二维数组,用来存储各种类型对象
*/
createMatrix: function() {
matrix = [];
for(var i = 0; i < level.y; i++) {
matrix[i] = [];
for(var j = 0; j < level.x; j++) { //0-否 >=1-是
matrix[i][j] = {
hidden: 1,//是否隐藏的
number: 0,//数字
flag: 0,//旗子
doubt: 0,//问号
bomb: 0,//炸弹 0-没有 1-正常炸弹 2-标记错误的炸弹 3-被点击后造成游戏结束的炸弹
hover: 0//hover样式
};
}
}
}
/**
* 随机布雷,1为有雷
* @param count 雷的个数
*/
createMines: function(count) {
while(count > 0) {
var i = Math.ceil(Math.random() * (level.y - 1));
var j = Math.ceil(Math.random() * (level.x - 1));
if(matrix[i][j] !== 1) {
matrix[i][j].bomb = 1;
count--;
}
}
}
/**
* 创建数字,根据方格周围的雷数量统计
*/
createNumbers: function() {
for(var i = 0; i < level.y; i++) {
for(var j = 0; j < level.x; j++) { //确保当前格子不是雷 if(matrix[i][j].bomb === 0) { i > 0 && j > 0 && matrix[i-1][j-1].bomb === 1 && matrix[i][j].number++;//左上方
i > 0 && matrix[i-1][j].bomb === 1 && matrix[i][j].number++;//正上方
i > 0 && j < level.x - 1 && matrix[i-1][j+1].bomb === 1 && matrix[i][j].number++;//右上方
j < level.x - 1 && matrix[i][j+1].bomb === 1 && matrix[i][j].number++;//正右方
i < level.y - 1 && j < level.x - 1 && matrix[i+1][j+1].bomb === 1 && matrix[i][j].number++;//右下方
i < level.y - 1 && matrix[i+1][j].bomb === 1 && matrix[i][j].number++;//正下方
i < level.y - 1 && j > 0 && matrix[i+1][j-1].bomb === 1 && matrix[i][j].number++;//左下方
j > 0 && matrix[i][j-1].bomb === 1 && matrix[i][j].number++;//正左方
}
}
}
}
[/code]

2、开始后的点击事件

游戏初始化后,第一次点击需要后台判断是否有雷,有雷的话重新布雷,知道保证第一次点击不出现雷,

由于需要用到鼠标右键功能,所以还需要屏蔽浏览器的右键菜单。

为了方便区分选中的格子,还需要一些hover样式,

还有就是级别的选择变化事件,代码如下:

[code]
/**
* 绑定事件
*/
bindEvent: function() {
//阻止默认右击事件
canvas.addEventListener('contextmenu', function(e) {
e.returnValue = false;
});
canvas.addEventListener('mouseup', function(e) {
if(mineSweeping.status === 'stopped') {
mineSweeping.stoppedClickHandler(e);
} else {
mineSweeping.startedClickHandler(e);
}
});
canvas.addEventListener('mousemove', function(e) {
if(mineSweeping.status !== 'stopped') {
mineSweeping.startedHoverHandler(e);
}
});
canvas.addEventListener('mouseleave', function(e) {
if(mineSweeping.status !== 'stopped') {
mineSweeping.startedHoverHandler(e);
}
});
doc.querySelector('select').addEventListener('change', function() {
//改变当前的级别,并重新初始化
level = levels[parseInt(this.value)];
mineSweeping.init();
});
}
/**
* 开始后点击事件回调
* @param e
*/
startedClickHandler: function(e) {
var x = Math.floor(e.offsetX/Config.size),
y = Math.floor(e.offsetY/Config.size);
var m = matrix[y][x];
if(this.status === 'started') {//游戏已经开始
if(e.which === 1) {//鼠标右键点击
if(m.hidden === 1 && m.flag === 0) {
//格子是隐藏的并且未标记旗子则直接打开
this.openBlock(x, y);
} else if(m.hidden === 0) {
//格子已经显示,则开启周围的格子,windows上是双击触发这个效果
this.openAroundBlock(x, y);
}
} else if(e.which === 3 && m.hidden === 1) {//鼠标左键并且格子是隐藏的
if(m.flag === 1) {
//如果有旗子,则把旗子换成问号,同时炸弹和隐藏格子数量增加
m.flag = 0;
m.doubt = 1;
lastMines++;
lastHidden++;
} else if(m.doubt === 1) {
//如果是问号,则直接去掉问号
m.doubt = 0;
} else {
//如果不是问号,也不是旗子,则标记旗子
m.flag = 1;
lastMines--;
lastHidden--;
}
}
if(lastMines === lastHidden) {
//剩余的雷和剩余未被揭开的格子相等时游戏结束,传一个true过去代表通关造成的结束
this.over(true);
}
} else if(this.status === 'inited') {//游戏刚刚初始化
if(e.which === 1) {
//如果是鼠标右键
this.openBlock(x, y);
this.status = 'started';
this.startTime = 0;
this.createStartTimer();//开始计时
}
}
this.infos[1].innerHTML = lastMines;//修改剩余雷数
if(this.status !== 'stopped') {
//游戏开始后才进行绘制
this.draw();
}
}
[/code]

3、揭开方块

揭开方块需要很多种情况,比如插旗的格子就不能被揭开,揭开是炸弹的话就要结束游戏,

如果是空白的话,则自动打开周围的无雷格子,

插旗的数量和未被揭开的格子数量相等时游戏就通关等,代码如下:

[code]
/**
* 打开方格
* @param x x坐标
* @param y y坐标
*/
openBlock: function(x, y) {
var m = matrix[y][x];
if(m.bomb === 1) {
//如果有炸弹
if(this.status === 'started') {
//此时游戏已经开始了,因此要直接结束游戏
m.bomb = 3;
this.over();
} else if(this.status === 'inited') {
//此时游戏刚初始化,避免第一次点击就碰着雷,所以重新初始化,知道没碰到雷位置
this.init();
this.openBlock(x, y);
}
} else if(m.flag === 1) {
//如果有旗子的话,会造成游戏结束,此种情况可能发生在自动打开周围方格时
m.hidden = 0;
m.flag = 0;
m.bomb = 2;
this.over();
} else {
//如果有数字,正常情况是直接显示数字
m.hidden = 0;
if(m.doubt === 1) {
//如果是问号,则把问号去掉
m.doubt = 0;
}
lastHidden--;//剩余未被开启的格子减1
if(m.number === 0) {
//如果是空白的话则把周围的都显示出来
x > 0 && y > 0 && matrix[y-1][x-1].hidden === 1 && this.openBlock(x-1, y-1);
y > 0 && matrix[y-1][x].hidden === 1 && this.openBlock(x, y-1);
y > 0 && x < level.x - 1 && matrix[y-1][x+1].hidden === 1 && this.openBlock(x+1, y-1);
x < level.x - 1 && matrix[y][x+1] === 1 && this.openBlock(x+1, y);
y < level.y - 1 && x < level.x - 1 && matrix[y+1][x+1].hidden === 1 && this.openBlock(x+1, y+1);
y < level.y - 1 && matrix[y+1][x].hidden === 1 && this.openBlock(x, y+1);
y < level.y - 1 && x > 0 && matrix[y+1][x-1].hidden === 1 && this.openBlock(x-1, y+1);
x > 0 && matrix[y][x-1].hidden === 1 && this.openBlock(x-1, y);
}
}
}
[/code]

4、揭开周围格子

当点击已显示的数字方格时,需要将周围的无雷格子全部揭开,

但前提是周围插旗的数量要和雷的数量一直才行,代码如下:

[code]
/**
* 打开方格周围的方格
* @param x
* @param y
*/
openAroundBlock: function(x, y) {
var flagCount = 0,//周围旗子统计
hiddenCount = 0;//周围隐藏的格子统计
for(var i = y - 1; i <= y + 1; i++) {
for(var j = x - 1; j <= x + 1; j++) { //在边界的话有可能索引报错 try { if(matrix[i][j].flag === 1) { flagCount++; } if(matrix[i][j].hidden === 1) { hiddenCount++; } } catch (ex) {} } } /** * 如果旗子数等于周围的炸弹数,并且有未标记旗子的隐藏格子 */ if(flagCount === matrix[y][x].number && hiddenCount > flagCount) {
for(i = y - 1; i <= y + 1; i++) {
for(j = x - 1; j <= x + 1; j++) {
try {
if(matrix[i][j].bomb === 0 && matrix[i][j].hidden === 1) {
//没有炸弹,并且是隐藏的才打开格子
this.openBlock(j, i);
}
} catch (ex) {}
}
}
}
}
[/code]

5、其它辅助功能

比如剩余雷数提醒,计时,结束及开始样式等,当然这些都是小case。

代码如下:

[code]
/**
* 游戏结束时显示所有的方块
*/
createMatrixForOver: function() {
for(var i = 0; i < level.y; i++) {
for(var j = 0; j < level.x; j++) {
var m = matrix[i][j];
m.hidden = 0;
//如果当前格子没有炸弹,但是却标了红旗,表示错误的标法,同时把旗子去掉
if(m.bomb === 0 && m.flag === 1) {
m.flag = 0;
m.bomb = 2;
}
}
}
}
/**
* 绘制各种形状
*/
draw: function() {
this.clear();//首先清除画布,重新绘制
for(var i = 0; i < level.y; i++) {
for(var j = 0; j < level.x; j++) { var m = matrix[i][j]; var size = Config.size; var x = j * size, y = i * size; if(m.hidden === 1) { //隐藏的就绘制隐藏的icon this.drawImage(x, y, 0); } else if(m.bomb === 2 || m.bomb === 3) { //正常炸弹 if (m.bomb === 3) { //游戏结束时会有一个产生结束的肇事者炸弹,将其背景变红 context.fillStyle = Color.bombBg; context.fillRect(x, y, size, size); } this.drawImage(x, y, 4); } else if(m.bomb === 1) { //标记错误的炸弹 this.drawImage(x, y, 3); } else if(m.number > 0) {
//数字
this.drawNumber(m.number, x, y);
}
if(m.doubt === 1) {
//问号
this.drawImage(x, y, 2);
} else if(m.flag === 1) {
//旗子
this.drawImage(x, y, 1);
}
//绘制边框
this.drawBorder(x, y, m.hover);
}
}
this.drawCopy();
}
/**
* 绘制边框
* @param x x坐标
* @param y y坐标
* @param color 颜色序号,0-正常,1-hover
*/
drawBorder: function(x, y, color) {
context.beginPath();
context.strokeStyle = Color.border[color];
context.rect(x, y, Config.size, Config.size);
context.closePath();
context.stroke();
},
/**
* 绘制版权信息
*/
drawCopy: function() {
context.textAlign = 'center';
context.textBaseline = 'middle';
context.font = '12px Arial';
context.fillStyle = Color.copy;
context.fillText('All right by 谢承雄 版权所有!', canvas.width/2, canvas.height/2);
},
/**
* 绘制图形
* @param x x坐标
* @param y y坐标
* @param sx 裁剪位置序号
*/
drawImage: function(x, y, sx) {
context.drawImage(image, sx*(Config.size-1), 0, Config.size-1, Config.size-1, x, y, Config.size, Config.size);
},
/**
* 绘制数字
* @param number
* @param x
* @param y
*/
drawNumber: function(number, x, y) {
context.textAlign = 'center';
context.textBaseline = 'middle';
context.font = '14px Arial bold';
context.fillStyle = Color.number[number-1];
context.fillText(number, x+Config.size/2, y+Config.size/2);
},
/**
* 绘制结束屏幕
* @param isPassed 是否通关造成的结束
*/
drawOver: function(isPassed) {
var tipsText = isPassed ? 'GAME PASS!' : 'GAME OVER!';
context.fillStyle = Color.mask;
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = Color.btnBg;
context.fillRect(canvas.width/2-Config.btnWidth/2, canvas.height/2-Config.btnHeight/2, Config.btnWidth, Config.btnHeight);
context.textAlign = 'center';
context.textBaseline = 'middle';
context.font = '20px Arial';
context.fillStyle = Color.btnFont;
context.fillText('再来一次', canvas.width/2, canvas.height/2+Config.btnHeight/2-20);
context.fillStyle = Color.overFont;
context.fillText(tipsText, canvas.width/2, canvas.height/2-Config.btnHeight);
}
/**
* 游戏开始后的hover事件回调
* @param e
*/
startedHoverHandler: function(e) {
if(e.type === 'mousemove') {//鼠标滑动
var x = Math.floor(e.offsetX/Config.size),
y = Math.floor(e.offsetY/Config.size);
var m = matrix[y][x];
if(m.hover === 0) {//之前没有hover才添加hover效果
for(var i = 0; i < level.y; i++) {
for(var j = 0; j < level.x; j++) {
m = matrix[i][j];
//要隐藏的元素才满足hover要求
m.hover = +(m.hidden === 1 && i === y && j === x);
}
}
this.draw();
}
} else {//鼠标离开canvas画布
for(i = 0; i < level.y; i++) {
for(j = 0; j < level.x; j++) { m = matrix[i][j]; if(m.hover === 1) { //如果有hover,则把hover去掉 m.hover = 0; this.draw(); return; } } } } }, /** * 结束后的点击事件回调 * @param e */ stoppedClickHandler: function(e) { var x = e.offsetX, y = e.offsetY; //坐标在按钮范围内则点击有效 if(x > (canvas.width-Config.btnWidth)/2
&& x < (canvas.width+Config.btnWidth)/2 && y > (canvas.height-Config.btnHeight)/2
&& y < (canvas.height+Config.btnHeight)/2) {
this.init();
}
}
[/code]
我的前端个人主页:http://xiechengxiong.com/

最后把demo地址附上:http://xiechengxiong.com/xui/demos/minesweeping/

html5 canvas 扫雷游戏相关推荐

  1. Html5 Canvas 扫雷 (IE9测试通过)

    扫雷是一个非常经典的游戏,记得在第一次接触的windows 3.22 上就有扫雷了,到现在的Win7,依然保留着这个经典的游戏,结合Html5 Canvas,模仿Win7的UI,将老板扫雷进行了升级. ...

  2. HTML5 canvas五子棋游戏

    1.html5 canvas cookbook http://www.html5canvastutorials.com/cookbook/ 2.html5 canvas 2D API http://i ...

  3. Html5 Canvas斗地主游戏

    过完年来公司,没什么事,主管说研究下html5 游戏,然后主管就给了一个斗地主的demo,随后我就开始看代码, 现在我看了html5以及canvas相关知识和斗地主的demo后,自己用demo上的素材 ...

  4. HTML5——canvas制作游戏2048

    笔者刚学HTML5不久,这是笔者第一次用HTML5制作游戏,希望大家多多提点意见. 废话不多说,先来一张效果图: 笔者只做了游戏主体,至于那些得分啊,历史最高分之类的,都没有去实现.那些都是比较简单的 ...

  5. HTML5 Canvas小游戏

    知识点 canvas JavaScript 开发准备 代码获取 $ wget https://labfile.oss.aliyuncs.com/courses/361/simple_canvas_ga ...

  6. [译]怎样用HTML5 Canvas制作一个简单的游戏

    这是我翻译自LostDecadeGames主页的一篇文章,原文地址:How To Make A Simple HTML5 Canvas Game. 下面是正文: 自从我制作了一些HTML5游戏(例如C ...

  7. html5上色游戏制作,怎样用HTML5 Canvas制作一个简单的游戏

    原文连接: How To Make A Simple HTML5 Canvas Game 自从我制作了一些HTML5游戏(例如Crypt Run)后,我收到了很多建议,要求我写一篇关于怎样利用HTML ...

  8. 如何用html5创作一个游戏

    原文连接:How To Make A Simple HTML5 Canvas Game 自从我制作了一些HTML5游戏(例如Crypt Run)后,我收到了很多建议,要求我写一篇关于怎样利用HTML5 ...

  9. HTML5/Javascript 2D游戏引擎列表

    2D  Javascript游戏引擎列表 Akihabara Lincense: GPL2/MIT Akihabara 是用于创建8/16位图游戏的js库和工具集合.它通过使用HTML5 Canvas ...

最新文章

  1. 正确认识自己,把自己摆正位置
  2. 【FI】SAP ODN简介
  3. cisco 交换机 定期 自动 备份配置 -linux,交换机定时自动备份配置文件的方法
  4. es根据字段长度过滤_Es 根据数组长度查询
  5. Vscode解决Setting.json报警告:Problems loading reference ... Unable to load schema from ...
  6. c mysql并行多条sql_Linux上使用C语言执行多条SQL命令访问MYSQL数据库的有关问题...
  7. 内置模块/核心模块 (自带的)---http 服务器模块
  8. IOCP中的socket错误和资源释放处理方法
  9. mysql同步到另一台服务器_mysql数据库从一台服务器迁移到另一台服务器上
  10. 关于网上商城开发的随笔记录1
  11. 参数估计:矩估计和最大似然估计
  12. 图层重命名快捷键_ps怎么给图层(批量)重命名的方法
  13. 《NVM-Express-1_4-2019.06.10-Ratified》学习笔记(8.8)-- Reservations
  14. 流放者柯南自建服务器 linux,流放者柯南自建服务器教程一览服务器搭建方法介绍...
  15. 我自己的java软件开发职业规划
  16. HTML学习13:div和表格布局
  17. Metasploit工具配置使用
  18. 推荐4款最好用的远程桌面访问软件,亲测好评
  19. 「Snappy」- 使用 snap 安装应用 @20210208
  20. 明日方舟骑兵与猎人活动良心来袭 叉叉助手挂机刷关搬奖池

热门文章

  1. 辛酸篇----一套房产八个名字!婚姻岂能如此设防
  2. Eclipse+git中merge代码时出现conflict(冲突)的问题解决方案
  3. 电力VR技术来进行安全教育培训有什么好处广州华锐互动
  4. 路由器DNS代理的工作原理介绍
  5. 期末考各项低控掠过!!!
  6. Android 获取联系人和电话号码
  7. Chromebook2013 由Fyde os 升级为Deepin v20.2.1 (一)
  8. python中的reshape是什么意思_python中reshape的用法(深度学习入门程序)
  9. 如何在RobotStudio搭建基本工作站
  10. 独享带宽和共享带宽的区别