1. 程式人生 > 實用技巧 >小遊戲之莫交叉

小遊戲之莫交叉

做這個小遊戲的靈感來源於...那時候在西永上班的幾個同事,一個設計健哥,一個後臺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關的鎮樓圖吧,太燒腦了,目前我還沒去研究最優解題的思路,後面哪天興趣來了再去考慮。

最後我覺得還是要把健哥的設計貼出來,做個紀念,雖然我們最終沒能一起完成這個專案,但是我還是很欣賞你的設計的,不僅限於這個莫交叉:)