一、游戏玩法简介
牌型介绍:
单张:任何一张单牌。
对子:两张牌点相同的牌。
三张:三张牌点相同的牌。
四张:四张牌点相同的牌。
杂顺:连续五张牌点相邻花色不全相同的牌。如“34567”“910JQK”“10JQKA”等。
同花:由相同花色的五张牌组成,但不是顺。如红桃“3579J”。
葫芦:三张加一对,如“555JJ”。
金刚:四张加单张,如“5555J”。
同花顺:花色相同的顺子。
比牌规则
(1)只有张数相同的牌可以比较大小,即如果别人出一对,我也要出一对才能压他。
(2)先比较牌点,如果牌点相同,再比较花色。牌点从大到小依次为:2 A K Q J 10 9 8 7 6 5 4 3。花色从大到小依次为:黑桃 红桃 梅花 方块。
(3)五张的牌型中,同花顺>金刚>葫芦>同花>杂顺。
出牌规则
一副牌共52张(不含大小王),游戏开始后,发给4个玩家,每个玩家拿13张牌。拿到方块3的玩家先出牌(也可以选择赢家先出)。
二、游戏逻辑编写--基础函数
- 如何表示这52张牌?
//方式一:用牌值的方式表示
unsigned char cbRepetoryCard[52] =
{
//方块A-K
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,
//梅花A-K
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,
//红桃A-K
0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,
//黑桃A-K
0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,
};
//方式二:用索引的方式表示
unsigned char cbRepetoryIndex[52];
//如果cbRepetoryIndex[0] == 3代表方块A有3张,当然在锄大地里每种牌最多一张
//如果cbRepetoryIndex[51] == 1代表黑桃K有1张
- 如何获取一张牌的花色和牌值?
//获取花色,黑桃、红桃、梅花、方块的花色分别是0x30、0x20、0x10、0x00
unsigned char GetCardColor(unsigned char cbCardData)
{
return cbCardData & 0xF0;
}
//获取牌值,A 2 3 ... J Q K分别是1 2 3 ... 11 12 13
unsigned char GetCardValue(unsigned char cbCardData)
{
return cbCardData & 0x0F;
}
- 如何获取一张牌的逻辑花色和逻辑牌值?
//获取逻辑花色,方块、梅花、红桃、黑桃分别是0、1、2、3
unsigned char GetCardLogicColor(unsigned char cbCardData)
{
unsigned char cbCardColor = GetCardColor(cbCardData);
return (cbCardColor >> 4);
}
//获取逻辑牌值,3 4 5 ... K A 2分别是0 1 2 ... 10 11 12
unsigned char GetCardLogicValue(unsigned char cbCardData)
{
unsigned char cbCardValue = GetCardValue(cbCardData);
return (cbCardValue < 3) ? (cbCardValue + 10) : (cbCardValue - 3);
}
- 使用逻辑花色和逻辑牌值:比较两张牌的大小、构造一张牌。
//比较两个牌的大小,如果前者小于后者返回真,否则返回假
bool IsCard1BiggerThan2(unsigned char cbCard1, unsigned char cbCard2)
{
unsigned char cbCardLogicValue1 = GetCardLogicValue(cbCard1);
unsigned char cbCardLogicValue2 = GetCardLogicValue(cbCard2);
if (cbCardLogicValue1 == cbCardLogicValue2)
{
unsigned char cbCardLogicColor1 = GetCardLogicColor(cbCard1);
unsigned char cbCardLogicColor2 = GetCardLogicColor(cbCard2);
return (cbCardLogicColor1 > cbCardLogicColor2);
}
return (cbCardLogicValue1 > cbCardLogicValue2);
}
//通过逻辑花色和逻辑牌值构造一张牌
unsigned char MakeCardData(unsigned char cbCardLogicValue
, unsigned char cbCardLogicColor)
{
return (cbCardLogicValue >= 11)
? ((cbCardLogicColor << 4) | (cbCardLogicValue - 10))
: ((cbCardLogicColor << 4) | (cbCardLogicValue + 3));
}
- 校验一张牌、校验一个牌数组是否合法、是否排好序了
//牌是否合法
bool IsValidCardData(unsigned char cbCardData)
{
unsigned char cbColor = GetCardColor(cbCardData);
unsigned char cbValue = GetCardValue(cbCardData);
return (cbColor <= 0x30 && cbValue >= 0x01 && cbValue <= 0x0D);
}
//牌数组是否合法
bool IsValidCardArrayWithZero(const unsigned char arr[], int len)
{
for(int i = 0; i < len; i++)
{
if (arr[i] != 0 && !IsValidCardData(arr[i]))
{
return false;
}
}
return true;
}
//牌数组是否已经从大到小排好序,并且不包含0
bool IsSortedCardArrayWithoutZero(const unsigned char arr[], int len)
{
assert(IsValidCardArrayWithoutZero(arr, len));
for (int i = 0; i < len - 1; i++)
{
if (!IsCard1BiggerThan2(arr[i], arr[i + 1]))
{
return false;
}
}
return true;
}
- 如何对一个手牌数组排序?
//对牌数组做降序排序,因为手牌是从大到小摆放的
void SortCardArray(unsigned char arr[], int len)
{
assert(IsValidCardArrayWithZero(arr, len));
unsigned char temp;
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - 1 - i; j++)
{
//这里的if else显得很臃肿,但我希望别人能一眼就看懂我要表达什么意思
if (arr[j] == 0 && arr[j + 1] == 0)
{
;
}
else if (arr[j] == 0 && arr[j + 1] != 0)
{
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
else if (arr[j] != 0 && arr[j + 1] == 0)
{
;
}
else
{
if (!IsCard1BiggerThan2(arr[j], arr[j + 1]))
{
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
- 如何获取一个牌数组里的有效牌个数?
//获取一个牌数组(有序可以含0)里的有效牌个数
int GetValidCardCount(const unsigned char arr[], int len)
{
//使用注意:在获取数组有效牌个数之前,需先给数组做排序
assert(IsValidCardArrayWithZero(arr, len));
int cnt = len;
for (int i = 0; i < len; i++)
{
if (arr[i] == 0)
{
cnt = i;
break;
}
}
for (int i = cnt + 1; i < len; i++)
{
if (arr[i] != 0)
{
assert(false);
}
}
return cnt;
}
- 如何判断一个牌数组(有序无0)能不能构成顺子
//判断一个牌数组(有序无0)能否连起来
bool IsCardArrayLinked(const unsigned char arr[], int len, bool bMaxShunZiK = false)
{
assert(len == 5);
assert(IsSortedCardArrayWithoutZero(arr, len));
unsigned char cbCardLogicValueFirst = GetCardLogicValue(arr[0]);
//如果传进来的数组里第一张牌的点数比7小(最小的顺子是34567),或者等于2,那么必然不能构成顺子
if (cbCardLogicValueFirst < 4 || cbCardLogicValueFirst == 12)
{
return false;
}
//默认情况下顺子能顺到A,即最大的顺子是10、J、Q、K、A。但有些地方玩法规定顺子最多顺到K
if (bMaxShunZiK && cbCardLogicValueFirst == 11)
{
return false;
}
for (int i = 1; i < len; i++)
{
unsigned char cbCardLogicValue = GetCardLogicValue(arr[i]);
if (cbCardLogicValueFirst - cbCardLogicValue != i)
{
return false;
}
}
return true;
}
- 如何判断一个数组(有序无0)是否花色相同
//分析扑克数组(有序无0),是否同花色
bool IsCardArraySameColor(const unsigned char arr[], int len)
{
assert(len == 5);
assert(IsSortedCardArrayWithoutZero(arr, len));
unsigned char cbCardLogicColorFirst = GetCardLogicColor(arr[0]);
for (int i = 1; i < len; i++)
{
if (cbCardLogicColorFirst != GetCardLogicColor(arr[i]))
{
return false;
}
}
return true;
}
二、游戏逻辑编写--介绍两个结构体
假如给你一个牌数组,问你这个数组里有多少个单张,多少个对子,多少个顺子等。不可能每次都去遍历数组,所以这里引入两个结构体,可以表示一个数组有哪些东西。
/*
分布
假设你有3张牌,方块3(逻辑牌值0,逻辑花色0),梅花3(逻辑牌值0,逻辑花色1),黑桃2(逻辑牌值12,逻辑花色3)
那么totalCnt为3,代表这个数组总共有3张牌
distrCnt[0][0]为1,distrCnt[0][1]为1,distrCnt[12][3]为1,代表方块3有一张,梅花3有一张,黑桃2有一张
distrCnt[0][4]为2,distrCnt[12][4]为1,代表点数为3的牌一共有2张,点数为2的牌一共有1张
*/
struct StructDistribution
{
int totalCnt; //总张数
int distrCnt[13][5];//数组的第一维对应逻辑牌值,第二维对应逻辑花色
StructDistribution()
{
clear();
}
void clear()
{
totalCnt = 0;
memset(distrCnt, 0, sizeof(distrCnt));
}
};
/*
结果
假设你有6张牌:两个对子,两个单张
黑桃2(0x32),红桃2(0x22),黑桃A(0x31),红桃A(0x21),方块4(0x04),方块3(0x03)
那么typeCnt[0]=2,typeCnt[1]=2,typeCnt[2]=0,typeCnt[3]=0
cardData[0][0]=0x04,cardData[0][1]=0x03
cardData[1][0]=0x32,cardData[1][1]=0x22,cardData[1][2]=0x31,cardData[1][3]=0x21
*/
struct StructResult
{
int typeCnt[4]; //typeCnt[0]单张、typeCnt[1]对子、typeCnt[2]三张、typeCnt[3]四张
unsigned char cardData[4][13]; //单张、对子、三张、四张的牌分别是什么
StructResult()
{
clear();
}
void clear()
{
memset(typeCnt, 0, sizeof(typeCnt));
memset(cardData, 0, sizeof(cardData));
}
};
- 如何把一个牌数组存进刚才的结构体中
//分析牌数组(有序无0),并将分析结果存进StResult
void AnalyseIntoResult(const unsigned char arr[], int len, StructResult& stResult)
{
assert(IsSortedCardArrayWithoutZero(arr, len));
stResult.clear();
for (int i = 0; i < len; i++)
{
int sameCnt = 1;
unsigned char value1 = GetCardLogicValue(arr[i]);
for (int j = i + 1; j < len; j++)
{
unsigned char value2 = GetCardLogicValue(arr[j]);
if (value1 == value2)
{
sameCnt++;
}
else
{
break;
}
}
switch (sameCnt)
{
case 1:
{
int thisTypeCnt = stResult.typeCnt[sameCnt - 1]++;
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt] = arr[i];
break;
}
case 2:
{
int thisTypeCnt = stResult.typeCnt[sameCnt - 1]++;
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt] = arr[i];
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt + 1] = arr[i + 1];
break;
}
case 3:
{
int thisTypeCnt = stResult.typeCnt[sameCnt - 1]++;
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt] = arr[i];
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt + 1] = arr[i + 1];
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt + 2] = arr[i + 2];
break;
}
case 4:
{
int thisTypeCnt = stResult.typeCnt[sameCnt - 1]++;
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt] = arr[i];
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt + 1] = arr[i + 1];
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt + 2] = arr[i + 2];
stResult.cardData[sameCnt - 1][thisTypeCnt * sameCnt + 3] = arr[i + 3];
break;
}
default:
break;
}
i += sameCnt - 1;
}
}
//分析牌数组(有序无0),并将分析结果存进StDistribution
void AnalyseIntoDistribution(const unsigned char arr[], int len, StructDistribution& stDistribution)
{
assert(IsSortedCardArrayWithoutZero(arr, len));
stDistribution.clear();
stDistribution.totalCnt = len;
for (int i = 0; i < len; i++)
{
unsigned char cbCardLogicValue = GetCardLogicValue(arr[i]);
unsigned char cbCardLogicColor = GetCardLogicColor(arr[i]);
stDistribution.distrCnt[cbCardLogicValue][cbCardLogicColor]++;
stDistribution.distrCnt[cbCardLogicValue][4]++;
}
}
三、游戏逻辑编写--使用这两个结构体
假如在玩游戏的过程中,玩家A出了一手牌,服务器把玩家A出的牌告诉了玩家B,玩家B要根据玩家A所出的牌的类型来播放相应的动画,比方说顺子和同花有不同的动画,那么如何确定一手牌的类型呢?
#define TYPE_ERROR 0 //类型错误
#define TYPE_YI_ZHANG 1 //单张
#define TYPE_LIANG_ZHANG 2 //对子
#define TYPE_SAN_ZHANG 3 //三张
#define TYPE_SI_ZHANG 4 //四张
#define TYPE_SHUN_ZI 5 //顺子(点数相连,花色不全相同)
#define TYPE_TONG_HUA 6 //同花(点数不相连,花色相同)
#define TYPE_HU_LU 7 //葫芦(三张点数相同,两张点数相同,三张和两张的点数不同)
#define TYPE_JIN_GANG 8 //金刚(四张点数相同,四张和一张的点数不同)
#define TYPE_TONG_HUA_SHUN 9 //同花顺(点数相连,花色相同)
//通过一个牌数组,获取类型(单张、对子、三张、四张、顺子、同花、葫芦、金刚、同花顺)
int GetTypeByCardArray(const unsigned char arr[], int len)
{
assert(IsSortedCardArrayWithoutZero(arr, len));
if (len == 1)
{
return TYPE_YI_ZHANG;
}
else if (len == 2)
{
if (GetCardValue(arr[0]) == GetCardValue(arr[1]))
{
return TYPE_LIANG_ZHANG;
}
}
else if (len == 3)
{
if (GetCardValue(arr[0]) == GetCardValue(arr[1])
&& GetCardValue(arr[1]) == GetCardValue(arr[2]))
{
return TYPE_SAN_ZHANG;
}
}
else if (len == 4)
{
if (GetCardValue(arr[0]) == GetCardValue(arr[1])
&& GetCardValue(arr[1]) == GetCardValue(arr[2])
&& GetCardValue(arr[2]) == GetCardValue(arr[3]))
{
return TYPE_SI_ZHANG;
}
}
else if (len == 5)
{
StructResult stResult;
AnalyseIntoResult(arr, len, stResult);
if (stResult.typeCnt[0] == 5)
{
bool bLinked = IsCardArrayLinked(arr, len);
bool bSameColor = IsCardArraySameColor(arr, len);
if (bLinked && !bSameColor)
{
return TYPE_SHUN_ZI;
}
else if (!bLinked && bSameColor)
{
return TYPE_TONG_HUA;
}
else if (bLinked && bSameColor)
{
return TYPE_TONG_HUA_SHUN;
}
}
else
{
if (stResult.typeCnt[1] == 1 && stResult.typeCnt[2] == 1)
{
return TYPE_HU_LU;
}
if (stResult.typeCnt[0] == 1 && stResult.typeCnt[3] == 1)
{
return TYPE_JIN_GANG;
}
}
}
return TYPE_ERROR;
}
四、游戏逻辑编写--搜索出牌
/*
搜索出牌的结果集
假如别人出了3、4、5、6、7,我手里有4、5、6、7、8、9
那么我有两种出法:4、5、6、7、8和5、6、7、8、9
即cnt为2
data[0][0]=4,data[0][1]=5,data[0][2]=6,data[0][3]=7,data[0][4]=8
data[1][0]=5,data[1][1]=6,data[1][2]=7,data[1][3]=8,data[1][4]=9
*/
struct StructSearchOutCardSet
{
int cnt; //有多少种出牌方式,比如
unsigned char data[13][5]; //出牌结果集
StructSearchOutCardSet()
{
clear();
}
void clear()
{
cnt = 0;
memset(data, 0, sizeof(data));
}
};
//搜索顺子,refDis中存着自己的手牌,refRes用来存结果,turnCard指别人出的顺子中的最大牌
void SearchShunZi(const StDistribution& refDis, StOutCard& refRes
, unsigned char turnCard)
{
assert(IsValidCardData(turnCard));
unsigned char turnCardLogicValue = GetCardLogicValue(turnCard);
unsigned char turnCardLogicColor = GetCardLogicColor(turnCard);
int linkCnt = 0;
for(int i = 0; i < 13 - 1; i++)//12代表A,如果要求顺子最大到K,那么可以传入一个参数来区分
{
//如果某个点数的牌没有,那么把连牌数置0,并跳过本次循环
if(refDis.distribution[i][4] == 0)
{
linkCnt = 0;
continue;
}
//如果连牌数大过5个并且点数比turnCard大
if(++linkCnt >= 5 && i >= turnCardLogicValue)
{
for(int j = 0; j < 4; j++)
{
if(refDis.distribution[i][j] > 0 && j > turnCardLogicColor)
{
refRes.card[refRes.count][0] = MakeCardData(i, j);
for(int k = 1; k < 5; k++)
{
for(int m = 0; m < 4; m++)
{
if(refDis.distribution[i - k][m] > 0)
{
refRes.card[refRes.count][k] = MakeCardData(i - k, m);
break;
}
}
}
refRes.count++;
}
}
}
}
}
//搜索同花
void SearchTongHua(const StDistribution& refDis, StOutCard& refRes
, unsigned char turnCard)
{
assert(IsValidCardData(turnCard));
for(int i = 0; i < 4; i++)
{
int sameColorCnt = 0;
for(int j = 0; j < 13; j++)
{
if(refDis.distribution[j][i] > 0)
{
sameColorCnt++;
}
if(sameColorCnt >= 5)
{
unsigned char currentCard = MakeCardData(j, i);
if(IsCard1BiggerThan2(currentCard, turnCard))
{
refRes.card[refRes.count][0] = currentCard;
int resultCnt = 1;
for(int k = 0; k < j; k++)
{
if(refDis.distribution[k][i] > 0)
{
refRes.card[refRes.count][5 - resultCnt] = MakeCardData(k, i);
resultCnt++;
}
if(resultCnt >= 5)
{
break;
}
}
refRes.count++;
}
}
}
}
}
//搜索葫芦
void SearchHuLu(const StDistribution& refDis, StOutCard& refRes
, unsigned char turnCard)
{
assert(IsValidCardData(turnCard));
int cnt = 0;
unsigned char three[13] = { 0 };
unsigned char two[13] = { 0 };
unsigned char turnCardLogicValue = GetCardLogicValue(turnCard);
for(int i = 0; i < 13; i++)
{
if(refDis.distribution[i][4] == 3 && i > turnCardLogicValue)
{
for(int j = 0; j < 13; j++)
{
if(refDis.distribution[j][4] == 2)
{
three[cnt] = i;
two[cnt] = j;
cnt++;
}
}
}
}
for(int i = 0; i < cnt; i++)
{
if(three[i] != 0 && two[i] != 0)
{
int index = 0;
for(int j = 3; j >= 0; j--)
{
if(refDis.distribution[three[i]][j] > 0)
{
refRes.card[refRes.count][index++]
= MakeCardData(three[i], j);
}
}
for(int j = 3; j >= 0; j--)
{
if(refDis.distribution[two[i]][j] > 0)
{
refRes.card[refRes.count][index++]
= MakeCardData(two[i], j);
}
}
refRes.count++;
}
}
}
//搜索出牌
bool SearchOutCard(const unsigned char arrSelf[], int lenSelf, const unsigned char arrOther[]
, int lenOther, StructSearchOutCardSet& refSt)
{
assert(IsSortedCardArrayWithoutZero(arrSelf, lenSelf));
assert(IsSortedCardArrayWithoutZero(arrOther, lenOther));
refSt.clear();
unsigned char turnCard = arrOther[0];
int turnType = GetTypeByCardArray(arrOther, lenOther);
StResult st1;
AnalyseIntoResult(arrSelf, lenSelf, st1);
if(turnType == TYPE_YI_ZHANG
|| turnType == TYPE_LIANG_ZHANG
|| turnType == TYPE_SAN_ZHANG
|| turnType == TYPE_SI_ZHANG)
{
for(int i = turnType - 1; i < 4; i++)
{
for(int j = 0; j < st1.typeCnt[i]; j++)
{
int index = (st1.typeCnt[i] - 1 - j) * (i + 1);
if(IsCard1BiggerThan2(st1.cardData[i][index], turnCard))
{
memcpy(refSt.data[refSt.cnt], &st1.cardData[i][index], lenOther);
refSt.cnt++;
}
}
}
}
StDistribution st2;
AnalyseIntoDistribution(arrSelf, lenSelf, st2);
if(turnType == TYPE_SHUN_ZI)
{
SearchShunZi(st2, refRes, turnCard);
}
else if(turnType == TYPE_TONG_HUA)
{
SearchTongHua(st2, refRes, turnCard);
}
else if(turnType == TYPE_HU_LU)
{
SearchHuLu(st2, refRes, arrOther[2]);
}
else if(turnType == TYPE_JIN_GANG)
{
SearchJinGang(st2, refRes, arrOther[2]);
}
else if(turnType == TYPE_TONG_HUA_SHUN)
{
SearchTongHuaShun(st2, refRes, turnCard);
}
return true;
}
网友评论