前段时间遇到这个问题,想了一些,也搜了一些,发现网上也只是零星讲了一些笼统思想,复杂的我也没看懂,便自己去尝试写了一些代码来处理这个问题。还是先说明一下适用场景,只是针对一副牌去掉大小王,规则是常见的四人跑得快游戏规则。大致流程,先分析整理手牌,再分析上家出牌牌型,然后对比自家手牌出牌,显示剩余手牌。只是实现这个精简的且有代表性的步骤。
1、整理手牌
获取一段字符串,整理并记录手牌
HashMap cmpMap = new HashMap();
// 存储牌大小的对比库,I代表10,A用14记录,2用15记录
cmpMap.put("A", 14);
cmpMap.put("2", 15);
cmpMap.put("3", 3);
cmpMap.put("4", 4);
cmpMap.put("5", 5);
cmpMap.put("6", 6);
cmpMap.put("7", 7);
cmpMap.put("8", 8);
cmpMap.put("9", 9);
cmpMap.put("I", 10);
cmpMap.put("J", 11);
cmpMap.put("Q", 12);
cmpMap.put("K", 13);
再用一个数组int[] myCard = new int[16];// 有效范围myCard[3]~myCard[15]
下标对应cmpMap的纸牌大小,该下标对应数组值是手牌的张数。
String temp;
int length = mypoker.length();//mypoker是输入手牌字符串
int weight;// 牌大小临时变量
for (int i = 0; i < length; i++) {
temp = mypoker.substring(i, i + 1);
if (cmpMap.containsKey(temp)) {
weight = cmpMap.get(temp);
myCard[weight]++;
} else {
System.out.println("ERROR");
return;
}
}
接下来是存储手牌单双三炸
//有效范围3~15,小标对应牌大小,值对应是否存在这样牌型
//至于为什么要区分开,是为后续出牌策略的妥协
boolean[] myCardType1 = new boolean[16];//存在一张牌
boolean[] myCardSingle = new boolean[16];//只是一张牌
boolean[] myCardType2 = new boolean[16];
boolean[] myCardDouble = new boolean[16];
boolean[] myCardType3 = new boolean[16];
boolean[] myCardTreble = new boolean[16];
boolean[] myCardType4 = new boolean[16];
for (int i = 3; i < 16; i++) {
if (myCard[i] < 0 || myCard[i] > 4) {
System.out.println("ERROR");
return;
}
switch (myCard[i]) {
case 4:
myCardType1[i] = true;
myCardType2[i] = true;
myCardType3[i] = true;
myCardType4[i] = true;
break;
case 3:
myCardType1[i] = true;
myCardType2[i] = true;
myCardType3[i] = true;
myCardTreble[i] = true;
break;
case 2:
myCardType1[i] = true;
myCardType2[i] = true;
myCardDouble[i] = true;
break;
case 1:
myCardType1[i] = true;
myCardSingle[i] = true;
break;
default:
break;
}
}
这样就把手牌大小和牌型全部记录下来了,不知道这样是否合理高效,只是我目前刚想到的思路。
2、分析牌型
分析上家出牌牌型,这也是纠结的很,调研分析了能想到的牌型,大致分为八类,单张、对子、纯三张、炸弹;顺子、飞机、连队、三带一。
2.1上家出牌分析整理
int length = beforeCard.length();//beforeCard是上家出牌字符串
int[] bCardWeight = new int[length];//数组记录每张牌对应大小
if (length > 12) {
System.out.println("ERROR");
return;
}
int j;
for (j = 0; j < length; j++) {
temp = beforeCard.substring(j, j + 1);
if (cmpMap.containsKey(temp)) {
bCardWeight[j] = cmpMap.get(temp);
} else {
System.out.println("ERROR");
return;
}
}
Arrays.sort(bCardWeight);// 升序排列牌大小
2.2牌型分析
private static int getBeforeCardStyle(int[] bCardWeight) {
// single:1,double:2,treble:3,bomb:4,
// chain:5,flight:6,multi_pairs:7,treble+1:8
// 返回对应牌型数值
int style = -1;
int n = bCardWeight.length;
switch (n) {
case 1:
style = 1;
break;
case 2:
if (bCardWeight[0] == bCardWeight[1])
style = 2;
break;
case 3:
if (bCardWeight[0] == bCardWeight[1]
&& bCardWeight[1] == bCardWeight[2])
style = 3;
break;
case 5:
case 7:
case 11:
if (isChain(bCardWeight, n))
style = 5;
break;
case 4:
if (bCardWeight[0] != bCardWeight[1]) {
if (bCardWeight[1] == bCardWeight[2]
&& bCardWeight[2] == bCardWeight[3])
style = 8;
} else {
if (bCardWeight[1] == bCardWeight[2]) {
if (bCardWeight[2] == bCardWeight[3])
style = 4;
else
style = 8;
}
}
break;
case 6:
case 8:
case 12:
if (isChain(bCardWeight, n))
style = 5;
else if (isFlight(bCardWeight, n))
style = 6;
else if (isMultiPairs(bCardWeight, n))
style = 7;
break;
case 9:
if (isChain(bCardWeight, n))
style = 5;
else if (isFlight(bCardWeight, n))
style = 6;
break;
case 10:
if (isChain(bCardWeight, n))
style = 5;
else if (isMultiPairs(bCardWeight, n))
style = 7;
break;
default:
break;
}
return style;
}
可能是有些优化没做好,整个代码还挺长的,但是逻辑不复杂,就贴出写着较麻烦的飞机牌型判断代码。
private static boolean isFlight(int[] bCardWeight, int n) {
if (n == 6 || n == 9) {
if (isTrebleOnly(bCardWeight, n))
return true;
} else if (n == 8) {
if (isFlightPlus(bCardWeight, n))
return true;
} else if (n == 12) {
if (isTrebleOnly(bCardWeight, n))
return true;
else if (isFlightPlus(bCardWeight, n))
return true;
}
return false;
}
private static boolean isTrebleOnly(int[] bCardWeight, int n) {
//判断是否为纯飞机不带单张
if (n % 3 != 0)
return false;
int i;
int j = 0;
for (i = 0; i < n; i = i + 3) {
if (bCardWeight[i] == bCardWeight[i + 1]
&& bCardWeight[i + 1] == bCardWeight[i + 2])
j++;
}
if (j == n / 3) {
j = 0;
for (i = 0; i < n - 3; i = i + 3) {
if (bCardWeight[i + 3] - bCardWeight[i] == 1)
j++;
}
if (j + 1 == n / 3)
return true;
}
return false;
}
private static boolean isFlightPlus(int[] bCardWeight, int n) {
//判断是否为飞机带单张
int m = n / 4;
int[] num = new int[m];
HashMap numMap = new HashMap();
for (int i = 0; i < n; i++) {
if (numMap.containsKey(bCardWeight[i])) {
numMap.put(bCardWeight[i], numMap.get(bCardWeight[i]) + 1);
} else
numMap.put(bCardWeight[i], 1);
}
int j = 0;
for (int key : numMap.keySet()) {
if (numMap.get(key) >= 3) {
num[j] = key;
j++;
}
}
if (j == m) {
Arrays.sort(num);
int i = m - 1;
while (i > 0 && num[i] - num[i - 1] == 1) {
i--;
}
if (i == 0)
return true;
}
return false;
}
3、出牌策略
说到出牌策略,其实这个也只是贪心地推荐最小压过上家的牌,并没有很全面地大局观分析。
说回上面为什么要分别存储存在单双三炸和只是单双三炸的布尔数组,正是为了出牌考虑,打个简单地比方:总不能为了出个三带一,带单张把四张炸弹拆了带;或者是有三张没单张带同时有四张炸,没单张不去拆对子或其他三张凑单而直接出炸弹,这样都划不来。所以在某些时候出牌,先是遍历仅仅是对应牌型的,不够再从其他牌型(除了炸弹)来凑,实在打不过再去遍历炸弹牌型。
int bCardStyle = getBeforeCardStyle(bCardWeight);// 上家出牌类型值
//flag记录是否出牌,如果能出在相应函数中出牌
//出牌函数带上myCard数组,是手牌减去出牌,存储剩余
boolean flag = false;
if (bCardStyle == -1) {
System.out.println("ERROR");
return;
}
switch (bCardStyle) {
case 1:
case 2:
case 3:
case 4:
flag = sameCardOut(bCardWeight[0], bCardStyle, myCardSingle,myCardDouble, myCardTreble, myCardType4, myCard);
break;
case 5:
flag = chainCardOut(bCardWeight[0], length, myCardType1,myCardType4, myCard);
break;
case 6:
flag = flightCardOut(bCardWeight, myCardSingle, myCardDouble,myCardTreble, myCardType4, myCard);
break;
case 7:
flag = multiPairsCardOut(bCardWeight[0], length, myCardType2,myCardType4, myCard);
break;
case 8:
flag = trebleOneCardOut(bCardWeight, myCardSingle, myCardDouble,myCardTreble, myCardType4, myCard);
break;
default:
break;
}
由于有些优化不好,代码冗长,贴出其中对顺子和飞机牌型的出牌函数,飞机牌型写的时候最纠结,有点不友好,希望有想法的可以指导一下。
private static boolean chainCardOut(int w, int length,
boolean[] myCardType1, boolean[] myCardType4, int[] myCard) {
int i = w + 1;
String temp;
if (w + length - 1 != 14) {
for (int j = w + 1; j < 16 - length; j++) {
i = j;
temp = "";
while (i < j + length && myCardType1[i] && !myCardType4[i]) {
temp = temp + toGetCard(i);
i++;
}
if (i == j + length) {
System.out.println(temp);
for (i = j; i < j + length; i++)
myCard[i]--;
return true;
}
}
}
i = 3;
while (i < 16) {
if (myCardType4[i]) {
temp = toGetCard(i);
System.out.println(temp + temp + temp + temp);
myCard[i] = 0;
return true;
}
i++;
}
return false;
}
private static boolean flightCardOut(int[] bCardWeight,
boolean[] myCardSingle, boolean[] myCardDouble,
boolean[] myCardTreble, boolean[] myCardType4, int[] myCard) {
int length = bCardWeight.length;
boolean isTrebleOnly = isTrebleOnly(bCardWeight, length);//判断是否是纯三张不带牌的飞机
if (isTrebleOnly) {
int a = bCardWeight[0];
int l = length / 3;
for (int i = a + 1; i < 14; i++) {
int j = i;
int k = 0;
while (j < 14 && myCardTreble[j]) {
j++;
k++;
}
if (k >= l) {
String temp = "";
for (k = i; k - i < l; k++) {
temp = temp + toGetCard(k) + toGetCard(k)
+ toGetCard(k);
//toGetCard(k)获取数字k对应字符串
//myCard[k]减去出牌张数
myCard[k] = myCard[k] - 3;
}
System.out.println(temp);
return true;
}
}
} else {
int a = getFlightPlus(bCardWeight, length);
//获取飞机带牌类型的飞机开头牌的大小
if (a == -1)
System.out.println("ERROR");
else {
int l = length / 4;
int[] cardTemp = new int[l * 2];
for (int i = a + 1; i < 14; i++) {
int j = i;
int k = 0;
while (j < 14 && myCardTreble[j]) {
j++;
k++;
}
if (k >= l) {
int n = 0;
String temp = "";
for (k = i; k - i < l; k++) {
temp = temp + toGetCard(k) + toGetCard(k)
+ toGetCard(k);
cardTemp[n] = k;
n++;
}
j = 0;
k = 3;
while (j != l && k < 15) {
if (k == i)
k = k + l;
if (myCardSingle[k]) {
temp = temp + toGetCard(k);
j++;
cardTemp[n] = k;
n++;
}
k++;
}
if (j < l) {
k = 3;
while (j != l && k < 15) {
if (k == i)
k = k + l;
if (myCardDouble[k]) {
if (j + 2 <= l) {
temp = temp + toGetCard(k)
+ toGetCard(k);
j = j + 2;
cardTemp[n] = k;
cardTemp[n + 1] = k;
n = n + 2;
} else {
temp = temp + toGetCard(k);
j++;
cardTemp[n] = k;
n++;
}
}
k++;
}
}
if (j < l) {
k = 3;
while (j != l && k < 15) {
if (k == i)
k = k + l;
if (myCardTreble[k]) {
if (j + 3 <= l) {
temp = temp + toGetCard(k)
+ toGetCard(k) + toGetCard(k);
j = j + 3;
cardTemp[n] = k;
cardTemp[n + 1] = k;
cardTemp[n + 2] = k;
n = n + 3;
} else if (j + 2 <= l) {
temp = temp + toGetCard(k)
+ toGetCard(k);
j = j + 2;
cardTemp[n] = k;
cardTemp[n + 1] = k;
n = n + 2;
} else {
temp = temp + toGetCard(k);
j++;
cardTemp[n] = k;
n++;
}
}
k++;
}
}
if (j == l) {
System.out.println(temp);
int m;
for (m = 0; m < l; m++)
myCard[cardTemp[m]] = 0;
for (m = l; m < 2 * l; m++)
myCard[cardTemp[m]]--;
return true;
}
}
}
}
}
int i = 3;
while (i < 16) {
if (myCardType4[i]) {
String temp = toGetCard(i);
System.out.println(temp + temp + temp + temp);
myCard[i] = 0;
return true;
}
i++;
}
return false;
}
总结
就写这么多了,主要是分享一下记录牌处理牌的方法和牌型判断的思路,以及出牌的策略,代码写的有点乱,也很冗长,大概是一开始就没组织好。至于这种存储牌的方式有点讨巧,似乎有些不妥,个人局限,高级数据结构并不能很好运用,凑合着看吧,也算是分享一下我对这个问题的一个思路。欢迎其他小伙伴有任何更好的方法跟我分享,谢谢。