小遊戲之莫交叉
做這個小遊戲的靈感來源於...那時候在西永上班的幾個同事,一個設計健哥,一個後臺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關的鎮樓圖吧,太燒腦了,目前我還沒去研究最優解題的思路,後面哪天興趣來了再去考慮。
最後我覺得還是要把健哥的設計貼出來,做個紀念,雖然我們最終沒能一起完成這個專案,但是我還是很欣賞你的設計的,不僅限於這個莫交叉:)