html5 canvas 扫雷游戏
最近由于工作一直比较忙,所以没什么闲暇时间来打理博客,趁着中秋放假的时间,宅在家里实在不知道干啥,
于是还是干起了自己的老本行,写写小游戏,进了新公司,发现自己写的这些小游戏在别人大牛面前确实不值一提,
反正慢慢来嘛,总有一天我相信自己会写出一款非常有深度、高达上的游戏来的。
闲话少说,最近奉上一款经典的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 扫雷游戏相关推荐
- Html5 Canvas 扫雷 (IE9测试通过)
扫雷是一个非常经典的游戏,记得在第一次接触的windows 3.22 上就有扫雷了,到现在的Win7,依然保留着这个经典的游戏,结合Html5 Canvas,模仿Win7的UI,将老板扫雷进行了升级. ...
- HTML5 canvas五子棋游戏
1.html5 canvas cookbook http://www.html5canvastutorials.com/cookbook/ 2.html5 canvas 2D API http://i ...
- Html5 Canvas斗地主游戏
过完年来公司,没什么事,主管说研究下html5 游戏,然后主管就给了一个斗地主的demo,随后我就开始看代码, 现在我看了html5以及canvas相关知识和斗地主的demo后,自己用demo上的素材 ...
- HTML5——canvas制作游戏2048
笔者刚学HTML5不久,这是笔者第一次用HTML5制作游戏,希望大家多多提点意见. 废话不多说,先来一张效果图: 笔者只做了游戏主体,至于那些得分啊,历史最高分之类的,都没有去实现.那些都是比较简单的 ...
- HTML5 Canvas小游戏
知识点 canvas JavaScript 开发准备 代码获取 $ wget https://labfile.oss.aliyuncs.com/courses/361/simple_canvas_ga ...
- [译]怎样用HTML5 Canvas制作一个简单的游戏
这是我翻译自LostDecadeGames主页的一篇文章,原文地址:How To Make A Simple HTML5 Canvas Game. 下面是正文: 自从我制作了一些HTML5游戏(例如C ...
- html5上色游戏制作,怎样用HTML5 Canvas制作一个简单的游戏
原文连接: How To Make A Simple HTML5 Canvas Game 自从我制作了一些HTML5游戏(例如Crypt Run)后,我收到了很多建议,要求我写一篇关于怎样利用HTML ...
- 如何用html5创作一个游戏
原文连接:How To Make A Simple HTML5 Canvas Game 自从我制作了一些HTML5游戏(例如Crypt Run)后,我收到了很多建议,要求我写一篇关于怎样利用HTML5 ...
- HTML5/Javascript 2D游戏引擎列表
2D Javascript游戏引擎列表 Akihabara Lincense: GPL2/MIT Akihabara 是用于创建8/16位图游戏的js库和工具集合.它通过使用HTML5 Canvas ...
最新文章
- 正确认识自己,把自己摆正位置
- 【FI】SAP ODN简介
- cisco 交换机 定期 自动 备份配置 -linux,交换机定时自动备份配置文件的方法
- es根据字段长度过滤_Es 根据数组长度查询
- Vscode解决Setting.json报警告:Problems loading reference ... Unable to load schema from ...
- c mysql并行多条sql_Linux上使用C语言执行多条SQL命令访问MYSQL数据库的有关问题...
- 内置模块/核心模块 (自带的)---http 服务器模块
- IOCP中的socket错误和资源释放处理方法
- mysql同步到另一台服务器_mysql数据库从一台服务器迁移到另一台服务器上
- 关于网上商城开发的随笔记录1
- 参数估计:矩估计和最大似然估计
- 图层重命名快捷键_ps怎么给图层(批量)重命名的方法
- 《NVM-Express-1_4-2019.06.10-Ratified》学习笔记(8.8)-- Reservations
- 流放者柯南自建服务器 linux,流放者柯南自建服务器教程一览服务器搭建方法介绍...
- 我自己的java软件开发职业规划
- HTML学习13:div和表格布局
- Metasploit工具配置使用
- 推荐4款最好用的远程桌面访问软件,亲测好评
- 「Snappy」- 使用 snap 安装应用 @20210208
- 明日方舟骑兵与猎人活动良心来袭 叉叉助手挂机刷关搬奖池
热门文章
- 辛酸篇----一套房产八个名字!婚姻岂能如此设防
- Eclipse+git中merge代码时出现conflict(冲突)的问题解决方案
- 电力VR技术来进行安全教育培训有什么好处广州华锐互动
- 路由器DNS代理的工作原理介绍
- 期末考各项低控掠过!!!
- Android 获取联系人和电话号码
- Chromebook2013 由Fyde os 升级为Deepin v20.2.1 (一)
- python中的reshape是什么意思_python中reshape的用法(深度学习入门程序)
- 如何在RobotStudio搭建基本工作站
- 独享带宽和共享带宽的区别