Unity3D手機斗地主游戲開發實戰(04)_出牌判斷大小


之前我們實現了叫地主、玩家和電腦自動出牌主要功能,但是還有個問題,出牌的時候,沒有有效性檢查和比較牌力大小。比如說,出牌3,4,5,目前是可以出牌的,然后下家可以出任何牌如3,6,9。

  • 問題1:出牌檢查有效性,就是出牌類型判斷,像單張、對子、順子、炸彈等等類型;
  • 問題2:上家出牌后,下家再出牌的時候,要判斷當前牌力是否大於上家的牌力;

那本篇我們主要解決以上2個問題。

卡牌信息類重構

首先,原先的卡牌類,已經實現了單張卡牌牌力的比較,但是有些復雜,我們先對這個比較邏輯進行優化。思路是卡牌的cardIndex就表示在此類型卡牌中的大小權重,所有,在初始化卡牌的過程中,對cardIndex進行特殊的處理:

cardIndex=(卡牌的原始索引+10)%13

比如:普通牌3--cardIndex=(3+10)%13=0,轉換后排最小

普通牌J--cardIndex=(11+10)%13=8,轉換后排第8張,如此,我們就可以輕松比較cardIndex,來判斷單牌的實際大小了。

再看看優化后的代碼,是不是比以前簡潔很多?更主要的是,為了方便后面的復雜組合牌力的判斷。

public class CardInfo : IComparable
{
    public string cardName; //卡牌圖片名
    public CardTypes cardType; //牌的類型
    public int cardIndex;      //牌在所在類型的索引3-10,J,Q,K,A,2(0-12)
    public bool isSelected;    //是否選中


    public CardInfo(string cardName)
    {
        this.cardName = cardName;
        var splits = cardName.Split('_');

        switch (splits[1])
        {
            case "1":
                cardType = CardTypes.Hearts;
                cardIndex = (int.Parse(splits[2]) + 10) % 13;
                break;
            case "2":
                cardType = CardTypes.Spades;
                cardIndex = (int.Parse(splits[2]) + 10) % 13;
                break;
            case "3":
                cardType = CardTypes.Diamonds;
                cardIndex = (int.Parse(splits[2]) + 10) % 13;
                break;
            case "4":
                cardType = CardTypes.Clubs;
                cardIndex = (int.Parse(splits[2]) + 10) % 13;
                break;
            case "joker":
                cardType = CardTypes.Joker;
                cardIndex = (int.Parse(splits[2]) + 10) % 13;
                break;
            default:
                throw new Exception(string.Format("卡牌文件名{0}非法!", cardName));
        }
    }

    //卡牌大小比較
    public int CompareTo(object obj)
    {
        CardInfo other = obj as CardInfo;

        if (other == null)
            throw new Exception("比較對象類型非法!");

        //如果當前是大小王
        if (cardType == CardTypes.Joker)
        {
            //對方也是大小王
            if (other.cardType == CardTypes.Joker)
            {
                return cardIndex.CompareTo(other.cardIndex);
            }
            //對方不是大小王
            return 1;
        }
        //如果是一般的牌
        else
        {
            //對方是大小王
            if (other.cardType == CardTypes.Joker)
            {
                return -1;
            }
            //如果對方也是一般的牌
            else
            {
                //計算牌力
                if (cardIndex == other.cardIndex)
                {
                    return -cardType.CompareTo(other.cardType);
                }

                return cardIndex.CompareTo(other.cardIndex);
            }
        }
    }

}
View Code

出牌類型基類

接下來,那怎么判斷出牌有效性和出牌的牌力大小判斷呢?

我們這樣想,每次出牌都是一組牌堆,那我們首先要判斷這一組牌堆的類型,比如3帶1,單張、炸彈等等;

其次,確定牌堆類型后,我們需要判斷這個牌堆是否是合法的,其實就遍歷驗證是否符合上述的牌堆類型,如果滿足,就認為合法,如果所有類型都不滿足,則出牌無效,不允許出牌;

再者,怎么判斷下家出牌的牌力要大於上家呢,這里我們還得有一個方法,判斷相同類型的2個牌堆,牌堆1是否比牌堆2牌力大;

最后,為了實現電腦出牌的AI,還得有自動查找1個跟上家出牌類型一樣的牌堆,而且比上家的牌堆大,如果有這種牌,則可以壓住上家,否則自動過牌;

有了整體思路,我們這樣設計,先定義一個虛基類--出牌類型基類,這里定義了所有需要子類牌堆類型實現的方法

    /// <summary>
    /// 出牌類型基類
    /// </summary>
    public abstract class FollowCardsBase
    {
        /// <summary>
        /// 驗證類型
        /// </summary>
        /// <returns></returns>
        public abstract bool Validate(List<CardInfo> cardInfos);
        /// <summary>
        /// 找到最小滿足的牌組
        /// </summary>
        /// <returns></returns>
        public abstract List<CardInfo> FindBigger(List<CardInfo> handCardInfos, List<CardInfo> cardInfos);
        /// <summary>
        /// 判斷是否牌大過要比較的牌組
        /// </summary>
        /// <param name="handCardInfos"></param>
        /// <param name="cardInfos"></param>
        /// <returns></returns>
        public abstract bool IsBigger(List<CardInfo> handCardInfos, List<CardInfo> cardInfos);
    }
}
View Code

Validate方法:子類實現驗證出牌是否滿足此類型;

IsBigger方法:給定2個出牌的牌堆,判斷此類型牌堆1是否滿足牌力大於牌堆2;

FindBigger方法:在給定的手牌中,找出符合類型中滿足牌力大於給定牌堆的組合;

這樣,我們定義好基類,再利用子類去實現各自的方法,比如對子牌類型的子類,我們判斷如果是對子,出牌的時候Validate判斷是否也是對子,如果出牌是對子,而且IsBigger

,就允許出牌;當然AI出牌的時候,通過FindBigger,找到滿足對子類型,且比給定的對子牌力大的牌組,進行后續出牌操作。

定義各個類型牌型

因為斗地主涉及的牌型有很多,我們可以簡單歸納一下。

我這里把單張和順子作為一種牌組類型來實現,因為考慮順子其實就是一種單張的特殊情景,只是約束條件是大於等於連續5張的單張牌。所以對子和連對、3帶1或3帶1的飛機等等,就都可以歸為一類,個人感覺實現會簡單些。

這里還是以單張和順子舉例:

        /// <summary>
        /// 驗證類型
        /// </summary>
        /// <returns></returns>
        public override bool Validate(List<CardInfo> cardInfos)
        {
            cardInfos.Sort();

            if (cardInfos.Count == 1)   //單張
            {
                return true;
            }
            else if (cardInfos.Count >= 5)  //順子
            {
                //如果最大的牌是王或者2,則不是順子
                if (cardInfos.Last().cardType == CardTypes.Joker || cardInfos.Last().cardIndex == 12)
                    return false;

                for (int i = 0; i < cardInfos.Count - 2; i++)
                {
                    if (cardInfos[i].cardIndex + 1 != cardInfos[i + 1].cardIndex)
                        return false;
                }
                return true;
            }
            return false;
        }
View Code

按照上一節所述,我們牌組類型子類,首先需要實現Validate方法,來驗證是否屬於單張或順子。

  • 如果是一張牌,毫無疑問,是單張
  • 如果是大於等於5張牌,最大的牌不是王或者2,而且是連續的牌,則是順子
  • 其他情況肯定不是單張或順子
        /// <summary>
        /// 判斷是否牌大過要比較的牌組
        /// </summary>
        /// <param name="handCardInfos"></param>
        /// <param name="cardInfos"></param>
        /// <returns></returns>
        public override bool IsBigger(List<CardInfo> handCardInfos, List<CardInfo> cardInfos)
        {
            cardInfos.Sort();
            handCardInfos.Sort();

            //牌數一樣且最小牌比要比較的牌組的最小牌大
            if (handCardInfos.Count == cardInfos.Count && Validate(handCardInfos) && Validate(cardInfos))
            {
                if (handCardInfos[0].CompareTo(cardInfos[0]) > 0)
                    return true;
            }
            return false;
        }
View Code

接着,怎么判斷2組牌組的牌力大小呢?

  • 第一步,將2牌組按照從小到大排序
  • 判斷2牌組的牌數是否一樣
  • 判斷2牌組的類型是否都是單張或順子
  • 如果滿足以上2個條件,再判斷2牌組的第一張大小
  • 如果牌組1的第一張大於牌組2的第一張,則可以認為牌組1牌力大於牌組2
  • 反之也成立
  • 否則,如果相等,則牌力一樣
        /// <summary>
        /// 找到最小滿足的牌組
        /// </summary>
        /// <returns></returns>
        public override List<CardInfo> FindBigger(List<CardInfo> handCardInfos, List<CardInfo> cardInfos)
        {
            cardInfos.Sort();
            handCardInfos.Sort();

            if (cardInfos.Count == 1)   //單張
            {
                var cardInfo = handCardInfos.FirstOrDefault(s => s.CompareTo(cardInfos[0]) > 0 && s.cardIndex != cardInfos[0].cardIndex);
                if (cardInfo != null)
                {
                    var result = new List<CardInfo>();
                    result.Add(cardInfo);
                    return result;
                }
                return null;
            }

            else if (cardInfos.Count >= 5)  //順子
            {
                var count = handCardInfos.Count - cardInfos.Count;

                if (count >= 0)
                {
                    //手牌比牌組多count,則有count + 1可能滿足牌組
                    for (int i = 0; i < count + 1; i++)
                    {
                        var mayBiggerCardInfos = handCardInfos.Skip(i).Take(cardInfos.Count).ToList();
                        //是順子,且最小的牌比要比較的牌組最小牌要大
                        if (Validate(mayBiggerCardInfos) && mayBiggerCardInfos[0].CompareTo(cardInfos[0]) > 0 && mayBiggerCardInfos[0].cardIndex != cardInfos[0].cardIndex)

                        {
                            return mayBiggerCardInfos;
                        }
                    }
                    return null;
                }
                return null;
            }
            return null;
        }
View Code

再來看,我們怎么根據給定的單張或順子,在手牌中找到對應牌力大於上家的牌組;

這里提供了一個簡單的實現方式,找到最小滿足的牌組,當然,如果要實現智能的電腦出牌的AI,或者智能提示出牌,肯定要復雜很多,就不在探討范圍內了。

  • 第一步,將2牌組按照從小到大排序
  • 如果上家牌是單張,那簡單,從手牌中找到第1張滿足牌力大於上家牌的單張即可;
  • 如果上家牌是順子,判斷手牌比上家牌的牌數只差,比如手牌17張,上家牌是5張的順子,那就是17-5=12,理論上有12+1最多可能滿足的牌組組合;
  • 那從第一張手牌開始,遍歷12+1次,每次取當前手牌后的5張牌,判斷是否比上家牌牌力大,如果不滿足,則繼續下輪遍歷;
  • 如果找到,則返回該牌組,可以認為這牌組牌力大於上家牌,且類型為單張或順子,牌數跟上家牌一樣;
  • 如果遍歷完還沒找到,認為手牌中沒有大於上家牌的牌組,游戲玩家可以跳過次回合;

至此,我們的單張和順子類型的出牌檢驗邏輯,大體上就完成了。接下來,我們只需要稍微修改下之前的出牌邏輯~

出牌邏輯調整

這里需要調整2處,一方面,在玩家出牌時,要增加判斷,如果玩家選擇的牌堆,牌力不夠,需要提示玩家,很簡單,不在復述;

再來看電腦出牌的邏輯,我們在PlayOthre類中,增加以下這段代碼:

if (CardManager._instance.cardManagerState == CardManagerStates.Playing)
            {
                if (Input.GetKeyDown(KeyCode.Q)) //出牌
                {
                    var singleCards = new SingleCards();
                    var cardInfos = singleCards.FindBigger(this.cardInfos, CardManager._instance.currentCardInfos);
                    if (cardInfos != null)
                    {
                        cardInfos.ForEach(s => s.isSelected = true);
                        ForFollow();
                    }
                    else
                    {
                        NotFollow();
                    }
                }
            }
View Code

電腦AI出牌的時候,通過FindBigger,去匹配手牌中有沒有滿足牌力大於上家牌的牌組類型,如果有,將返回的牌組出出去,如果沒有,則自動過牌。

那現在玩家出牌、電腦AI出牌就可以正常處理了,我們來看一下效果:

寫在最后

我們的斗地主游戲開發有段時間了,本文就作為階段性的結束篇~當然,遠算不上是一個成品游戲,我只是簡單的實現了部分功能,有些代碼現在看來還是偷工減料。要完成一個成品游戲,需要大量的精力和時間,條件也不允許,以后有機會的話,可能會對這個項目完善和優化。

這是我第一次寫一個系列的東西,寫得不好請大家多多包涵~推出這個系列,我的主旨是想和大家分享下Unity3D開發一個斗地主游戲的整體架構和設計模式,雖然是個半成品,但是有些代碼我是真的用心去寫的,盡量的把我實現的思路和對游戲的理解展現給大家。就像一千個人眼中有一千個哈姆雷特,由於我們對游戲理解的不同,同樣的斗地主游戲,不同的開發者,實現的方法可能就是不同的。我能做的,我希望做的,就是把個人的思想剖析給你們,如果你們能理解,或者對你們有所幫助,也是我的幸事。

最后,謝謝各位的一路陪伴。也歡迎大家關注,我們一同學習,共同進步,以后會堅持出新的系列!

資源

項目源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM