最新文章
Cocos2d-x游戏开发实例详解7:对象释放时机
03-25 13:59
Cocos2d-x游戏开发实例详解6:自动释放池
03-25 13:55
Cocos2d-x游戏开发实例详解5:神奇的自动释放
03-25 13:49
Cocos2d-x游戏开发实例详解4:游戏主循环
03-25 13:44
Cocos2d-x游戏开发实例详解3:无限滚动地图
03-25 13:37
Cocos2d-x游戏开发实例详解2:开始菜单续
03-25 13:32
Cocos2d-x制作《单机斗地主》源码解剖7:出牌与跟牌(完)
上一课我们对玩家手中的牌进行了分析归类,接下来将实现电脑玩家出牌与跟牌的策略。
出牌策略
首先来看 update 函数,它是一个循环,每帧都会被调用一次。以下是该函数的代码:
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 函数
该函数用于清除所有出的牌,代码如下:
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 函数
这是电脑出牌策略函数,这里只是简单地判断,打出排在第一位置牌值最小的牌型。代码如下:
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 函数
这是跟牌策略函数,代码如下:
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);
}
其中代码就不一一分析了,请自行下载源码阅读。