过年的十八天假期迷迷糊糊一下子就过去了(LZ还是实习生,鉴于大学最后一个寒假了,所以就多请了好多天假),又要返工上班了。这是年后的第一篇博客了。其实就是水一下,毕竟不能冷落博客太久啊。
这两天刚好抱着玩的心情写了个接水管游戏,本来用css3更容易写。。。就旋转那些东西,不过因为LZ比较喜欢canvas的写法,所以还是用了canvas来写。
先上个DEMO吧:接水管 玩法很简单:点击水管块就可以旋转水管,连通水管后点击上面的水阀,然后就可以了。可能还有bug,LZ暂时没去测试了。
因为代码也很简单,重点也不多,就先说下游戏思路:
【游戏逻辑】
游戏逻辑很简单,就是水管总共有4个面,给四个面一个代号:0,1,2,3;然后水管有两个口,一个进水口,一个出水口,就可以用0,1,2,3来表示水管的两个口(如果想写更复杂的,多个口的,也一个样,一个进水口,多个出水口,用数组保存就行了。我写的这个比较简单,只有两个口,所以后面判断连通性也比较容易。),保存好水管块的进出口数据后,当水管旋转时,相应改变水管进出口参数就行了。
然后就是连通性判断:水管只有两个口的话就很简单了。一般有一个最初的进水口,然后就从那个水管块开始,先获取出水口,然后再遍历当前水管块周围的水管块的进水口是否跟当前水管块的出水口相连通(被遍历的水管块的进水口和出水口都要进行判断,因为水管可以旋转,也就是说进水口和出水口在一开始是无法确定的,当确定了连通关系后,就可以确定水管的进水口和出水口了,之后的循环也一样),如果对的上。就说明连通了。然后就跳到那个水管块,再进行那个水管块周围的水管块的遍历,就这样重复遍历,直到最后一个也就是出水的那个水管块也连通了,就说明整条水管连通了。
上面的是只有两个口的水管连通性判断,如果游戏中有三个口的水管甚至四个口的水管,判断方式就没那么简单了。我写的是两个口的,不过也想了一下多口的判断,大概就是找到水管的进水口,因为是多个出水口,所以就得一个一个来,对每一条线路进行判断,每当跳转到一个新的水管块,就把之前的那个水管块保存为新水管块的父类水管。然后对新水管的出水口进行循环判断,如果没有发现有连通的水管,则跳转至父类水管,并将新水管块加入“此路不通”列表,再次遍历父类水管块的其他出水口进行判断,如此循环,当循环到父类为最初进水口的那个水管块的时候,就说明水管没有连通路线,当然,如果循环到新水管块为出口水管时,就说明连通了。
楼主比较懒,所以就只写了两个口的,没写多个口的了。
【代码部分】
首先定义水管块对象:
var Box = function(center , style , Gateway , angle , coordinate){ this.center = center; this.style = style; this.angle = angle; this.endangle = angle; this.Gateway = Gateway; this.coordinate = coordinate; } Box.prototype = { constructor:Box, draw:function(){ this.setHole(); if(this.angle!==this.endangle){ canclick = false; this.rotate(); } var ext = [ {x:0,y:-boxWidth/2}, {x:boxWidth/2,y:0}, {x:0,y:boxWidth/2}, {x:-boxWidth/2,y:0}, ] ctx.save(); ctx.translate(this.center.x , this.center.y); ctx.rotate(this.angle); //画管道 switch(this.style){ case 0:break; case 1:ctx.drawImage(document.getElementById("pipe1") , -boxWidth/2 , -boxWidth/2 , boxWidth , boxWidth); break; case 2:ctx.drawImage(document.getElementById("pipe2") , -boxWidth/2 , -boxWidth/2 , boxWidth , boxWidth); break; case 3:ctx.drawImage(document.getElementById("start") , -boxWidth/2 , -boxWidth/2 , boxWidth , boxWidth); break; } ctx.restore(); }, rotate:function(){ if(Math.abs(this.endangle-this.angle)<=0.01){ canclick = true; this.endangle = this.endangle>=2*Math.PI?0:this.endangle; this.angle = this.endangle; if(this.style===3){ var result = connectPipes(); if(result){ // alert(‘成功连通‘) connectSuccess = true; } else { alert("游戏失败") window.location.reload(); } } } else { this.angle += (this.endangle-this.angle)*0.2; } }, setHole:function(){ if(this.style===1){ var zl = this.endangle/(0.5*Math.PI); var initHole1 = 0 , initHole2 = 2; this.inHole = (initHole1+zl)>=4?((initHole1+zl)-4):(initHole1+zl); this.outHole = (initHole2+zl)>=4?((initHole2+zl)-4):(initHole2+zl); } else if(this.style===2){ var zl = this.endangle/(0.5*Math.PI); var initHole1 = 1 , initHole2 = 2; this.inHole = (initHole1+zl)>=4?((initHole1+zl)-4):(initHole1+zl); this.outHole = (initHole2+zl)>=4?((initHole2+zl)-4):(initHole2+zl); } } }
对象主要属性包括:水管位置,水管种类(主要就两个,1为直线的,2为九十度折角的),水管是否可以旋转的判定,水管旋转的初始角度,以及水管所处位置的行和列。
如何让水管点击后旋转,其实也很简单,就是先把画布平移到水管块中心,然后旋转,然后再把水管画出来,就旋转好了。
然后就是判断水管连通性的代码:
//判断水管连通性 function connectPipes(){ var index = 0; while(1){ var result = getHole(boxes[index]); if(boxes[index+result.nextBox]){ if(result.hole===boxes[index+result.nextBox].inHole){ index = index+result.nextBox; } else if(result.hole===boxes[index+result.nextBox].outHole){ var num = boxes[index+result.nextBox].inHole; boxes[index+result.nextBox].inHole = result.hole; boxes[index+result.nextBox].outHole = num; index = index+result.nextBox; } else { break; } } else { break; } } if(index===boxes.length-1) return true; else return false; } function getHole(box){ var hole="0"; var nextBox=0; switch(box.outHole){ case 0 : hole = 2; nextBox = -cols; break; case 1 : hole = 3; if(box.coordinate.cols===cols-1){ nextBox = 1000000; } else nextBox = 1; break; case 2 : hole = 0; nextBox = cols; break; case 3 : hole = 1; if(box.coordinate.cols===0){ nextBox = 1000000; } else nextBox = -1; break; } return {hole:hole , nextBox:nextBox}; }
逻辑之前已经说过了,而且代码也比较简单,就不解释了,getHole是返回当前水管块的出水口如果要连通,需要的进水口的参数以及水管在水管数组里的位置。
然后就是路径,如何保证一定有条成功的路呢?因为如果管道全部随机的话,可能会陷入死路。所以,我就干脆定好几条正确的路径,每次刷新页面就取其中一条,其他水管块也添加一些随机出来的水管作干扰。所以,我就专门用一个data.js文件来存放所有的路径,路径文件代码如下:
//水管路径 var allPath = [ [ {rows:0,cols:0,style:1}, {rows:1,cols:0,style:1}, {rows:2,cols:0,style:1}, {rows:3,cols:0,style:2}, {rows:3,cols:1,style:2}, {rows:2,cols:1,style:2}, {rows:2,cols:2,style:1}, {rows:2,cols:3,style:1}, {rows:2,cols:4,style:1}, {rows:2,cols:5,style:1}, {rows:2,cols:6,style:1}, {rows:2,cols:7,style:2}, {rows:3,cols:7,style:1}, ], [ {rows:0,cols:0,style:1}, {rows:1,cols:0,style:1}, {rows:2,cols:0,style:2}, {rows:2,cols:1,style:2}, {rows:1,cols:1,style:2}, {rows:1,cols:2,style:2}, {rows:2,cols:2,style:2}, {rows:2,cols:3,style:1}, {rows:2,cols:4,style:1}, {rows:2,cols:5,style:1}, {rows:2,cols:6,style:2}, {rows:1,cols:6,style:2}, {rows:1,cols:7,style:2}, {rows:2,cols:7,style:1}, {rows:3,cols:7,style:1}, ], [ {rows:0,cols:0,style:1}, {rows:1,cols:0,style:1}, {rows:2,cols:0,style:1}, {rows:3,cols:0,style:2}, {rows:3,cols:1,style:2}, {rows:2,cols:1,style:1}, {rows:1,cols:1,style:2}, {rows:1,cols:2,style:1}, {rows:1,cols:3,style:1}, {rows:1,cols:4,style:1}, {rows:1,cols:5,style:2}, {rows:1,cols:5,style:2}, {rows:2,cols:5,style:2}, {rows:2,cols:6,style:1}, {rows:2,cols:7,style:2}, {rows:3,cols:7,style:1}, ], ]
路径的每一个点包含的参数就是行列位置以及水管的类型,如果想游戏更多变化,可以再多加几条路径,我就只弄了三条啦:下面是取路径然后生成相对应的水管,同时把水管的旋转角度也随机。
var n = getRandom(0 , allPath.length-1); var path = allPath[n]; path.foreach(function(){ var index = this.rows*cols + this.cols; if((this.rows==0&&this.cols==0)||(this.rows==(rows-1)&&this.cols==(cols-1))){ boxes[index] = new Box({x:(this.cols*boxWidth)+boxWidth/2+jiange , y:(this.rows*boxWidth)+boxWidth/2+marginTop} , this.style , true , 0 , {rows:this.rows , cols:this.cols}); } else if(this.rows>0&&this.rows<3&&this.cols>2&&this.cols<5){ boxes[index] = new Box({x:(this.cols*boxWidth)+boxWidth/2+jiange , y:(this.rows*boxWidth)+boxWidth/2+marginTop} , this.style , true , 0.5*Math.PI , {rows:this.rows , cols:this.cols}); } else{ boxes[index] = new Box({x:(this.cols*boxWidth)+boxWidth/2+jiange , y:(this.rows*boxWidth)+boxWidth/2+marginTop} , this.style , false , parseInt(getRandom(0,3))*0.5*Math.PI , {rows:this.rows , cols:this.cols}); } });
然后就木有啦。。。。下面贴出所有代码:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <style> body{ padding:0; margin:0; overflow: hidden; } #cas{ display: block; margin:40px auto; border:1px solid; } </style> <title>接水管游戏</title> </head> <body> <canvas id="cas" width="800" height="800">您的浏览器不支持canvas</canvas> <div style="display:none"> <img src="start.png" id="start"> <img src="pipes0.jpg" id="pipe0"> <img src="pipe1.png" id="pipe1"> <img src="pipe2.png" id="pipe2"> </div> <script src="data.js"></script> <script> var PipeGame = (function(){ var canvas = document.getElementById("cas"); var ctx = canvas.getContext(‘2d‘); var cols = 8 , //拼图列 rows = 4 , //拼图行 jiange = 20 , //拼图区与canvas之间的边缘宽度 boxWidth = (canvas.width-2*jiange)/cols, //水管块宽高 marginTop = (canvas.height - boxWidth*rows)/2, //拼图区与顶部高度 pipeWidth = 30 , //水管宽度 canclick = true, //当前是否可以点击 activityBox = null, //用于保存运动中的水管块 boxes = []; //保存水管块的容器 toolBoxes = []; //保存工具对象 connectSuccess = false; return function(){ var Box = function(center , style , Gateway , angle , coordinate){ this.center = center; this.style = style; this.angle = angle; this.endangle = angle; this.Gateway = Gateway; this.coordinate = coordinate; } Box.prototype = { constructor:Box, draw:function(){ this.setHole(); if(this.angle!==this.endangle){ canclick = false; this.rotate(); } var ext = [ {x:0,y:-boxWidth/2}, {x:boxWidth/2,y:0}, {x:0,y:boxWidth/2}, {x:-boxWidth/2,y:0}, ] ctx.save(); ctx.translate(this.center.x , this.center.y); ctx.rotate(this.angle); //画管道 switch(this.style){ case 0:break; case 1:ctx.drawImage(document.getElementById("pipe1") , -boxWidth/2 , -boxWidth/2 , boxWidth , boxWidth); break; case 2:ctx.drawImage(document.getElementById("pipe2") , -boxWidth/2 , -boxWidth/2 , boxWidth , boxWidth); break; case 3:ctx.drawImage(document.getElementById("start") , -boxWidth/2 , -boxWidth/2 , boxWidth , boxWidth); break; } ctx.restore(); }, rotate:function(){ if(Math.abs(this.endangle-this.angle)<=0.01){ canclick = true; this.endangle = this.endangle>=2*Math.PI?0:this.endangle; this.angle = this.endangle; if(this.style===3){ var result = connectPipes(); if(result){ // alert(‘成功连通‘) connectSuccess = true; } else { alert("游戏失败") window.location.reload(); } } } else { this.angle += (this.endangle-this.angle)*0.2; } }, setHole:function(){ if(this.style===1){ var zl = this.endangle/(0.5*Math.PI); var initHole1 = 0 , initHole2 = 2; this.inHole = (initHole1+zl)>=4?((initHole1+zl)-4):(initHole1+zl); this.outHole = (initHole2+zl)>=4?((initHole2+zl)-4):(initHole2+zl); } else if(this.style===2){ var zl = this.endangle/(0.5*Math.PI); var initHole1 = 1 , initHole2 = 2; this.inHole = (initHole1+zl)>=4?((initHole1+zl)-4):(initHole1+zl); this.outHole = (initHole2+zl)>=4?((initHole2+zl)-4):(initHole2+zl); } } } //判断水管连通性 function connectPipes(){ var index = 0; while(1){ var result = getHole(boxes[index]); if(boxes[index+result.nextBox]){ if(result.hole===boxes[index+result.nextBox].inHole){ index = index+result.nextBox; } else if(result.hole===boxes[index+result.nextBox].outHole){ var num = boxes[index+result.nextBox].inHole; boxes[index+result.nextBox].inHole = result.hole; boxes[index+result.nextBox].outHole = num; index = index+result.nextBox; } else { break; } } else { break; } } if(index===boxes.length-1) return true; else return false; } function getHole(box){ var hole="0"; var nextBox=0; switch(box.outHole){ case 0 : hole = 2; nextBox = -cols; break; case 1 : hole = 3; if(box.coordinate.cols===cols-1){ nextBox = 1000000; } else nextBox = 1; break; case 2 : hole = 0; nextBox = cols; break; case 3 : hole = 1; if(box.coordinate.cols===0){ nextBox = 1000000; } else nextBox = -1; break; } return {hole:hole , nextBox:nextBox}; } //数组扩展循环遍历方法 Array.prototype.foreach = function(callback){ for(var i=0;i<this.length;i++){ callback.apply(this[i] , [i]); } } window.addEventListener("click" , function(){ if(!canclick) return false; else { canclick = false; var x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - canvas.offsetLeft; var y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop - canvas.offsetTop; boxes.foreach(function(){ if(x>=(this.center.x-boxWidth/2)&&x<=(this.center.x+boxWidth/2)&&y>=(this.center.y-boxWidth/2)&&y<=(this.center.y+boxWidth/2)&&!this.Gateway){ this.endangle = this.endangle + 0.5*Math.PI; activityBox = this; } }); toolBoxes.foreach(function(){ if(this.constructor===Box&&x>=(this.center.x-boxWidth/2)&&x<=(this.center.x+boxWidth/2)&&y>=(this.center.y-boxWidth/2)&&y<=(this.center.y+boxWidth/2)&&this.style===3){ this.endangle = this.endangle + 4*Math.PI; } }) } },false) //获得随机数,如果c不为空则为获取除c之外的随机数 function getRandom(a , b , c){ if(c!==null){ var random = 0; while(1){ var num = Math.round(Math.random()*(b-a)+a) if(num !== c){ random = num; break; } } return random; } else { return Math.round(Math.random()*(b-a)+a); } } var Painter = { drawHaoZe:function(){ //绘制饮水器 ctx.save(); ctx.fillStyle = "rgba(0,0,0,0.8)"; ctx.fillRect(315 , 105+marginTop , 172 , 172); ctx.strokeRect(315 , 105+marginTop , 172 , 172); ctx.fillStyle = "#FFF"; ctx.font = "20px 微软雅黑"; ctx.textAlign = "center"; ctx.textBaseLine = "middle"; ctx.fillText("净水器" , canvas.width/2 , canvas.height/2); ctx.restore(); }, drawBackGround:function(){ ctx.save(); ctx.fillStyle = "#BB8350"; ctx.fillRect(0 , boxes[0].center.y-boxWidth/2-jiange , boxWidth*cols+2*jiange , boxWidth*rows+2*jiange); for(var i=0;i<rows;i++){ for(var j=0;j<cols;j++){ ctx.drawImage(document.getElementById("pipe0") , (j*boxWidth)+jiange , (i*boxWidth)+marginTop , boxWidth , boxWidth); } } ctx.restore; }, drawWaterBox:function(){ var wb1 = new WaterBox({x:canvas.width/2 , y:jiange+50} , false , canvas.width-(2*jiange) , 100) var wb2 = new WaterBox({x:canvas.width/2 , y:canvas.height-jiange-50} , true , canvas.width-(2*jiange) , 100) toolBoxes.push(wb1); toolBoxes.push(wb2); }, drawOutPipe:function(){ var t1 = new Box({x:boxes[0].center.x , y:boxes[0].center.y-boxWidth} , 1 , true , 0); var t2 = new Box({x:boxes[0].center.x , y:boxes[0].center.y-boxWidth} , 3 , false , 0); var t3 = new Box({x:boxes[boxes.length-1].center.x , y:boxes[boxes.length-1].center.y+boxWidth} , 1 , true , 0); toolBoxes.push(t1); toolBoxes.push(t2); toolBoxes.push(t3); } } var WaterBox = function(center , isgood , width , height){ this.center = center; this.isgood = isgood; this.w = width; this.h = height; this.waterHeight = isgood?0:this.h*2/3; this.start = false; this.end = false; } WaterBox.prototype = { constructor:WaterBox, draw:function(){ ctx.save(); ctx.strokeStyle = "#000"; ctx.strokeRect(this.center.x-this.w/2 , this.center.y-this.h/2 , this.w , this.h); ctx.fillStyle = this.isgood?"rgba(52,160,209,1)":"rgba(164,164,164,1)"; if(this.start) { this.isgood?this.addWater():this.reduceWater(); if(this.isgood){ var h = this.waterHeight===this.h*2/3?0:this.h ctx.fillRect(this.center.x+(this.w/2-66) , this.center.y-this.h/2 , 37 , h) if(h===0){this.end = true} } } ctx.fillRect(this.center.x-this.w/2 , (this.center.y-this.h/2)+(this.h-this.waterHeight) , this.w , this.waterHeight); ctx.restore(); }, reduceWater:function(){ this.waterHeight = this.waterHeight<=0?0:this.waterHeight-0.5; }, addWater:function(){ this.waterHeight = this.waterHeight>=this.h*2/3?this.h*2/3:this.waterHeight+0.5; } } //动画初始化 function initAnimation(){ for(var i=0;i<rows;i++){ for(var j=0;j<cols;j++){ var b; //低难度 // var b = new Box({x:(j*boxWidth)+boxWidth/2+jiange , y:(i*boxWidth)+boxWidth/2+marginTop} , 0 , true , 0 , {rows:i , cols:j}); //高难度 if(i>0&&i<3&&j>2&&j<5){ b = new Box({x:(j*boxWidth)+boxWidth/2+jiange , y:(i*boxWidth)+boxWidth/2+marginTop} , 1 , true , 0.5*Math.PI , {rows:i , cols:j}); } else { b = new Box({x:(j*boxWidth)+boxWidth/2+jiange , y:(i*boxWidth)+boxWidth/2+marginTop}, getRandom(0,2) , false , parseInt(getRandom(0,3))*0.5*Math.PI , {rows:i , cols:j}); } boxes.push(b); } } var n = getRandom(0 , allPath.length-1); var path = allPath[n]; path.foreach(function(){ var index = this.rows*cols + this.cols; if((this.rows==0&&this.cols==0)||(this.rows==(rows-1)&&this.cols==(cols-1))){ boxes[index] = new Box({x:(this.cols*boxWidth)+boxWidth/2+jiange , y:(this.rows*boxWidth)+boxWidth/2+marginTop} , this.style , true , 0 , {rows:this.rows , cols:this.cols}); } else if(this.rows>0&&this.rows<3&&this.cols>2&&this.cols<5){ boxes[index] = new Box({x:(this.cols*boxWidth)+boxWidth/2+jiange , y:(this.rows*boxWidth)+boxWidth/2+marginTop} , this.style , true , 0.5*Math.PI , {rows:this.rows , cols:this.cols}); } else{ boxes[index] = new Box({x:(this.cols*boxWidth)+boxWidth/2+jiange , y:(this.rows*boxWidth)+boxWidth/2+marginTop} , this.style , false , parseInt(getRandom(0,3))*0.5*Math.PI , {rows:this.rows , cols:this.cols}); } }); Painter.drawWaterBox(); Painter.drawOutPipe(); animate(); } //动画每一帧的发生舞台 function animate(){ ctx.clearRect(0,0,canvas.width,canvas.height) Painter.drawBackGround(); //如果是当前运动中的水管块则跳过,当其他水管块绘制完后再单独绘制运动中的水管块,以保证其不被其他拼图覆盖 var number; boxes.foreach(function(index){ if(this == activityBox){ number = index; } else this.draw(); }); if(activityBox) boxes[number].draw(); toolBoxes.foreach(function(){ if(this.constructor===WaterBox&&connectSuccess){ this.start = true; if(this.end){ alert("你赢啦") window.location.reload(); } } this.draw(); }) Painter.drawHaoZe(); RAF(animate) } //动画兼容性写法 window.RAF = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000 / 60); }; })(); initAnimation(); } })(); window.onload = PipeGame(); </script> </body> </html>
楼主学canvas还不久,发的东西均为学习,代码有不当之处请见谅
原文:http://www.cnblogs.com/axes/p/3552683.html