javascript開發HTML5遊戲--鬥地主(單機模式part2)

最近學習使用了一款HTML5遊戲引擎(青瓷引擎),並用它嘗試作了一個鬥地主的遊戲,簡單實現了單機對戰和網絡對戰,代碼可已放到github上,在此談談本身如何經過引擎來開發這款遊戲的。javascript

 客戶端代碼html

   服務端代碼java

          (點擊圖片進入遊戲體驗)git

前文連接:github

javascript開發HTML5遊戲--鬥地主(單機模式part1)數組

本文章爲第二部份內容,主要包括髮牌、搶地主流程。主要內容以下:網絡

  1. 發牌
  2. 搶地主流程
  3. 肯定地主
  4. 手牌佈局問題

1、發牌

  發牌,就是取3張底牌,而後3個玩家各發17張牌,以前我把一副牌的信息都放置在了Scripts/logic/clone/Card.js中,也就是qc.landlord.Card類。存放在這個類的屬性data中,這是一個數組,數組中的對象有3個屬性,以下:dom

  • icon : 牌圖片文件名,用於顯示這張牌;
  • val : 牌值,也就是大小,因爲在鬥地主規則中除大小王外,2最大,A其次,因此這裏並不徹底按照數值排大小,A是14,2是15,小王16,大王17。其它牌本身的數值就是大小;
  • type : 花色,雖說在鬥地主中沒有花色大小問題,可是爲了手牌美觀,通常都會把同等大小的牌按照必定的順序(黑桃-紅心-草花-方塊)來排列。

  javascript的數組有許多強大的方法,在發牌這塊上算是不少都派上用場了,整個發牌流程的思路以下:ide

  1. 使用數組slice方法複製一副牌來發牌,保證原牌組不會變更;
  2. 使用數組splice方法結合隨機數,抽取一張牌,這裏利用splice作刪除時會以一個數組形式返回被刪除的幾個元素,獲得返回的對象後加入到對應玩家的手牌的數組中。這樣就不用去作一個洗牌的代碼,比如如買了一副新牌常常咱們都要洗一下再玩,可是也能夠不洗,三我的每人隨機從中抽一張,直到17張爲止,大概就是這個思路。
  3. 使用數組sort方法對每一個玩家手牌排序,這裏須要咱們本身寫一個數組元素的比較方法。由於按照上面的方法,每一個玩家的手牌都是亂序的,咱們要求是從大到小而且等值的按照花色排序,排序不只是爲了展現美觀,後面AI分析牌也用於判斷順子、連對之類的牌型,這裏給出牌比較的代碼,以下:
 1 /**  2  * 卡牌排序  3  * @method cardSort  4  * @param {Object} a [description]  5  * @param {Object} b [description]  6  * @return 1 : a < b ,-1 a : > b [description]  7  */
 8 GameRule.prototype.cardSort = function (a, b){  9     var va = parseInt(a.val); 10     var vb = parseInt(b.val); 11     if(va === vb){ 12         return a.type > b.type ? 1 : -1; 13     } else if(va > vb){ 14         return -1; 15     } else { 16         return 1; 17  } 18 };

  發牌的時候,利用定時器每0.2秒給每一個玩家都發一張牌,共發17張,這樣玩家就能夠看到一個發牌的動畫。左右邊的AI玩家不須要顯示牌,只須要顯示背面,因此每次只須要在各自的手牌容器中加一個牌的圖片就能夠;可是玩家本身的牌要按順序顯示,因此每次取牌,都要根據大小判斷位置再放進去。代碼在Scripts/ui/PlayUI.js中,這裏給主要的代碼,以下:佈局

 1 //發牌
 2 PlayUI.prototype.dealCards = function (){  3     var self = this,  4         cards = G.cardMgr.getNewCards();  5     //抽三張底牌
 6     for (var i = 0; i < 3; i++) {  7  G.hiddenCards.push(self.getOneCard(cards));  8  }  9     //總牌數
10     var total = 17; 11     var deal = function (){ 12         //左邊電腦玩家發牌
13         card = self.getOneCard(cards); 14  G.leftPlayer.cardList.push(card); 15         var c = self.game.add.clone(self.cardPrefab, self.leftPlayerArea.getScript('qc.engine.PlayerUI').cardContainer); 16         c.visible = true; 17         c.interactive = false; 18         //右邊電腦玩家發牌
19         card = self.getOneCard(cards); 20  G.rightPlayer.cardList.push(card); 21         c = self.game.add.clone(self.cardPrefab, self.rightPlayerArea.getScript('qc.engine.PlayerUI').cardContainer); 22         c.visible = true; 23         c.interactive = false; 24         //左邊電腦玩家發牌
25         //玩家的牌
26         card = self.getOneCard(cards); 27  G.ownPlayer.cardList.push(card); 28  self.insertOneCard(card); 29         if ( --total > 0) { 30             self.dealTimer = self.game.timer.add(200, deal); 31         } else { 32  G.leftPlayer.cardList.sort(G.gameRule.cardSort); 33  G.rightPlayer.cardList.sort(G.gameRule.cardSort); 34  G.ownPlayer.cardList.sort(G.gameRule.cardSort); 35             for (i = 0; i < G.currentCards.length; i++) { 36                 G.currentCards[i].getScript('qc.engine.CardUI').isSelected = false; 37  } 38             //進入搶地主階段
39  self.robLandlord(); 40  } 41  }; 42  deal(); 43 
44 };

 2、搶地主流程

  一、流程介紹

  搶地主,就是玩家輪換叫分的過程,代碼的流程以下:

二、AI手牌評分

  這裏實現的AI搶地主,先根據手牌對AI玩家進行手牌評分,若是評分大於上家的叫分,就叫分,不然不叫。總體思路是看了如下這篇文章來寫的,包括後面的AI出牌之類都是從這邊看的,文章連接:鬥地主ai設計

     叫牌原則分析
       由於在鬥地主中,火箭、炸彈、王和2能夠認爲是大牌,因此叫牌須要按照這些牌的多少來判斷。下面是一個簡單的原則,來自於上面這篇文章: 
       假定火箭爲8分,炸~彈爲6分,大王4分,小王3分,一個2爲2分,則當分數 
       大於等於7分時叫3分; 
       大於等於5分時叫2分; 
       大於等於3分時叫1分; 
       小於三分不叫

  我在Scripts/logci/AILogic.js下建立了AILogic類,在建立對象時須要傳入一個玩家對象,該類會對玩家手牌進行分析歸類,這些在AI出牌中再詳細闡述,這裏咱們先看看AI手牌評分的代碼吧。以下:

 1 /**  2  * 手牌評分,用於AI根據本身手牌來叫分  3  * @method function  4  * @return {[nmber]} 所評得分  5  */
 6 AILogic.prototype.judgeScore = function() {  7     var self = this,  8         score = 0;  9     score += self._bomb.length * 6;//有炸彈加六分
10     if(self._kingBomb.length > 0 ){//王炸8分
11         score += 8; 12     } else { 13         if(self.cards[0].val === 17){ 14             score += 4; 15         } else if(self.cards[0].val === 16){ 16             score += 3; 17  } 18  } 19     for (var i = 0; i < self.cards.length; i++) { 20         if(self.cards[i].val === 15){ 21             score += 2; 22  } 23  } 24     console.info(self.player.name + "手牌評分:" + score); 25     if(score >= 7){ 26         return 3; 27     } else if(score >= 5){ 28         return 2; 29     } else if(score >= 3){ 30         return 1; 31     } else {//4至關於不叫
32         return 4; 33  } 34 };

  三、輪換搶地主

  繼發牌完成以後,就進入到了搶地主階段,發完牌後隨機選取一個玩家開始叫分。因爲進入單機模式便給每個玩家添加了一個nextPlayer指向本身下一家,造成一個循環的引用,因此很容易找到本身下一家。若是是玩家則給玩家顯示叫分按鈕,AI則給出分數,主要代碼以下:

 1 /**  2  * 搶地主階段  3  * @method robLandlord  4  */
 5 PlayUI.prototype.robLandlord = function (){  6     var self = this;  7     //隨機獲取從哪一家開始
 8     var fb = G.gameRule.random(1,3);  9     var firstPlayer = fb === 1 ? G.ownPlayer : (fb == 2 ? G.rightPlayer : G.leftPlayer); 10  self.provideScore(firstPlayer); 11 }; 12 
13 /** 14  * 輪換叫分 15  * @method robLandlord 16  */
17 PlayUI.prototype.provideScore = function(player){ 18     var self = this; 19     if(player.isAI){//AI玩家隨機出分
20         self.scoreThree.visible = false; 21         self.scoreTwo.visible = false; 22         self.scoreOne.visible = false; 23         self.scoreZero.visible = false; 24         self.game.timer.add(1000, function (){ 25             var s = (new AILogic(player)).judgeScore(); 26             var area = player.nextPlayer.isAI ? window.landlordUI.rightCards : window.landlordUI.leftCards; 27             if(s < 4 && s > self.currentScore){//小於3分
28                 console.info(player.name + ":叫" + s); 29                 self.currentScore = s; 30                 self.scorePanel.text = s + ''; 31                 self.currentLandlord = player; 32                 //根據下家是不是AI判斷他的出牌區
33                 for (var i = 0; i < area.children.length; i++) {//清空
34  area.children[i].destroy(); 35  } 36                 var mesg = self.game.add.clone(self.msgPrefab, area); 37                 mesg.text = s + '分'; 38                 if(s === 3){//三分,得地主
39  self.setLandlord(player); 40                     return; 41  } 42             } else { 43                 var mesg = self.game.add.clone(self.msgPrefab, area); 44                 mesg.text = '不叫'; 45                 console.info(player.name + "沒有叫分搶地主"); 46  } 47             if(++self.round  === 3){//已經三次再也不進行
48                 if(self.currentLandlord){//有叫分的得地主
49  self.setLandlord(self.currentLandlord); 50                 } else {//沒有叫分,從新發牌
51  self.showRestartMesg(); 52  self.startGame(); 53  } 54             } else { 55  self.provideScore(player.nextPlayer); 56  } 57  }); 58     } else { 59         self.scoreZero.visible = true; 60         self.scoreThree.visible = true; 61         if(self.currentScore < 2) 62             self.scoreTwo.visible = true; 63         if(self.currentScore < 1) 64             self.scoreOne.visible = true; 65  } 66 }; 67 
68 /** 69  * 玩家給分(搶地主) 70  * @method function 71  * @return {[type]} [description] 72  */
73 PlayUI.prototype.playerProvideScore = function(score){ 74     var self = this; 75     if(score < 4){//小於3分
76         self.currentScore = score; 77         self.scorePanel.text = score + ''; 78         self.currentLandlord = G.ownPlayer; 79         var mesg = self.game.add.clone(self.msgPrefab, window.landlordUI.ownCards); 80         mesg.text = score + '分'; 81         if(score === 3){//三分,得地主
82  self.setLandlord(G.ownPlayer); 83             return; 84  } 85     } else { 86         var mesg = self.game.add.clone(self.msgPrefab, window.landlordUI.ownCards); 87         mesg.text = '不叫'; 88  } 89     if(++self.round  === 3){//已經三次再也不進行
90         if(self.currentLandlord){//有叫分的得地主
91  self.setLandlord(self.currentLandlord); 92         } else {//沒有叫分,從新發牌
93  self.showRestartMesg(); 94  self.startGame(); 95  } 96     } else { 97  self.provideScore(G.ownPlayer.nextPlayer); 98  } 99 };
View Code

   這裏的playerProvideScore方法是玩家叫分,玩家有四個叫分按鈕:1分、2分、3分、不叫,每一個按鈕事件都是調用這個方法,只是傳入不一樣的分數。詳細完整代碼能夠在github上查看,去玩玩這個遊戲結合代碼應該更好理解。

3、肯定地主

  完成搶地主後,肯定地主的環節也是有很多事情要作,主要是如下幾點:

  • 將底牌給地主,這裏AI玩家只要修改牌數量,玩家的則須要將3張底牌插入對應位置,保證順序
  • 顯示出底牌
  • 界面上標明各個玩家身份
  • 保存本局的分數,也就是地主叫的分數
  • 讓地主開始出牌

  代碼以下:

 1 //設置地主
 2 PlayUI.prototype.setLandlord = function(player){  3     var self = this;  4     self.scorePanel.text = self.currentScore + '';  5     self.scoreThree.visible = false;  6     self.scoreTwo.visible = false;  7     self.scoreOne.visible = false;  8     self.scoreZero.visible = false;  9     //顯示底牌
10     var oldHiddenCard = self.hiddenContainer.children; 11     for (var i = 0; i < self.hiddenContainer.children.length; i++) { 12         self.hiddenContainer.children[i].frame = G.hiddenCards[i].icon; 13  } 14     //self.startBtn.visible = false;
15     //設置地主及農民信息
16     G.ownPlayer.isLandlord = false; 17     G.leftPlayer.isLandlord = false; 18     G.rightPlayer.isLandlord = false; 19     player.isLandlord = true; 20  self.setAIStation(self.leftPlayerArea, G.leftPlayer.isLandlord); 21  self.setAIStation(self.rightPlayerArea, G.rightPlayer.isLandlord); 22     self.ownPlayerArea.getScript('qc.engine.PlayerUI').headPic.frame = G.ownPlayer.isLandlord ? 'landlord.png' : 'peasant.png'; 23     self.ownPlayerArea.getScript('qc.engine.PlayerUI').headPic.visible = true; 24     //把底牌給地主
25     player.cardList = player.cardList.concat(G.hiddenCards); 26  player.cardList.sort(G.gameRule.cardSort); 27  self.reDraw(); 28     if(!player.isAI){//不是AI須要從新渲染牌組
29         for (i = 0; i < G.hiddenCards.length; i++) { 30  self.insertOneCard(G.hiddenCards[i]); 31  } 32  } 33     for (i = 0; i < G.currentCards.length; i++) { 34         G.currentCards[i].getScript('qc.engine.CardUI').isSelected = false; 35  } 36     console.info('本輪地主是' + player.name); 37     //由地主開始出牌
38  window.landlordUI.cleanAllPlayArea(); 39  window.landlordUI.playCard(player); 40 };

4、手牌佈局問題

  

  完成上面的步驟後,遊戲也進入能夠愉快打牌的階段了,這裏分享下我在用青瓷引擎作手牌佈局顯示的時候遇到的些問題。如上圖,每一個區域的手牌,左右兩邊玩家顯示卻是問題不大,由於其只是顯示相應數量的牌,都以背面顯示,並不須要真正顯示牌。主要仍是在玩家手牌的問題,若是每張牌都咱們去控制佈局,會很繁瑣,我一開始就走了一個錯誤的路線,用這樣的方法:每張牌都放在手牌區域底下,每張牌設置不一樣的AnchoredX屬性值,來達到每張牌錯開的效果,可是會致使一些問題:

  • 每當玩家出牌後,須要對全部牌從新佈局
  • 每當玩家出牌後,剩下牌難以居中顯示
  • 要加入底牌時,因爲要找在對應位置,難以實現

  後面才發現青瓷引擎爲咱們提供了一個很好的佈局組件:表格佈局組件(點擊我看文檔),很好幫我實現了這個功能。真是一開始沒看全文檔,浪費了很多時間。實現的話,在牌的容器節點加入TableLayout組件,屬性設置以下圖,而後就只要往如圖cardList加子節點(卡牌圖片),刪除子節點(卡牌圖片),全部牌總體居中顯示,並且每張牌固定錯開30像素,不用作其餘任何事情,就達到了我想要的佈局效果。固然,左右兩邊玩家的手牌我也用了一樣的方式,只是用的是豎直的排列方式。

  肯定完了地主,就能夠進入玩牌了,我會在下一篇文章分享單機模式鬥地主剩下的流程。

相關文章
相關標籤/搜索