Cocos2d-x制作《单机斗地主》源码解剖5:玩家的出牌

2015年03月24日 08:50 0 点赞 0 评论 更新于 2025-11-21 18:21

当游戏玩家选择好要出的牌后,首先需要判断该牌型是否符合游戏规则,即是否合法。若牌型合法,则高亮显示“出牌”按钮;若不合法,则显示灰色“出牌”按钮,表示不可出牌。下面我们来详细探讨如何判断玩家所选择的牌型是否合法。

牌型判断函数 PaiDuanPaiXing

int GameScene::PaiDuanPaiXing() {
// 对出的牌进行排序
PlayerOutPaiXu(m_arrPlayerOut);
// 牌型判断
int length = m_arrPlayerOut->count();
PaiXing px;
// 牌的张数少于5张类型判断:单、对、三张、四张
if (length < 5 && length > 0) {
Poker* pk = (Poker*)m_arrPlayerOut->objectAtIndex(0);
Poker* pk1 = (Poker*)m_arrPlayerOut->objectAtIndex(length - 1);
if (pk->getNum() == pk1->getNum()) {
return length;
}
// 三带一
pk1 = (Poker*)m_arrPlayerOut->objectAtIndex(length - 2);
if (pk->getNum() == pk1->getNum() && length == 4) {
return THREE_ONE_CARD;
}
// 双鬼
if (pk->getHuaSe() == Gui && pk1->getHuaSe() == Gui) {
return BOMB_CARD;
}
}
// 牌的张数大于等于5张的类型判断
if (length >= 5) {
// 是否为连牌牌型(单)
if (IsLianPai()) {
return CONNECT_CARD;
}
if (IsLianDui()) { // 判断连对
return COMPANY_CARD;
}
// 判断飞机类型
return IsFeiJi();
}
return ERROR_CARD;
}

排序函数 PlayerOutPaiXu

在上述代码中,PlayerOutPaiXu(m_arrPlayerOut) 函数的作用是对玩家选出的牌 m_arrPlayerOut 进行排序,以便后续分析牌型。以下是该函数的具体实现:

void GameScene::PlayerOutPaiXu(CCArray* m_arrPlayerOut) {
// 对出的牌进行分离
std::vector<JiShu> vec; // JiShu是一个结构体,下面显示代码
while (m_arrPlayerOut->count() > 0) {
JiShu js;
js.arr = CCArray::create();
// 取出第一个
Poker* pk = (Poker*)m_arrPlayerOut->objectAtIndex(0);
m_arrPlayerOut->removeObjectAtIndex(0);
js.num = 1;
js.pkZhi = pk->getNum();
js.arr->addObject(pk);
// 找出与第一个相同的牌
int i = 0;
while (i < m_arrPlayerOut->count()) {
Poker* pk1 = (Poker*)m_arrPlayerOut->objectAtIndex(i++);
if (pk1->getNum() == pk->getNum()) {
++js.num;
js.arr->addObject(pk1);
m_arrPlayerOut->removeObject(pk1);
--i;
}
}
// 把js存储起来用于排序
vec.push_back(js);
}
// 对vec进行排序,按牌值从小到大排序
for (int i = 0; i < vec.size() - 1 &&!vec.empty(); ++i) {
for (int j = 0; j < vec.size() - i - 1; ++j) {
if (vec[j].pkZhi > vec[j + 1].pkZhi) {
JiShu temp = vec[j];
vec[j] = vec[j + 1];
vec[j + 1] = temp;
}
}
}
stable_sort(vec.begin(), vec.end(), isShorter); // 按牌的数量从小到大再排一次
// 将排序好的牌重新放入m_playerOut中
for (std::vector<JiShu>::iterator it = vec.begin(); it != vec.end(); ++it) {
m_arrPlayerOut->addObjectsFromArray(it->arr);
}
}

// 记数 排序出的牌用
struct JiShu {
int pkZhi; // 牌值
int num;   // 牌数量
CCArray* arr; // 集合牌
};

上述代码的综合思想是:判断出的牌 m_arrPlayerOut 里有几个相同的牌,并通过 JiShu 结构体记录下来,保存在 std::vector<JiShu> vec 中。然后按牌值和相同牌的数量进行排序,最后将排序好的牌放回 m_arrPlayerOut 中,方便后续分析牌型。例如,出的牌 66633 经过排序会变成 33666665543 经过排序变成 345566

牌型枚举类型

在代码中,return BOMB_CARD;return THREE_ONE_CARD; 等返回值是枚举变量,分别代表不同的牌型。以下是斗地主的 13 种牌型枚举定义:

// 斗地主共有13种牌型
enum CARD_TYPE {
SINGLE_CARD = 1,        // 单牌
DOUBLE_CARD,            // 对子
THREE_CARD,             // 3不带
BOMB_CARD,              // 炸弹
THREE_ONE_CARD,         // 3带1
THREE_TWO_CARD,         // 3带2
BOMB_TWO_CARD,          // 四个带2张单牌
BOMB_TWOOO_CARD,        // 四个带2对
CONNECT_CARD,           // 连牌
COMPANY_CARD,           // 连队
AIRCRAFT_CARD,          // 飞机不带
AIRCRAFT_SINGLE_CARD,   // 飞机带单牌
AIRCRAFT_DOBULE_CARD,   // 飞机带对子
ERROR_CARD              // 错误的牌型
};

牌型判断的详细分析

判断连牌 IsLianPai

IsLianPai() 函数用于判断是否为连牌,例如 34567。以下是该函数的代码:

bool GameScene::IsLianPai() {
int length = m_arrPlayerOut->count();
CCArray* arr = m_arrPlayerOut;
// 所有牌值必须小于2
CCObject* object;
CCARRAY_FOREACH(arr, object) {
if (((Poker*)object)->getNum() >= 12) { // 12代表牌值2,下面解释为什么
return false;
}
}
// 必须是连续的(前一张牌值加1是否等于后一张牌值)
for (int i = 0; i < length - 1; ++i) {
Poker* pk = (Poker*)arr->objectAtIndex(i);
Poker* pk1 = (Poker*)arr->objectAtIndex(i + 1);
if (pk->getNum() + 1 != pk1->getNum()) {
return false;
}
}
return true;
}

这里的牌值是从 0 开始设置的,例如 3 的牌值为 04 的牌值为 1,因此 12 代表牌值 2

判断连对 IsLianDui

以下是判断是否为连对的 IsLianDui() 函数代码:

bool GameScene::IsLianDui() {
int length = m_arrPlayerOut->count();
CCArray* arr = m_arrPlayerOut;
// 所有牌值必须小于2
CCObject* object;
CCARRAY_FOREACH(arr, object) {
if (((Poker*)object)->getNum() >= 12) {
return false;
}
}
// 大于等于6张牌并且数量为偶数
if (length < 6 || length % 2 != 0) {
return false;
}
// 必须是连续的
for (int i = 0; i < length - 2; i += 2) {
Poker* pk = (Poker*)arr->objectAtIndex(i);
Poker* pk1 = (Poker*)arr->objectAtIndex(i + 2);
if (pk->getNum() + 1 != pk1->getNum()) {
return false;
}
}
return true;
}

判断飞机类型 IsFeiJi

IsFeiJi() 函数用于判断飞机类型,需要注意的是,该函数返回的是一种飞机的类型,而不是 bool 值。以下是该函数的代码:

int GameScene::IsFeiJi() {
int length = m_arrPlayerOut->count();
CRAD_INDEX card_index = FenXiFeiJi(); // 分析牌是否是飞机,下面解释
// 判断三带二
if (card_index.three_index.size() * 3 + card_index.duble_index.size() * 2 == length &&
card_index.three_index.size() == 1 && card_index.duble_index.size() == 1) {
return THREE_TWO_CARD;
}
// 判断飞机
if (card_index.three_index.size() > 1 && card_index.four_index.empty() &&
IsFeiJiLian(card_index.three_index)) {
// 飞机不带
if (card_index.three_index.size() * 3 == length &&
card_index.duble_index.size() + card_index.single_index.size() == 0) {
return AIRCRAFT_CARD;
}
// 飞机带单
if (card_index.three_index.size() * 3 + card_index.single_index.size() == length &&
card_index.duble_index.size() == 0) {
return AIRCRAFT_SINGLE_CARD;
}
// 飞机带双
if (card_index.three_index.size() * 3 + card_index.duble_index.size() * 2 == length &&
card_index.single_index.size() == 0) {
return AIRCRAFT_DOBULE_CARD;
}
}
// 判断四带
if (card_index.three_index.empty() &&!card_index.four_index.empty() && length % 2 == 0) {
// 四带单
if (card_index.four_index.size() * 4 + card_index.single_index.size() == length &&
card_index.four_index.size() == 1 && card_index.single_index.size() == 2) {
return BOMB_TWO_CARD;
}
// 四带对
if (card_index.four_index.size() * 4 + card_index.duble_index.size() * 2 == length &&
card_index.four_index.size() == 1 && card_index.duble_index.size() == 1) {
return BOMB_TWOOO_CARD;
}
}
return ERROR_CARD;
}

struct CRAD_INDEX { // 分析飞机
std::vector<int> single_index; // 单张
std::vector<int> duble_index;  // 双张
std::vector<int> three_index;  // 三张
std::vector<int> four_index;   // 四张
};

牌型分类函数 FenXiFeiJi

FenXiFeiJi() 函数用于对出的牌进行分类,以下是该函数的代码:

CRAD_INDEX GameScene::FenXiFeiJi() {
// 分析牌型结构
CCArray* arr = m_arrPlayerOut;
// 飞机的类型
CRAD_INDEX m_cardIndex;
for (int i = 0; i < arr->count();) {
int time = 0; // 相同牌的个数
Poker* pk = (Poker*)arr->objectAtIndex(i);
// 找出相同牌
for (int j = i; j < arr->count(); ++j) {
Poker* pk1 = (Poker*)arr->objectAtIndex(j);
if (pk->getNum() == pk1->getNum()) {
++time;
++i;
}
}
// 单张
if (time == 1) {
m_cardIndex.single_index.push_back(pk->getNum());
} else if (time == 2) {
m_cardIndex.duble_index.push_back(pk->getNum());
} else if (time == 3) {
m_cardIndex.three_index.push_back(pk->getNum());
} else if (time == 4) {
m_cardIndex.four_index.push_back(pk->getNum());
}
}
return m_cardIndex;
}

本章至此结束,通过上述代码,我们就可以判断玩家(人)出的牌是否合法了。

作者信息

boke

boke

共发布了 3994 篇文章