引言
好久没写博客了,今天来写下上个星期花了几天时间做的这个web端的canvas游戏—-抓猫游戏
游戏介绍
首先,这个游戏是前几年出来的,比较火的一个游戏,虽然我没玩过,最近访问网站时,不小心404了,在404界面有了一个这个flash做的游戏如图
游戏的玩法就是每次点击图中不是灰点的圆点,猫都会走一步,如果猫最终走出图,则失败;若你用灰色点将猫困住,则成功.
思考
- 首先,图中圆点的构建,我准备用canvas来绘制
- 其次,对于圆点的记数,是用坐标[0,1]这种还是0,1,2这种
- 对于圆点的数据结构建模
- 圆点的寻路算法
探索过程
先思考数据结构,用什么数据结构呢?多点,有最短路径,有闭环,当然就是图了,二叉树肯定不行.
什么图?看一下游戏玩法,普通的一个点可以向周围6个方向发散,而且可以返回,那就是一个无权无向图了.
什么最短路径算法?最简单的有dfs(Depth First Search)与bfs(Breadth First Search),当然还有Dijkstra算法,A star算法,稍微了解了下,后面两个主要针对有权图,当然无权也可以,之后可以去试一下,这里我用的是bfs算法
圆点怎么个走法?有哪些规律?要将可通过的边界点记录下来,寻找到边界点的最短路径和下一步的走法;普通圆点a(假设是11*11的矩阵),如果是在奇数行的话(第一行为0行),他的6个下一节点位置和在偶数行的下一个节点位置不同,如图
上图的14号节点是在第1行里的,他的左上是14-11=3,左是14-1=13,左下是14+11=25,右上是14-10=4,右是14+1=15,右下是14+12=26
在如下图
上图的26号节点是在第1行里的,他的左上是26-12=14,左是26-1=25,左下是26+10=36,右上是26-11=15,右是26+1=27,右下是26+11=37
用什么语言呢? 先准备用python,后来想想算了,既然放页面上还是用js去实现了
以前数据结构课上学过,图有两种实现方式,一种是邻接表的形式,每个节点后面跟着一个记录者该节点可以到达的下一个节点的数组;还有一种是邻接矩阵的形式,每个节点代表矩阵的横向以及纵向的点,x,y=0的话,则代表x与y之间不连通;x,y=1的话(无权,如果有权的话,则是权值),则代表x与y之间连通,所以一般无向图的都是对称的,中间斜对角线是无穷大,代表自己到自己的距离不可达.
这里我用了邻接数组,二维数组来实现无权无向图
图有顶点Vertex类,还有Graph类,Graph里主要有一些图内自带的属性以及函数方法,比如新增边addEdge,新增节点addVertex,删除节点removeVertex以及init初始化,随机生成灰色点等,闲话少说上代码
canvas实现
核心的算法实现就上面所说,当然我们还得用canvas去实现他,在canvas实现的过程中也有一些坑.
- 每次点击的重绘过程如何实现
- 小猫图片的移动或者变换如何实现
首先,需要对你所点击的canvas做监听操作,需要在callback函数里面做判断,如果点击的在绿色点区域内,则重绘,否则不做操作.
其次,小猫图片我首先想到的也用canvas来做,后来发现行不通,无法实现变换和移动操作,后来干脆直接用html的image标签来做,对其的position进行dom操作,并且一同放在canvas的监听事件里面.上代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259/*** Created by Saintyun on 2017/5/12.*/var container = document.getElementById("container")var tips = document.getElementById("tips")document.write(returnCitySN["cip"]+','+returnCitySN["cname"])var timer = document.getElementById("timer")function startop(){var speed = 0.2;var start = self.setInterval(function(){container.style.opacity = speed.toString();speed+=0.2;console.log(speed)if(speed>=1){window.clearInterval(start)}},200)}function endop(){var speed = parseInt(container.style.opacity);var end = setInterval(function(){container.style.opacity = speed.toString();speed-=0.2;console.log(speed)if(speed<=0){window.clearInterval(end)location.reload()}},200)}window.onload = function(){startop()}var g = new Graph(121);// 定义当前猫的位置的数组var localvertex = 60var prevertexg.init(11);g.listBorder(11);var canvas = document.getElementById("canvas")var ctx = canvas.getContext('2d')var circle = {x: 64,y: 64,r: 20}var imagelocation = {x: 298,y: 268}var arr = []var myimage = document.getElementById("myimage")myimage.style.position = "absolute";myimage.style.left = imagelocation.x + "px";myimage.style.top = imagelocation.y + "px";myimage.src = '/canvas/image/stand_cat1.png'for (var i = 0; i < 11; i++) {for (var j = 0; j < 11; j++) {if (i % 2 == 0) {arr.push({x: circle.x + j * (2 * circle.r + 4),y: circle.y + i * (2 * circle.r + 4),r: 20,vertex: i * 11 + j})} else {arr.push({x: circle.x + j * (2 * circle.r + 4) + 22,y: circle.y + i * (2 * circle.r + 4),r: 20,vertex: i * 11 + j})}}}canvas.addEventListener('click', function (e) {p = getEventPosition(e);reDraw(p, ctx);//// console.log(g.num)}, false)// 为了实现循环重绘,所以就要将图形的基本参数事先保存下来// 点击重新绘制//得到点击的坐标function getEventPosition(ev) {var x, y;if (ev.layerX || ev.layerX == 0) {x = ev.layerX;y = ev.layerY;} else if (ev.offsetX || ev.offsetX == 0) { // Operax = ev.offsetX;y = ev.offsetY;}return {x: x, y: y};}//重绘function reDraw(p, ctx) {var date1 = new Date().getTime();var whichObject = [];for (var i = 0; i < arr.length; i++) {// 点击的不在当前 localvertex 圆下if (p && (arr[i].x + arr[i].r) >= p.x && p.x >= arr[i].x - arr[i].r&& (arr[i].y + arr[i].r) >= p.y && p.y >= arr[i].y - arr[i].r&& i != localvertex&& !g.num.contains(i)) {whichObject.push(i);//每次点击一次,image坐标改变一次ctx.fillStyle = '#728401';ctx.beginPath();ctx.arc(arr[i].x, arr[i].y, arr[i].r, 0, Math.PI * 2, true);ctx.fill();g.num.push(i)// 还有图片移动的6个方向的情况,第一次点击,反方向情况,之后根据路径来//然后获取图片移动之后的位置!// 1.记录点击的位置 :i// 2.将其canvas颜色改变,// 3.将其图的顶点去除 相关数组//g.removeVertex(i)if (borderVertext.indexOf(i) != -1) {borderVertext.splice(borderVertext.indexOf(i), 1)}// console.log(borderVertext)g.remarker()if(borderVertext.contains(localvertex)){console.log("猫跑出去咯!")tips.innerHTML = "额,就差一点,猫跑出去咯!"myimage.style.display = 'none'}else {g.bfs(localvertex)// nextnodeprevertex = localvertexlocalvertex = g.getpath(isInPath, localvertex)// }if (localvertex != -1) {console.log("localvertex:" + localvertex + "prevertex:" + prevertex)// 解决每点击一次,往已点击数组里加// 偶数行奇数行问题// 优化!把imagelocation的操作单独列出6个方向操作if (localvertex == prevertex + 11) {if (Math.floor(prevertex / 11) % 2 == 0) {//斜向右下imagelocation = {x: imagelocation.x + 22,y: imagelocation.y + 44}} else {imagelocation = {x: imagelocation.x - 22,y: imagelocation.y + 44}}} else if (localvertex == prevertex + 10) {// 斜向左下imagelocation = {x: imagelocation.x - 22,y: imagelocation.y + 44}} else if (localvertex == prevertex + 12) {//斜向右下imagelocation = {x: imagelocation.x + 22,y: imagelocation.y + 44}}else if (localvertex == prevertex - 12) {//imagelocation = {x: imagelocation.x - 22,y: imagelocation.y - 44}}else if (localvertex == prevertex + 1) {// 向右imagelocation = {x: imagelocation.x + 44,y: imagelocation.y}} else if (localvertex == prevertex - 1) {// 向左imagelocation = {x: imagelocation.x - 44,y: imagelocation.y}} else if (localvertex == prevertex - 11) {if (Math.floor(prevertex / 11) % 2 == 0) {// 斜向右上imagelocation = {x: imagelocation.x + 22,y: imagelocation.y - 44}} else {imagelocation = {x: imagelocation.x - 22,y: imagelocation.y - 44}}} else if (localvertex == prevertex - 10) {imagelocation = {x: imagelocation.x + 22,y: imagelocation.y - 44}}myimage.style.left = imagelocation.x + "px"myimage.style.top = imagelocation.y + "px"}else{myimage.src='/canvas/image/catch_cat.png'tips.innerHTML = "好耶!你抓住猫啦!"console.log("你抓住猫啦!")}}//注意图片所移动的位置 一定是没有点击过的 √// 到边界时候的处理// 包围起来,没有路走的处理break;}}// 接下来的问题是如何移动图片,或者是如何消除之前的图片// var date2 = new Date().getTime();// timer.innerHTML += "draw所耗费时间"+(date2-date1)+"ms"+"<br/>"}// 按照i*j = vfor (var i = 0; i < 11; ++i) {for (var j = 0; j < 11; ++j) {// ctx.clearRect(0, 0, canvas.width, canvas.height);if (g.num.contains(i * 11 + j)) {ctx.fillStyle = '#728401';} else {ctx.fillStyle = '#ccff00';}if (i % 2 == 0) {ctx.beginPath()ctx.arc(circle.x + j * (2 * circle.r + 4), circle.y + i * (2 * circle.r + 4), circle.r, 0, Math.PI * 2, true)ctx.fill()ctx.beginPath()ctx.fillStyle = 'black'ctx.fillText(i * 11 + j, circle.x + j * (2 * circle.r + 4), circle.y + i * (2 * circle.r + 4));} else {ctx.beginPath()ctx.arc(circle.x + j * (2 * circle.r + 4) + 22, circle.y + i * (2 * circle.r + 4), circle.r, 0, Math.PI * 2, true)ctx.fill()ctx.beginPath()ctx.fillStyle = 'black'ctx.fillText(i * 11 + j, circle.x + j * (2 * circle.r + 4) + 22, circle.y + i * (2 * circle.r + 4));}}}
实现的效果
实现的效果去这个网站地址就可以去看了,我把部署到vps上了
http://saintyun.cn/canvas
效果还行的,你可以打开控制台,看下每一步的路径以及目标点,我也没删掉这些,算是个小作弊吧~~~
优化
- 寻找下一步的走法时,只是单纯的根据最短路径的第一个值来,如果有多条最短路径,但是A路径的下一个节点a0是有点”自投罗网”的意思,就是a0方向的灰色节点较多,这样就比较容易围住,所以以后可以做个某方向灰色点分布趋势的判断,得出哪一个是最优点
- 目前只是单机版的,没有排行榜,难度切换,访问用户以及用户ip记录,步数记录,得分转化等功能
- 代码上冗余的有点多,需要优化
下期预告
我准备做一些android的ui控件或者效果或者实现一些小框架如网络连接之类的或者如下图所示的效果