C++开发斗地主(QT)第四篇之手牌分类

本篇将讲的是发到牌以后对手上的牌进行处理。在讲之前,我把本游戏开发要用到的声音 图片下载地址给大家:https://download.csdn.net/download/keepmoving0407/12363899

同时我也刚刚创建了一个QQ学习交流群:1005923608

我们先看一下LoadCards对象类

class LordCards
{
public:
    static int getMinSerialLength(int mainNum);
    static int getMaxSubNum(int mainNum);
    static int getDupSubNum(int mainNum);
    static int getCardSuit(int card);
    static int getCardValue(int v);
    static bool updateHandForNode(OneHand & best, OneHand &left, CardNode & node, bool isTrim);
public:
    LordCards(class CardGame * game,int id, const std::vector<int>&vec);
    LordCards(class CardGame * game, int id,int cards[], int num);
    ~LordCards();
    LordCards & operator = (const LordCards & other);
    void assign(class CardGame * game, const std::vector<int>&vec);
    void assign(class CardGame * game, int cards[], int num);

public:
    float winRateIfLord();
    bool  bigEnough();
    std::vector<int> removeSubset(const std::vector<int> & subset);
    ///手牌扫描
    int scanToTable();
public:
    std::string getKey(bool checkFlower, int &leastValue, int &maxCount);
    bool containsFlower(int value, int num);
    bool collectNode(CardNode & one, int value, int num);
    OneHand    calcPowerByRemoveNode(const CardNode & node);
    void       checkRocket (const std::string & key, OneHand & hand);
    void       checkBomb4 (const std::string & key, OneHand & hand, int top);
    void       checkSerial (const std::string & key, OneHand & hand, int top, int mainNum, int len, int subNum);
    void       checkSub (const std::string & key, OneHand & hand, int mainNum, int subNum, int poss);
    OneHand    calcPowerValue_noFlower();
    OneHand    calcPowerValue_expandAny(int countAny, int cardIndex);
    OneHand    calcPowerValue(bool checkFlower=false);
    //打出牌
    void        playcards(CardNode cards);
    CardNode   typeAndValueFind();
public:
    void collectAllNodes(std::set<CardNode> &possNodes, CardNode & node, int dup);
    void sortByFactorInNodes(std::vector<CardNode> &allNodes, const CardNode & other, bool isDirect);
    void                 getGreaterNodes_expandAny(int countAny, int cardIndex, std::set<CardNode> &possNodes, const CardNode &other);
    void                 getGreaterNodes_possNode(std::set<CardNode> &possNodes, const CardNode &other);
    std::vector<CardNode>  getNodesGreaterThan(const CardNode & node);
    //选择最好的出牌
    CardNode   getBestCardNode(CardNode simple=CardNode());
    void  getGreaterNodes_simple(std::set<CardNode> &possNodes, const CardNode &other);
    int get_GroupData();
public:
    class CardGame * theGame;
    //位置ID
    int id;
    CardNode curretCardNode;
    std::vector<int> theCards;
    //叫地主的倍(1 2 3)
    int multiple=-1;
    //一共打了几手牌,用于算“春天”
    int playTimes=0;
    int cardWeight;
    void sort();
    std::vector<int> m_fillCards[kCard_TableMax];
    //kCard_KindMax表示牌的面值大小
    //kCard_KindMax
    //0数组表示每张牌的数量
    //1表示单张序列,顺子 值>4表示顺子
    //2表示对子 值>1表示边对
    //3三条 值为>1表示飞机
    //4炸弹
    int cardsTable[kCard_KindMax][kCard_TableMax];     // 保存每牌面值的数目,比如A的牌有几张
};

看了这么多函数,担吓人,这就向通往山顶的阶梯,要一步一步上去!

本篇主要讲 int scanToTable()这个对牌进行扫描分类函数 

对牌的种类存储 int cardsTable[kCard_KindMax][kCard_TableMax];

kCard_KindMax的值为5 即0-4

kCard_TableMax的值为30其实只用到了3-17 

cardsTable[0][kCard_TableMax]存的是每张牌的个没有就是0 三张就是 3,如cardsTable[0][15]=3表示有三个2,cardsTable[0][16]=1表示有小王cardsTable[0][14]=2表示有2个A

cardsTable[1][kCard_TableMax]存的是否有顺子

cardsTable[2][kCard_TableMax]存的是否有双顺

cardsTable[3][kCard_TableMax]存的是否有飞机

cardsTable[4][kCard_TableMax]存的是否有炸弹

 

std::vector<int> m_fillCards[kCard_TableMax]数组是对牌的值(1-54)存储

 scanToTable()函数:

//对手牌进行扫描
int LordCards::scanToTable(){
    //初始化数组为0
    memset(cardsTable, 0, sizeof(cardsTable));
    //清空数组
    for (int i=0; i<kCard_ValueMax; ++i) {
        m_fillCards[i].clear();
    }
    //花牌个数统计 这里没有用到花牌
    int countAny = 0;
    //对牌到进行从小到大面值排序
    std::sort(theCards.begin(), theCards.end(), cardLessThan);
    for (int i=0; i<theCards.size(); i++) {
        //如果是花牌加1
        if (theCards[i] == kCard_Flower) {
            ++countAny;
            continue;
        }
        int val = LordCards::getCardValue(theCards[i]);//得到一个牌的面值(3-17)
        cardsTable[0][val]++;//某张牌个个数加1 如 val=15  那么数组 cardsTable[0][15]=0+1=1 下次再遇到就是再1等于2
        m_fillCards[val].push_back(theCards[i]);//这里存的牌的基本值 如黑桃2值为28
    }    
    for (int i = kCard_Value3; i <= kCard_ValueJoker2; ++i)//从3-17循环
    {
        for (int num = 4; num >= 1; --num)//从四张到单张循环
        {
            if (cardsTable[0][i] >= num)//如果有这个牌 并且数量为num
            {
                if (i <= kCard_ValueA)//如果小于等于A
                {
                    //这个是看牌是否连续,比如 cardsTable[num][5]=1 
                    //cardsTable[num][6]是不等于2 cardsTable[num][7]是否等于3 用来判断是否有顺子  双顺  飞机的
                    cardsTable[num][i] = cardsTable[num][i - 1] + 1;
                }
                else
                {
                    cardsTable[num][i] = 1;//有一个就是1
                }
            } 
            else 
            {
                cardsTable[num][i] = 0;//没有就是0
            }
        }
    }    
    //这个返回值是花牌的个数 没有用上
    return countAny;
}

如下牌

通过scanToTable()后

                                           3,4,5,6,7,8,9,10,J,Q,K,A,2,小,大

"cardsTable[0]的值为:0,0,0,2,2,1,0,1,2,2 ,3,   1,0,1,1,0,0,  1,0,0"//记录了第张牌的数量

"cardsTable[1]的值为:0,0,0,1,2,3,0,1,2,3, 4,5,0,1,2,0,0, 1,0,0" //发现一个顺子(有连续5连):7 8 9 10 J

"cardsTable[2]的值为:0,0,0,1,2,0,0,0,1,2,3,0,0,0,0,0,0,0,0,0"//发现一个双顺( 有对子3连):88 99 10 10

"cardsTable[3]的值为:0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0"//发现一个3条

"cardsTable[4]的值为:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"

 

m_fillCards这里存储了每张牌的大值

"数组[0]的值为"

"数组m_fillCards[1]的值为"

"数组m_fillCards[2]的值为"

"数组m_fillCards[3]的值为:16,42"//两个3

"数组m_fillCards[4]的值为:17,30"

"数组m_fillCards[5]的值为:5"

"数组m_fillCards[6]的值为"

"数组m_fillCards[7]的值为:7"

"数组m_fillCards[8]的值为:34,47"

"数组m_fillCards[9]的值为:35,48"

"数组m_fillCards[10]的值为:10,36,49"//三个10

"数组m_fillCards[11]的值为:24"

"数组m_fillCards[12]的值为"

"数组m_fillCards[13]的值为:52"

"数组m_fillCards[14]的值为:40"

"数组m_fillCards[15]的值为"

"数组m_fillCards[16]的值为"

"数组m_fillCards[17]的值为:54"//一个王

"数组m_fillCards[18]的值为"

"数组m_fillCards[19]的值为"

有了牌的这些信息,我们就可以计计算一手牌可以 分几次出,怎样出最花算。本篇就讲这里,下一篇将讲牌的权重,一个牌型的权重是多少?打了这个牌对整个牌的影响力是多少?第一次出哪个牌型最花算等等....

C++写的基于MFC界面的斗地主小游戏源码,内含详细注释,附带了简单的AI出牌规则,放出来供大家参考交流。vs2010编写,vs2015测试可用,理论上vs05及以上都可正常编译运行。 void Judge::MainFlow() { switch(DataCenter::Instance().GetPlayState()) { case EM_LandHolderBorn_PlayState: { //先检查是否已经问完了 //遍历玩家检查是否已经询问过了,如果已经都问过了,则设置叫分最高的为地主 BOOL bAllAsked = TRUE;//是否已经询问完了 vector & vecPlayer = DataCenter::Instance().GetPlayerList(); for (UINT i = 0; i m_nCurHighstScore) { m_nCurHighstScore = vecPlayer[i].GetLandOwerScore(); m_pToBeLandOwer = &vecPlayer;[i]; } if (vecPlayer[i].GetLandOwerScore() SetLandOwer(TRUE); } //然后根据情况执行询问流程 //如果地主已经产生,则跳入下一阶段 if (NULL != DataCenter::Instance().GetLandOwner()) { m_pCurPlayer = NULL; DataCenter::Instance().SetPlayState(EM_WaitPlayer_PlayState); MainFlow(); return; } //如果当前player为空,设置当前player为地主牌得主 if (m_pCurPlayer == NULL) { m_pCurPlayer = DataCenter::Instance().GetLandOwnerCardHolder(); } //对当前玩家执行地主问询 ASSERT(m_pCurPlayer); m_pCurPlayer->ExcuteCallLandOwer(); } break; case EM_WaitPlayer_PlayState: { //如果游戏已经结束,则执行结束逻辑 BOOL bLandOwerWin = FALSE; if (DataCenter::Instance().IsOver(bLandOwerWin)) { if (bLandOwerWin) { AfxMessageBox(_T("地主赢了!")); } else { AfxMessageBox(_T("佃户赢了!")); } DataCenter::Instance().SetPlayState(EM_WaitToStart_PlayState); //将所有玩家明牌 DataCenter::Instance().ShowAllPlayerCard(); RefreshView(); return; } //如果是出牌阶段而当前player为空,设置当前player为地主,并发予底牌 if (m_pCurPlayer == NULL) { m_pCurPlayer = DataCenter::Instance().GetLandOwner(); DataCenter::Instance().SendOutBottomCard(); RefreshView(); } ASSERT(m_pCurPlayer); m_pCurPlayer->ExcuteCallCardPlay(); } break; } } void Judge::CurPlayerCallScore(int nScore) { if (m_pCurPlayer == NULL) { ASSERT(FALSE); return; } //将玩家选择的分数设置给玩家 m_pCurPlayer->SetLandOwerScore(nScore); //如果当前玩家为空,直接返回 if(m_pCurPlayer == NULL) { return; } if (nScore == 3) { //如果玩家叫了三分,直接设为地主 m_pCurPlayer->SetLandOwer(TRUE); } else { //玩家叫的不是三分,则记下玩家叫的分数 m_pCurPlayer->SetLandOwerScore(nScore); } if (nScore == 0) { CString strWord; strWord.Format(_T("不叫")); m_pCurPlayer->Say(strWord); } else { CString strWord; strWord.Format(_T("%d分"), nScore); m_pCurPlayer->Say(strWord); } //玩家叫分后隐藏叫地主按钮 Judge::Instance().ShowCallLandOwerBtn(FALSE); //切换到下一个玩家,流程继续 SwitchToNextPlayer(); MainFlow(); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值