上一课我们研究对玩家手中的牌进行分析归类,下面就该实现电脑玩家出牌与跟牌的策略了。首先我们来看看出牌的策略,代码如下:

void GameScene::update(float delta){  
    switch (m_iState)  
    {  
        case 0:  
            SendPk();  
            break;  
        case 1:  
            schedule(schedule_selector(GameScene::Call),1);  
            break;  
        case 2:  
            scheduleOnce(schedule_selector(GameScene::OutCard),0.5);  
            break;  
        case 3:  
            IsShengLi();  
            break;  
        default:  
            break;  
    }  
}

首先解释下该函数,本函数为一个循环,每帧被调用一次。我们看一下头文件里m_iState的注释:

int m_iState;//当前状态 ,0:发牌状态 1:叫地主状态 2:出牌状态 3:结果状态

很明显,出牌和跟牌策略就在状态2,该函数延时0.5秒出牌。我们接下来看下OutCard函数的策略:

void GameScene::OutCard(float delta){  
    switch (m_iOutCard%3)  
    {  
        case 0:  
            m_chuPaiMenu->setVisible(true);//显示出牌菜单,包括”不出“,”出牌“  
            m_typeTem = PaiDuanPaiXing();//获得玩家出的牌的牌型,这个函数在cocos2dx《单机斗地主》源码解剖之六 玩家(人)的出牌中有解释。  
            if(!m_npcOne->getIsOutPk() && !m_npcTwo->getIsOutPk())//如果两个电脑玩家没出过牌,设”不出“按钮不可点,反应则然。  
                ((CCMenuItemFont *)m_chuPaiMenu->getChildByTag(0))->setEnabled(false);  
            else  
                ((CCMenuItemFont *)m_chuPaiMenu->getChildByTag(0))->setEnabled(true);  
            //出牌  
            if(!m_npcOne->getIsOutPk() && !m_npcTwo->getIsOutPk())  
            {  
                //清除所有出的牌  
                ClearOutPk();//下面贴代码  
                if (m_typeTem != ERROR_CARD)//ERROR_CARD为错误的牌型  
                    ((CCMenuItemFont *)m_chuPaiMenu->getChildByTag(1))->setEnabled(true);  
                else  
                    ((CCMenuItemFont *)m_chuPaiMenu->getChildByTag(1))->setEnabled(false);  
            }  
            else //跟牌  
            {  
                if(m_arrPlayerOut->count() != 0)  
                {  
                    Poker* pk = (Poker*)m_arrGenPk->objectAtIndex(0);//要跟的牌  
                    Poker* pk1 = (Poker*)m_arrPlayerOut->objectAtIndex(0);//玩家出的牌  
                    if(m_typeTem == m_type && pk1->getNum()>pk->getNum() || (m_typeTem==BOMB_CARD && m_type!=BOMB_CARD))//m_type为跟的牌的牌型  
                        ((CCMenuItemFont *)m_chuPaiMenu->getChildByTag(1))->setEnabled(true);  
                    else  
                        ((CCMenuItemFont *)m_chuPaiMenu->getChildByTag(1))->setEnabled(false);  
                }  
                else  
                    ((CCMenuItemFont *)m_chuPaiMenu->getChildByTag(1))->setEnabled(false);  
            }  
              
            break;  
        case 1:  
            m_chuPaiMenu->setVisible(false);  
            if(!m_player->getIsOutPk() && !m_npcOne->getIsOutPk())  
            {  
                //清除所有出的牌  
                ClearOutPk();  
                NpcOutPoker(m_npcTwo,m_arrGenPk,m_npcTwoOut);//电脑出牌策略,函数下面解释。  
            }  
            else  
                NpcGenPoker(m_npcTwo,m_arrGenPk ,m_npcTwoOut);//电脑跟牌策略,函数下面解释。  
            PlayerOutPaiXu(m_arrGenPk);//对要跟的牌进行排序,该函数在cocos2dx《单机斗地主》源码解剖之六 玩家(人)的出牌有解释。  
            PlayerOutPaiXu(m_npcTwoOut->getArrPk());//对电脑玩家出的牌进行排序  
            m_npcTwoOut->updatePkWeiZhi();//更新位置  
            m_npcTwo->updatePkWeiZhi();//同上  
            ++m_iOutCard;  
            if(IsOutPkFinish())//判断游戏是否结束,下面解释。  
                m_iState = 3;  
            break;  
        case 2:  
            if(!m_player->getIsOutPk() && !m_npcTwo->getIsOutPk())  
            {  
                //清除所有出的牌  
                ClearOutPk();  
                NpcOutPoker(m_npcOne,m_arrGenPk,m_npcOneOut);  
            }  
            else  
                NpcGenPoker(m_npcOne,m_arrGenPk,m_npcOneOut);  
            PlayerOutPaiXu(m_arrGenPk);  
            PlayerOutPaiXu(m_npcTwoOut->getArrPk());  
            m_npcOneOut->updatePkWeiZhi();  
            m_npcOne->updatePkWeiZhi();  
            ++m_iOutCard;  
            if(IsOutPkFinish())  
                m_iState = 3;  
            break;  
        default:  
            break;  
    }  
}

首先介绍一下这个状态机,我们看头文件对m_iOutCard变量的定义:int m_iOutCard;//论到谁出牌,0为玩家出牌与跟牌的策略,1和2为电脑玩家出牌与跟牌的策略。他们的意义已在代码里添加注释。

在上面代码中你一定发现了有些令人费解的函数(ClearOutPk(),NpcOutPoker(m_npcTwo,m_arrGenPk,m_npcTwoOut),NpcGenPoker(m_npcTwo,m_arrGenPk ,m_npcTwoOut),IsOutPkFinish()),下面一一解释:

ClearOutPk()的代码:

void GameScene::ClearOutPk()  
{  
    CCObject* object;
//清除玩家出的牌
CCARRAY_FOREACH(m_playerOut->getArrPk(),object){  
    Poker* pk = (Poker*)object;  
    pk->setVisible(false);  
}  
m_playerOut->getArrPk()->removeAllObjects();
//清除电脑玩家出的牌  
    CCARRAY_FOREACH(m_npcTwoOut->getArrPk(),object){  
        Poker* pk = (Poker*)object;  
        pk->setVisible(false);  
    }  
    m_npcTwoOut->getArrPk()->removeAllObjects();
//同上  
    CCARRAY_FOREACH(m_npcOneOut->getArrPk(),object){  
        Poker* pk = (Poker*)object;  
        pk->setVisible(false);  
    }  
    m_npcOneOut->getArrPk()->removeAllObjects();  
    this->getChildByTag(NpcOneBuChu)->setVisible(false);  
    this->getChildByTag(NpcTwoBuChu)->setVisible(false);  
}

NpcOutPoker(m_npcTwo,m_arrGenPk,m_npcTwoOut) 电脑出牌策略:

电脑出牌策略我这里只是简单的判断,打出排在第一位置牌值最小的牌型。

void GameScene::NpcOutPoker(Player* npc,CCArray* out,Player* out1){  
    //隐藏上一次出的牌  
    CCObject* object;  
    CCARRAY_FOREACH(out1->getArrPk(),object){ //out1为上一次出的牌  
        Poker* pk = (Poker*)object;  
        pk->setVisible(false);  
    }  
    out1->getArrPk()->removeAllObjects();  
    //打出牌值最小的一个牌型,也就是排在第一位置的牌型  
    PaiXing px = npc->m_vecPX.front();  
    out->removeAllObjects();  
    //三条出牌原则  
    if(px.type == THREE_CARD){  
        stable_sort(npc->m_vecPX.begin(),npc->m_vecPX.end(),isShorter1);  
        m_type = THREE_CARD;  
        //带单  
        for(std::vector<PaiXing>::iterator iter=npc->m_vecPX.begin();iter!=npc->m_vecPX.end();++iter)  
        {  
            //除非只剩两手牌,否则不能带王和2  
            Poker* pk = iter->vec.front();  
            if(pk->getNum() >= Er && npc->m_vecPX.size() != 1)  
                break;  
            if(iter->type == SINGLE_CARD)  
            {  
                out1->getArrPk()->addObject(iter->vec.front());  
                out->addObject(iter->vec.front());  
                npc->getArrPk()->removeObject(iter->vec.front());  
                npc->m_vecPX.erase(iter);  
                m_type = THREE_ONE_CARD;  
                break;  
            }  
        }  
        //带双  
        if(out1->getArrPk()->count() == 0)  
        {  
            for(std::vector<PaiXing>::iterator iter=npc->m_vecPX.begin();iter!=npc->m_vecPX.end();++iter)  
            {  
                //除非只剩两手牌,否则不能带王和2  
                Poker* pk = iter->vec.front();  
                if(pk->getNum() >= Er && npc->m_vecPX.size() != 1)  
                    break;  
                if(iter->type == DOUBLE_CARD)  
                {  
                    for(std::vector<Poker*>::iterator it=iter->vec.begin();it!=iter->vec.end();++it)  
                    {  
                        out1->getArrPk()->addObject(*it);  
                        out->addObject(*it);  
                        npc->getArrPk()->removeObject(*it);  
                    }  
                    npc->m_vecPX.erase(iter);  
                    m_type = THREE_TWO_CARD;  
                    break;  
                }  
            }  
        }  
    }  
    //三顺出牌原则  
    if(px.type == AIRCRAFT_CARD){  
        //有足够的单就带单  
        stable_sort(npc->m_vecPX.begin(),npc->m_vecPX.end(),isShorter1);  
        m_type = AIRCRAFT_CARD;  
        if(GetNpcPxNum(npc,SINGLE_CARD) >= px.vec.size()/3)  
        {  
            int num=0;  
            for (std::vector<PaiXing>::iterator it=npc->m_vecPX.begin(); it!=npc->m_vecPX.end()&&num<px.vec.size()/3;)  
            {  
                if(it->type == SINGLE_CARD)  
                {  
                    ++num;  
                    out1->getArrPk()->addObject(it->vec.front());  
                    out->addObject(it->vec.front());  
                    npc->getArrPk()->removeObject(it->vec.front());  
                    it = npc->m_vecPX.erase(it);  
                    m_type = AIRCRAFT_SINGLE_CARD;  
                }  
                else  
                    ++it;  
            }  
        }  
        //有足够的双就带双  
        if(GetNpcPxNum(npc,DOUBLE_CARD) >= px.vec.size()/3 && out1->getArrPk()->count() == 0)  
        {  
            int num=0;  
            for (std::vector<PaiXing>::iterator it=npc->m_vecPX.begin(); it!=npc->m_vecPX.end()&&num<px.vec.size()/3;)  
            {  
                if(it->type == DOUBLE_CARD)  
                {  
                    ++num;  
                    for(std::vector<Poker*>::iterator ite=it->vec.begin(); ite!=it->vec.end(); ++ite)  
                    {  
                        out1->getArrPk()->addObject(*ite);  
                        out->addObject(*ite);  
                        npc->getArrPk()->removeObject(*ite);  
                        m_type = AIRCRAFT_DOBULE_CARD;  
                    }  
                    it = npc->m_vecPX.erase(it);  
                }  
                else  
                    ++it;  
            }  
        }  
    }  
    //连牌出牌原则,直接出,不做处理  
    if(px.type == CONNECT_CARD){  
        m_type = CONNECT_CARD;  
    }  
    //双顺出牌原则,直接出,不做处理  
    if(px.type == COMPANY_CARD){  
        m_type = COMPANY_CARD;  
    }  
    //对子和单子出牌原则  
    if(px.type == DOUBLE_CARD || px.type == SINGLE_CARD){  
        int threeNum = GetNpcPxNum(npc,THREE_CARD)+GetNpcPxNum(npc,AIRCRAFT_CARD);  
        int chiBangNum = GetNpcPxNum(npc,DOUBLE_CARD)+GetNpcPxNum(npc,SINGLE_CARD);  
        //所有三条<=所有对子+所有单牌-2,出对子,否则出三带对  
        if(threeNum <= chiBangNum-2 || threeNum == 0)  
        {  
            if(px.type == DOUBLE_CARD)  
                m_type = DOUBLE_CARD;  
            if(px.type == SINGLE_CARD)  
                m_type = SINGLE_CARD;  
        }  
        else  
        {  
            PaiXing px = npc->m_vecPX.front();  
            std::vector<PaiXing>::iterator dle = npc->m_vecPX.begin();  
            npc->m_vecPX.erase(dle);  
            npc->m_vecPX.push_back(px);  
            NpcOutPoker(npc,out,out1);  
            return;  
        }  
    }  
    for(std::vector<Poker*>::iterator iter=px.vec.begin(); iter!=px.vec.end(); ++iter)  
    {  
        out1->getArrPk()->addObject(*iter);  
        out->addObject(*iter);  
        npc->getArrPk()->removeObject(*iter);  
        npc->setIsOutPk(true);  
    }  
    m_lastOut = npc;  
    //从npc->m_vecPX中移除px  
    for(std::vector<PaiXing>::iterator it=npc->m_vecPX.begin();it!=npc->m_vecPX.end();++it)  
    {  
        if(it->type == px.type && it->vec.front()->getNum() == px.vec.front()->getNum())  
        {  
            npc->m_vecPX.erase(it);  
            break;  
        }  
    }  
}

NpcGenPoker(m_npcTwo,m_arrGenPk ,m_npcTwoOut),IsOutPkFinish())跟牌策略:

void GameScene::NpcGenPoker(Player* npc,CCArray* out ,Player* out1){  
      
    //隐藏上一次出的牌  
    if(m_isChiBang)  
    {  
        CCObject* object;  
        CCARRAY_FOREACH(out1->getArrPk(),object){  
            Poker* pk = (Poker*)object;  
            pk->setVisible(false);  
        }  
        out1->getArrPk()->removeAllObjects();  
    }  
    /************************************************************************/  
    /*找出对应牌型出牌                                                      */  
    /************************************************************************/  
    for (std::vector<PaiXing>::iterator iter=npc->m_vecPX.begin(); iter!=npc->m_vecPX.end(); ++iter)  
    {  
        if(m_type == iter->type)  
        {  
            //对飞机、连牌进行判断  
            if(m_type == AIRCRAFT_CARD || m_type == CONNECT_CARD || m_type == COMPANY_CARD)  
                if(out->count() != iter->vec.size())  
                    continue;  
            Poker* pk = (Poker*)out->objectAtIndex(out->count()-1);  
            Poker* pk1 = iter->vec.front();  
            //如果对方是自己人大于2的牌不出  
            if(!npc->getIsDiZhu() && !m_lastOut->getIsDiZhu())  
            {  
                if(pk1->getNum()>=Er || m_type == BOMB_CARD)  
                {  
                    //pass  
                    if(npc == m_npcOne)  
                        this->getChildByTag(NpcOneBuChu)->setVisible(true);  
                    if(npc == m_npcTwo)  
                        this->getChildByTag(NpcTwoBuChu)->setVisible(true);  
                    npc->setIsOutPk(false);  
                    return;  
                }  
            }  
            if(pk1->getNum() > pk->getNum())  
            {  
                out->removeAllObjects();  
                for(std::vector<Poker*>::iterator it = iter->vec.begin(); it!=iter->vec.end(); ++it){  
                    out1->getArrPk()->addObject(*it);  
                    npc->getArrPk()->removeObject(*it);  
                    out->addObject(*it);  
                }  
                npc->m_vecPX.erase(iter);  
                npc->setIsOutPk(true);  
                m_lastOut = npc;  
                return;  
            }  
        }  
    }  
    //三带一或三带二  
    if(SanDaiYiOrEr(npc,out,out1))  
        return;  
    //四带单或四带双  
    //飞机带单或带双  
    if(FeiJiDaiChiBang(npc,out,out1))  
        return;  
    /************************************************************************/  
    /*如果除炸弹还剩一手牌                                                  */  
    /************************************************************************/  
    if(npc->m_vecPX.size() == 2)  
    {  
        for (std::vector<PaiXing>::iterator iter=npc->m_vecPX.begin(); iter!=npc->m_vecPX.end(); ++iter)  
        {  
            if(iter->type == BOMB_CARD && m_type != BOMB_CARD)  
            {  
                out->removeAllObjects();  
                for(std::vector<Poker*>::iterator it = iter->vec.begin(); it!=iter->vec.end(); ++it){  
                    out1->getArrPk()->addObject(*it);  
                    npc->getArrPk()->removeObject(*it);  
                    out->addObject(*it);  
                }  
                npc->m_vecPX.erase(iter);  
                m_lastOut = npc;  
                return;  
            }  
        }  
    }  
    /************************************************************************/  
    /* 如果出牌方是自己人不拆牌跟                                        */  
    /************************************************************************/  
    if(!npc->getIsDiZhu() && !m_lastOut->getIsDiZhu())  
    {  
        //pass  
        if(npc == m_npcOne)  
            this->getChildByTag(NpcOneBuChu)->setVisible(true);  
        if(npc == m_npcTwo)  
            this->getChildByTag(NpcTwoBuChu)->setVisible(true);  
        npc->setIsOutPk(false);  
        return;  
    }  
    /************************************************************************/  
    /*拆单张牌跟之                                                        */  
    /************************************************************************/  
    if(NpcChaiDan(npc,out,out1))  
        return;  
    /************************************************************************/  
    /*拆双牌跟之                                                        */  
    /************************************************************************/  
    if(NpcChaiDui(npc,out,out1))  
        return;  
    /************************************************************************/  
    /*拆三张牌跟之                                                        */  
    /************************************************************************/  
    if(NpcChaiSan(npc,out,out1))  
        return;  
    /************************************************************************/  
    /*拆飞机牌跟之                                                        */  
    /************************************************************************/  
    if(NpcChaiFeiJi(npc,out,out1))  
        return;  
    /************************************************************************/  
    /*拆连牌跟之                                                        */  
    /************************************************************************/  
    if(NpcChaiLianPai(npc,out,out1))  
        return;  
    /************************************************************************/  
    /*拆双顺跟之                                                        */  
    /************************************************************************/  
    if(NpcChaiShuangShun(npc,out,out1))  
        return;  
    //炸之  
    for (std::vector<PaiXing>::iterator iter=npc->m_vecPX.begin(); iter!=npc->m_vecPX.end(); ++iter)  
    {  
        if(iter->type == BOMB_CARD)  
        {  
            //如果出牌方出的不是炸弹就炸之,否则比较大小炸之  
            if(m_type != BOMB_CARD)  
            {  
                out->removeAllObjects();  
                for(std::vector<Poker*>::iterator it = iter->vec.begin(); it!=iter->vec.end(); ++it){  
                    out1->getArrPk()->addObject(*it);  
                    npc->getArrPk()->removeObject(*it);  
                    out->addObject(*it);  
                }  
                npc->m_vecPX.erase(iter);  
                m_type = BOMB_CARD;  
                npc->setIsOutPk(true);  
                m_lastOut = npc;  
                return;  
            }else  
            {  
                Poker* pk = (Poker*)out->objectAtIndex(0);  
                Poker* pk1 = iter->vec.front();  
                if(pk1->getNum()>pk->getNum())  
                {  
                    out->removeAllObjects();  
                    for(std::vector<Poker*>::iterator it = iter->vec.begin(); it!=iter->vec.end(); ++it){  
                        out1->getArrPk()->addObject(*it);  
                        npc->getArrPk()->removeObject(*it);  
                        out->addObject(*it);  
                    }  
                    npc->m_vecPX.erase(iter);  
                    m_type = BOMB_CARD;  
                    npc->setIsOutPk(true);  
                    m_lastOut = npc;  
                    return;  
                }  
            }  
              
        }  
    }  
    //pass  
      
    if(npc == m_npcOne)  
    {  
        this->getChildByTag(NpcOneBuChu)->setVisible(true);  
    }  
    if(npc == m_npcTwo)  
    {  
        this->getChildByTag(NpcTwoBuChu)->setVisible(true);  
    }  
    npc->setIsOutPk(false);  
}

其中代码就不一一分析了,请自行下载源码阅读。