鬥地主:字符串+正則的簡單實現
緣由:聽同學說學了集合後,就可以基本實現鬥地主功能,一開始聽到覺得不可能吧,後面就認真構想了一下,覺得如果加上正則匹配撲克牌出牌規則,實現鬥地主是可以的,只不過沒有圖形界面,只能在終端一個人玩,覺得寫出來了也挺有意思的,於是我就開始鬥地主的編寫(ps:寫了很久)。
自己測試很多次,沒有發現什麼bug。
如果有什麼邏輯沒考慮到,或者優化的地方希望評論告訴我,共同學習,共同進步(^_^)。
整體思路概述:
1:生成撲克牌,給玩家隨機發牌
1.1:利用撲克牌字符串與索引一一對應關係,通過隨機給玩家發索引,然後根據玩家的索引獲取牌,來達到給玩家隨機發牌的效果。
2:輪流出牌
2.1利用循環實現玩家輪流出牌,對每個玩家的出牌進行校驗:
a是否爲撲克牌校驗,
b是否是來自玩家自己手中的牌,
c正則匹配是否符合出牌規則,記錄對應牌數據:牌的類型,和牌的大小
2.2下一個玩家出牌(玩家可選擇出牌或跳過),校驗成功後,對牌類型和大小比較,滿足出牌大小條件,則繼續下一個玩家。(注:玩家選擇跳過,跳過兩次表示其它玩家要不起,那麼則需要重置牌記錄的數據)
說明:
1:3帶1不能帶一對牌,下面的正則匹配時:一對牌當作2個單牌匹配
2:沒把連炸彈帶牌(333344445678)考慮進去
3:可以把正則匹配方法單獨提取出來測試牌
下面是代碼:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
public class FightLandlord {
private static Scanner sc;
private static int skip = 0;// 記錄玩家跳過的次數
private static String name = "";// 玩家名字
public static void main(String[] args) {
playPoker();
}
// 修改:遍歷拼接時直接添加到Map集合中
public static void playPoker() {
ArrayList<String> p1 = new ArrayList<String>();// 玩家1集合
ArrayList<String> p2 = new ArrayList<String>();// 玩家2集合
ArrayList<String> p3 = new ArrayList<String>();// 玩家3集合
// -----------方法1: 根據字符串索引給玩家發牌-----------
indexStringPoker(p1, p2, p3);
String beforeW = "beforeW";// 初始玩家先出牌字符串
String afterW = "afterW";// 初始玩家:後出牌字符串
System.out.println("出牌簡化:'0'代表 牌'10','小'='小☺',大='大☻',e代表跳過,忽略了大小寫.(注:輸入其它非撲克牌字符也是默認跳過:1,你...)");
while (true) {
// -----------方法2: 循環判斷玩家出牌是否合理-----------
name = "地主 p1_";// 玩家名提示
beforeW = loopSyso(p1, beforeW, afterW, name);// 4
name = "農民 p2_";
beforeW = loopSyso(p2, beforeW, afterW, name);
name = "農民 p3_";
beforeW = loopSyso(p3, beforeW, afterW, name);
}
}
// -------------------------------方法1-1:根據字符串索引給玩家發牌-------------------------------
public static void indexStringPoker(ArrayList<String> p1, ArrayList<String> p2, ArrayList<String> p3) {
// 爲了方便排序,臨時替換: a替換0 b=j c=q d=k e=A f=2 小=g 大=h;
String cards = "3♣3♠3♥3♦4♣4♠4♥4♦5♣5♠5♥5♦6♣6♠6♥6♦7♣7♠7♥7♦8♣8♠8♥8♦9♣9♠9♥9♦a♣a♠a♥a♦b♣b♠b♥b♦c♣c♠c♥c♦d♣d♠d♥d♦e♣e♠e♥e♦f♣f♠f♥f♦g☺h☻";
ArrayList<Integer> key = new ArrayList<Integer>();// key集合=對應牌索引
for (int i = 0; i < 54; i++) {// 給集合添加內容:(0-53)
key.add(i);
}
Collections.shuffle(key);// 打亂鍵(索引)=打亂牌
// 給玩家添加牌
for (int i = 0; i < 54; i++) {
int index = key.get(i);
if (i < 20)
p1.add(cards.substring(2 * index, 2 * (index + 1)));
if (20 <= i && i < 37)
p2.add(cards.substring(2 * index, 2 * (index + 1)));
if (i >= 37)
p3.add(cards.substring(2 * index, 2 * (index + 1)));
}
// -----------方法1-1-1: 替換回來-->a替換0 b=j c=q d=k e=A f=2-----------
replaceSort(p1);
replaceSort(p2);
replaceSort(p3);
System.out.println("p1: " + p1 + p1.size());
System.out.println("p2: " + p2 + p2.size());
System.out.println("p3: " + p3 + p3.size());
}
// ----------------------方法1-1-1:建字符串替換回來,並轉換爲數組----------------------
public static void replaceSort(ArrayList<String> p) {
Collections.sort(p);// 對玩家牌排序
String ps = p.toString();// 轉換爲字符串
ps = ps.replaceAll("a", "0");
ps = ps.replaceAll("b", "J");
ps = ps.replaceAll("c", "Q");
ps = ps.replaceAll("d", "K");
ps = ps.replaceAll("e", "A");
ps = ps.replaceAll("f", "2");
// 必須完全排序,否則j索引跳過順序混亂的牌.---->大小-->遍歷時,j直接累加到了"大",最後的索引.當再依次遍歷到小的時候.在大之後索引查找,找不到了
ps = ps.replaceAll("g", "小");
ps = ps.replaceAll("h", "大");
ps = ps.replaceAll("[\\[\\]]", "");// 去除前後大括號
String[] split = ps.split(",");// 轉化爲數組
for (int i = 0; i < split.length; i++) {
p.set(i, split[i]);
}
}
// ------------------------方法2:循環出牌-------------------------------
public static String loopSyso(ArrayList<String> p, String beforeW, String afterW, String name) {
sc = new Scanner(System.in);
// 接收玩家牌,判斷
w: while (true) {
System.out.print(name + p + "\n\t\t" + name + "出牌:");
afterW = sc.next();// 接收玩家的出牌
// -----------方法2-1: 對玩家輸入的牌進行排序-----------
afterW = diySortString(afterW);
if (afterW.contains("E")) {
skip++;
if (skip == 3) {
System.out.println("跳過一輪");
skip = 0;
}
System.out.print("_______________________________" + name + "跳過:" + skip + "次______________________\n");
return beforeW;// 返回上家的牌
}
// 遍歷玩家輸入的字符串,記錄對應牌索引()
int[] pcopyI = new int[afterW.length()];// 默認爲0
int j = -1;// 記錄上次遍歷到的索引位置
f: for (int i = 0; i < afterW.length(); i++) {// 遍歷玩家字符串
String s1 = afterW.substring(i, i + 1);// 拿到玩家字符串的每個字符
// 不回頭遍歷:必須有序
for (j++; j < p.size(); j++) {// 遍歷玩家p牌對應的字符數組
String s = p.get(j);
if (s.contains(s1)) {// 對比是否有相同的牌: "3♣"包含3,匹配成功
pcopyI[i] = j;// 記錄牌中被替換對應的索引
continue f;// 跳轉到外循環,匹配下一個字符
}
}
System.out.println("你輸入的牌有不存在的,請重新輸入");
continue w;// 有不匹配字符,則跳轉到開始:提示玩家重新輸入
}
// ----------方法2-2:校驗比較牌大小------
int[] w1 = regexMatches(beforeW);// 獲取玩家"牌數據"(出牌類型,及對應出牌大小)
// 都跳過,玩家可自定義出牌---位置必須放在w1=後用於重置array"牌數據"
if (skip == 2) {// 跳過2次,說明其它玩家都不要,那麼"牌數據"還原爲初始值
w1[0] = -1;
w1[1] = 0;
}
// 當前玩家出牌
int[] w1c = regexMatches(afterW);
if (w1c[0] == -2) {// 牌不符合規則的終止條件:終止
continue;// -->玩家重新輸入
}
System.out.println("前類型=" + w1[1] + ",後類型=" + w1c[1] + ", 前:=" + w1[0] + ",後=" + w1c[0]);
// 比較類型
if (w1[1] == w1c[1] | w1c[1] == 10000 | w1[1] == 0) {
if (w1[0] >= w1c[0]) {// 比較牌大小
w1c[0] = w1[0];// 還原上家牌索引
w1c[1] = w1[1];// 還原上家牌類型
System.out.println("要不起,請重新選擇牌");
continue;
}
} else {// 類型不滿足
w1c[1] = w1[1];
w1c[0] = w1[0];
System.out.println("類型不匹配,請重新出牌");
continue;
}
// 當玩家不是連續輸入重置跳過次數=位置需在skip==2後//中間玩家輸錯,不至於更改
if (!afterW.contains("E")) {
skip = 0;
}
// 顯示出牌
System.out.print("________________________________" + name + afterW + "________________________\n");
// 移除牌
for (int i = 0; i < pcopyI.length; i++) {
p.set(pcopyI[i], "");
}
break w;// 出牌成功跳出循環
}
// ---------------------------------判斷玩家是否出完牌--------------------------
for (int i = 0; i < p.size(); i++) {
if (!p.get(i).equals("")) {
break;
}
if (i == (p.size() - 1)) {
System.out.println("________________________________" + name + " 先出完牌,game over");
System.exit(3000);
// return "over";//返回該上一層,判斷是否退出
}
}
beforeW = afterW;// 內存地址
return beforeW;// 默認值
}
// -----------方法2-1:對用戶輸入的字符串進行排序-----------
public static String diySortString(String str) {
str = str.toUpperCase();// 忽略大小寫
String sortS = "34567890JQKA2小☺大☻";// 指定排序規則字符串
if (!str.matches("[" + sortS + "]+")) {// 匹配是否非撲克牌字符,是跳過
return "E";
}
ArrayList<Integer> strA = new ArrayList<Integer>();
for (int i = 0; i < str.length(); i++) {
strA.add(sortS.indexOf(str.charAt(i)));// 匹配待排序字符串中的每個字符,對應在規則字符串中的索引,並添加
}
Collections.sort(strA);// 將字符的索引從小到大排序
String sortWp = "";
for (int i = 0; i < strA.size(); i++) {
sortWp = sortWp + sortS.charAt(strA.get(i));// 通過索引獲取對應字符
}
return sortWp;
}
// ------------------------方法2-2: 正則表達式-------------------
public static int[] regexMatches(String wp) {
int[] array = { -1, 0 };// 初始"牌數據"
String sCards = "34567890JQKA2小大";// 單牌=規則大小牌
String linkCards1 = "34567890JQKA";// 單連牌
String linkCards2 = "3344556677889900JJQQKKAA";// 對連牌
String linkCards3 = "333444555666777888999000JJJQQQKKKAAA";// 3連牌
String linkCards4 = "33334444555566667777888899990000JJJJQQQQKKKKAAAA";// 4連牌
// 匹配出牌屬於哪一類型
int index = -1;
if (wp.length() == 1) {
index = sCards.indexOf(wp);
array[0] = index;
array[1] = 1;
return array;// 單牌
} else if (linkCards1.contains(wp) && wp.length() > 4) {
index = linkCards1.indexOf(wp);
array[0] = index;
array[1] = 11;
return array;// 單連牌
} else if (wp.matches("(.)\\1")) {
index = sCards.indexOf(wp.charAt(0));
array[0] = index;
array[1] = 2;
return array;// 對牌
} else if (linkCards2.contains(wp) && wp.length() > 5 && linkCards2.indexOf(wp) % 2 == 0) {
index = linkCards2.indexOf(wp);
array[0] = index;
array[1] = 21;
return array;// 對連牌
} else if (wp.matches("(.)\\1\\1")) {
index = linkCards3.indexOf(wp);
array[0] = index;
array[1] = 3;
return array;// 三牌
} else if (!wp.matches("(.)\\1{3}") && (wp.matches(".(.)\\1\\1")) | wp.matches("(.)\\1\\1.")) {// 防止誤匹配4個相同的
index = sCards.indexOf(wp.charAt(2));// 獲取中間字符值,對應規則字符串中的索引,作爲比較
array[0] = index;
array[1] = 31;
return array;// 三帶一
} else if (linkCards3.contains(wp) && linkCards3.indexOf(wp) % 3 == 0 && wp.length() > 5) {
index = linkCards3.indexOf(wp);
array[0] = index;
array[1] = 33;
return array;// 三連牌
} else if (wp.length() > 7 && linkCards3.contains(wp.replaceAll("[" + wp.replaceAll("(.)\\1\\1", "") + "]", ""))
&& wp.replaceAll("[" + wp.replaceAll("(.)\\1\\1", "") + "]", "").length() / 3 == wp
.replaceAll("(.)\\1\\1", "").length()) {
// 長度8 12 // 滿足連續關係// 三帶1模式:匹配3個相同的數量/3等於額外數 ==4的倍數
index = linkCards3.indexOf(wp.replaceAll("[" + wp.replaceAll("(.)\\1\\1", "") + "]", ""));
array[0] = index;
array[1] = 3311;
return array;// 飛機
} else if (linkCards4.contains(wp) && linkCards3.indexOf(wp) % 4 == 0) {
array[0] = linkCards4.indexOf(wp);
array[1] = 44;
return array;// 四連牌
} else if (wp.matches("(.)\\1{3}.{2}") | wp.matches("..(.)\\1{3}")) {
index = sCards.indexOf(wp.charAt(2));// 44|5555|66
array[0] = index;
array[1] = 42;
return array;// 四帶二
} else if (wp.matches("(.)\\1{3}") | wp.equals("小大")) {
index = sCards.indexOf(wp.charAt(0));
array[0] = index;
array[1] = 10000;
return array;// 炸彈
} else {
if (wp.matches("beforeW")) {// 初始值,則返回默認類型
return array;
} else {
System.out.println("你輸入的牌不符合規則");
array[0] = -2;// 用於終止繼續執行條件
return array;
}
}
}
}