做这个小游戏的灵感来源于...那时候在西永上班的几个同事,一个设计健哥,一个后台wei哥,另一个产品wei哥,再加上我这个前端,组成的一个开发小组,有一段时间我想自己写一个网络上比较火的贪吃蛇游戏耍,我就把想法和健哥讨论了下,结果不得了,估计大家工作都做得快(PS人事的艳姐看到这里莫敲我们脑壳,公司的事情点都没耽误:)),就七嘴八舌的一起讨论,就说要不然我们几个一起来开发一款益智游戏,当然我的贪吃蛇提议就肯定没被采纳,因为都感觉简单了,虽然我考虑的贪吃蛇并不是大众所认为的那样,最终应该是健哥提议的莫交叉这个游戏,我们找了几个网上的范本玩了玩,感觉还可以,就打算从这个游戏入手,想打下我们几个人的小游戏江山,我只能说终究我们还是太年轻了,虽然我最终把游戏原型做出来了,健哥把设计也画出来了,但是还是因为要做工作,慢慢的就不了了之,N年后的今天,我突然想到我最近看得最强大脑这个节目,上面就有很多这种类似的“小游戏”,早就手痒想盗版做几个了,不知怎么就联想到了当初做的那个莫交叉游戏,在N个云盘、服务器上翻箱倒柜,终于还找到了当初做的源码,自己也再拾掇拾掇,打算还是写个博客,纪念一下N年前那个夏天的idea。
游戏的玩法,找了下百度,书名叫做“不交叉”,具体的解释链接是https://baike.baidu.com/item/%E4%B8%8D%E4%BA%A4%E5%8F%89/1581989?fr=aladdin,简单的说就是理清N个点之间的连线,除了点的交叉,没有线的交叉,随着初始点越来越多,线就越来越难理清楚,玩到后面还是有点烧脑,下面我们先来看下一个简单的起始面:
现在我们初始是5个点,拖动每个点的时候,每个点相关联的线都会跟着移动,绿色的线是不交叉的,红色的线是因为有交叉,我们需要把所有的线都变成绿色就成功了,下面有个简单的动画演示效果
可能大家看了这个动画,就感觉这个游戏很简单,是的,确实很简单,我曾经也这样天真的认为,那是因为我当时还没想清楚如果去开发这个游戏,其实最开始我觉得这个游戏可能有点难,我首先需要考虑3个问题:1、每个点都需要去连接线段,但是如何去连,可以看出有的点之间是没有连线的;2、点的移动是实时的,那么线的移动也是实时的,如何去考虑性能问题;3、根据第2点的问题,如何去判定游戏最终是成功的。当时我想如果把这3个问题解决了,应该就能考虑出代码思路了,然而还是too young了,我突然想到,点是随机生成,我怎么样才能知道哪些点之间要连线呢?突然就卡在这里了,不过考虑考虑,就在想,出这个游戏应该是要有解的吧(PS在写到这里的时候,突然冒出了一个想法,如果可以选择无解,那游戏的难度又会上一个台阶),那我为什么不先把正确的结果连接出来,然后再去随机移动初始点,再让用户去移动解答,这样就直接把思路理清楚了,至于第2点性能问题,貌似只能用Canvas了,所以就选择了createjs框架。
其实整体的游戏核心代码很简洁,总共才300多行,而且都是纯前端代码,那时候为了快速出效果,后面也没维护,所以就没怎么写注释,将就着看。
首先我们先定义画布(html):
1 <div class="box"> 2 <canvas id="gameView" width="600" height="600" style="background-color:rgba(0,0,0,0.6)"></canvas> 3 </div>
初始化系列参数(js):
1 var stage = new createjs.Stage("gameView"); 2 createjs.Touch.enable(stage,true,false); 3 var containerLine = new createjs.Container; 4 var containerOther = new createjs.Container; 5 stage.addChild(containerLine,containerOther); 6 var arr = []; 7 var lineArr = []; 8 var indexArr = []; 9 var jiaocha = false; 10 var rightArr = []; 11 var level = 5;
重置方法:
1 var restart = function(){ 2 containerLine.removeAllChildren() 3 containerOther.removeAllChildren() 4 arr = []; 5 lineArr = []; 6 indexArr = []; 7 jiaocha = false; 8 rightArr = []; 9 start(); 10 }
初始化界面上的点
1 var start = function(){ 2 for (var i =0;i<level;i++) { 3 var circle = new createjs.Shape(); 4 circle.graphics.beginFill("green").drawCircle(0, 0, 10); 5 let w = Math.random()*bgW; 6 let h = Math.random()*bgH; 7 let x = w; 8 let y = h; 9 circle.x = x; 10 circle.y = y; 11 circle.index = i; 12 circle.addEvent(i); 13 containerOther.addChild(circle); 14 arr.push(circle) 15 16 var point = {}; 17 point.x = x;point.y = y;point.index = i; 18 rightArr.push(point); 19 20 var text = new createjs.Text(i, "20px Arial", "#ff7700"); 21 text.x = x;text.y = y; 22 text.textBaseline = "alphabetic"; 23 containerOther.addChild(text) 24 stage.update(); 25 } 26 drawLine1(); 27 for(var j=0;j<level;j++){ 28 randomLine(); 29 } 30 drawLine2(); 31 } 32 start();
初始化方法里面的两个随机连线方法randomLine,drawLine1,drawLine2
1 var randomLine = function(){ 2 containerLine.removeAllChildren(); 3 var rand = Math.floor(Math.random()*arr.length); 4 var x = Math.random()*bgW; 5 var y = Math.random()*bgH; 6 for(var i = 0;i<lineArr.length;i++){ 7 var p3 = lineArr[i].p3; 8 var p4 = lineArr[i].p4; 9 if(rand == p3.index){ 10 lineArr[i].p3.x = x; 11 lineArr[i].p3.y = y; 12 } 13 if(rand == p4.index){ 14 lineArr[i].p4.x = x; 15 lineArr[i].p4.y = y; 16 } 17 18 } 19 arr[rand].x = x; 20 arr[rand].y = y; 21 stage.update(); 22 }
1 var drawLine1 = function(){ 2 //在每个点的时候都要去找一下另外的匹配不交叉点 3 for(var i=0;i<arr.length;i++){ 4 var p1 = {},p2 = {}; 5 p1.x = arr[i].x,p1.y = arr[i].y,p1.index = arr[i].index; 6 7 var count = Math.floor(Math.random()*level)+3; 8 for(let j=0;j<arr.length;j++){ 9 if(i!=j){ 10 p2.x = arr[j].x,p2.y = arr[j].y,p2.index = arr[j].index; 11 var result = checkLine(p1,p2,lineArr); 12 if(result == false){ 13 //先检查点有多少个了,多了就不画了 14 let countP = checkPointCount(p1,p2); 15 if(countP<=count) 16 { 17 var line = new createjs.Shape(); 18 line.graphics.setStrokeStyle(2).beginStroke("green"); 19 line.graphics.moveTo(p1.x, p1.y); 20 line.graphics.lineTo(p2.x, p2.y); 21 line.graphics.endStroke(); 22 containerLine.addChild(line); 23 stage.update(); 24 var point = []; 25 let p3={},p4={}; 26 p3.x = p1.x,p3.y = p1.y,p3.index = p1.index; 27 p4.x = p2.x,p4.y = p2.y,p4.index = p2.index; 28 point.p3 = p3;point.p4 = p4; 29 lineArr.push(point) 30 31 if(indexArr.indexOf(p1.index)==-1){ 32 indexArr.push(p1.index) 33 } 34 if(indexArr.indexOf(p2.index)==-1){ 35 indexArr.push(p2.index) 36 } 37 } 38 } 39 } 40 } 41 42 var c = checkPointCount(p1,p2); 43 console.log(i,c) 44 } 45 }
1 var drawLine2 = function(){ 2 jiaocha = false; 3 for(var i = 0;i<lineArr.length;i++){ 4 var p1 = lineArr[i].p3;var p2 = lineArr[i].p4; 5 var result = false; 6 for(var j=0;j<lineArr.length;j++){ 7 if(i!=j){ 8 var p3 = lineArr[j].p3;var p4 = lineArr[j].p4; 9 if(checkCross(p1,p2,p3,p4)==true){ 10 if((p1.x == p3.x && p1.y == p3.y) || (p1.x == p4.x && p1.y == p4.y) || (p2.x == p3.x && p2.y == p3.y) || (p2.x == p4.x && p2.y == p4.y)) 11 { 12 result = false; 13 } 14 else 15 { 16 result = true; 17 jiaocha = true; 18 break; 19 } 20 } 21 } 22 } 23 var line = new createjs.Shape(); 24 //要在这里判断每条线的交叉,然后再变颜色和做判断 25 line.graphics.setStrokeStyle(2).beginStroke(result==true?"red":"green"); 26 line.graphics.moveTo(p1.x, p1.y); 27 line.graphics.lineTo(p2.x, p2.y); 28 line.graphics.endStroke(); 29 containerLine.addChild(line); 30 stage.update(); 31 } 32 if(jiaocha==false){ 33 document.getElementsByClassName("over")[0].style.display = "block"; 34 } 35 }
在drawLine1的时候还有一个检查点的方法checkPointCount
1 var checkPointCount = function(p1,p2){ 2 var count = 0; 3 for(var i = 0;i<lineArr.length;i++){ 4 var p3 = lineArr[i].p3; 5 var p4 = lineArr[i].p4; 6 if(p1.index == p3.index || p1.index == p4.index || p2.index == p3.index || p2.index == p4.index){ 7 count++; 8 } 9 } 10 return count; 11 }
当上面的完成后,就成了我们初始化的界面,level是初始的点,我也用来当成通关数,接下来就是移动点的操作了,给每一个点增加一个移动事件,并在每一个点移动的时候去调用drawLine2方法重绘每一条关联的线
1 createjs.Shape.prototype.addEvent = function(count){ 2 var _this = this; 3 this.addEventListener("mouseover",function(){ 4 alert(); 5 }) 6 this.addEventListener("pressmove",function(_this){ 7 if(jiaocha==false){ 8 return; 9 } 10 var cir = _this.target; 11 var nowx = _this.stageX,nowy = _this.stageY; 12 cir.x = nowx;cir.y = nowy; 13 stage.update(); 14 containerLine.removeAllChildren(); 15 //这里要改变移动了点的信息 16 var index = _this.target.index; 17 for(var i = 0;i<lineArr.length;i++){ 18 if(lineArr[i].p3.index == index){ 19 lineArr[i].p3.x = nowx;lineArr[i].p3.y = nowy; 20 } 21 if(lineArr[i].p4.index == index){ 22 lineArr[i].p4.x = nowx;lineArr[i].p4.y = nowy; 23 } 24 } 25 drawLine2() 26 }) 27 }
在drawLine2方法里面有一个jiaocha变量,根据checkCross方法做一个判断所有lineArr数组里面的线是否有交汇,判断交汇方法如下:
1 var checkCross = function(p1, p2, p3, p4) { 2 var v1 = { 3 x: p1.x - p3.x, 4 y: p1.y - p3.y 5 }, 6 v2 = { 7 x: p2.x - p3.x, 8 y: p2.y - p3.y 9 }, 10 v3 = { 11 x: p4.x - p3.x, 12 y: p4.y - p3.y 13 }, 14 v = crossMul(v1, v3) * crossMul(v2, v3) 15 v1 = { 16 x: p3.x - p1.x, 17 y: p3.y - p1.y 18 } 19 v2 = { 20 x: p4.x - p1.x, 21 y: p4.y - p1.y 22 } 23 v3 = { 24 x: p2.x - p1.x, 25 y: p2.y - p1.y 26 } 27 var result = (v <= 0 && crossMul(v1, v3) * crossMul(v2, v3) <= 0) ? true : false; 28 if(result){ 29 if((p1.x == p3.x && p1.y == p3.y) || (p1.x == p4.x && p1.y == p4.y) || (p2.x == p3.x && p2.y == p3.y) || (p2.x == p4.x && p2.y == p4.y)) 30 { 31 result = false; 32 } 33 else 34 { 35 result = true; 36 } 37 } 38 return result; 39 }
1 var crossMul = function(v1, v2) { 2 return v1.x * v2.y - v1.y * v2.x; 3 }
到这里,整个游戏的思路就已经结束了,整体来说代码量不多,但是游戏是真的好玩,有兴趣的同学可以玩一下(在线地址:http://hudong.miaos.me/xxoo/ko_all.html),因为要涉及到很多点的操作,所以在PC上面操作体验会更好一些。另外目前的这种方法其实对于性能的问题并没有得到很好的解决,因为目前采用框架对于重绘是非常消耗资源,最后来一个30关的镇楼图吧,太烧脑了,目前我还没去研究最优解题的思路,后面哪天兴趣来了再去考虑。
最后我觉得还是要把健哥的设计贴出来,做个纪念,虽然我们最终没能一起完成这个项目,但是我还是很欣赏你的设计的,不仅限于这个莫交叉:)