單機鬥地主之完整功能初版
阿新 • • 發佈:2018-12-16
一、鬥地主規則介紹
1.1 基本規則
參加人數:3人
總牌數:54張,從2到A,四種花色("梅花", "紅桃", "黑桃", "方形"),共52張,加黑白色的小王,彩色的大王。
分牌數:每人先分17張,最後確定地主拿3張
勝利規則:三人中有一人出完牌
1.2 出牌規則
單張:任意一張牌
對子:兩張一樣的牌
順子:大於四張,連續的牌
三帶一:三張一樣的牌帶任意一張
炸彈:四張一樣,或雙王
1.3 大小規則
單張:從3到10,J,Q,K,A,2,小王,大王依次增大
對子:通單張規則
三帶一:三張牌滿足單張規則
順子:最小牌滿足單張規則
炸彈:具有通吃屬性,王炸無敵,四炸滿足單張規則
二、程式碼實現規則
2.1 定義撲克牌
每張撲克牌應該具有自己的花色與值
/** * 定義撲克牌 */ class Card implements Comparable<Card> { /** * 花色(黑桃、紅桃、梅花、方塊) */ private String color; /** * 大小(2-10,J,Q,K,A,大王,小王) */ private String value; public Card() { } public Card(String color, String value) { this.color = color; this.value = value; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public boolean equals(Object obj) { if (obj instanceof Card) { Card card = (Card) obj; if (card.value.equalsIgnoreCase(this.value) && card.color.equalsIgnoreCase(this.color)) { return true; } } return false; } @Override public int hashCode() { return color.hashCode() + value.hashCode(); } //定義大小規則 @Override public int compareTo(Card card) { return findIndex(this) - findIndex(card); } @Override public String toString() { final StringBuilder sb = new StringBuilder("Card{"); sb.append("color='").append(color).append('\''); sb.append(", value='").append(value).append('\''); sb.append('}'); return sb.toString(); } }
2.2 定義玩家
每個玩傢俱有自己的遊戲名,在遊戲中具有自己的上家與下家,持有牌與預出牌,並記錄當前是否具有出牌資格
/** * 定義玩家 */ static class Player { /** * 使用者名稱 */ private String username; /** * 是否為第一個出牌 */ private boolean first; /** * 持有牌 */ LinkedList<Card> cards = new LinkedList<>(); /** * 當前選擇出牌 */ LinkedList<Card> putCards = new LinkedList<>(); /** * 上一位出牌人 */ private Player before; /** * 下一位出牌人 */ private Player next; public Player() { } public Player(String username) { this.username = username; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public void setCards(LinkedList<Card> cards) { this.cards = cards; } public boolean isFirst() { return first; } public void setFirst(boolean first) { this.first = first; } public LinkedList<Card> getCards() { return cards; } public LinkedList<Card> getPutCards() { return putCards; } public void setPutCards(LinkedList<Card> putCards) { this.putCards = putCards; } public Player getBefore() { return before; } public void setBefore(Player before) { this.before = before; } public Player getNext() { return next; } public void setNext(Player next) { this.next = next; } @Override public String toString() { final StringBuilder sb = new StringBuilder("Player{"); sb.append("username='").append(username).append('\''); sb.append(", cards=").append(cards); sb.append(", putCards=").append(putCards); sb.append('}'); return sb.toString(); } }
2.3 定義遊戲規則
一場完整的鬥地主,需要洗牌,分牌,確定地主,判斷出牌是否大於上兩家
/**
* 玩撲克牌
*
* @author sunyiran
* @date 2018-10-19
*/
public class PlayCards {
/**
* 撲克牌的值
*/
private static final String[] cardVlues = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2", "小王",
"大王"};
/**
* 撲克牌的花色
*/
private static final String[] cardColors = {"梅花", "紅桃", "黑桃", "方形"};
/**
* 一副撲克牌
*/
private ArrayList<Card> cardList = new ArrayList<>(54);
/**
* 參加玩家
*/
private Player[] players;
public PlayCards() {
}
public PlayCards(Player[] players) {
getCardArr();
this.players = players;
}
/**
* 獲取一副撲克牌的演算法
*/
private void getCardArr() {
//前52張牌具有花色
for (int i = 0; i < 52; i++) {
Card card = new Card();
card.setColor(cardColors[i % 4]);
card.setValue(cardVlues[i % 13]);
cardList.add(card);
}
cardList.add(new Card("黑白", "小王"));
cardList.add(new Card("彩色", "大王"));
}
/**
* 獲取撲克牌大小位置
*
* @param card
* @return
*/
private int findIndex(Card card) {
for (int i = 0; i < cardVlues.length; i++) {
if (card.value.equalsIgnoreCase(cardVlues[i])) {
return i;
}
}
return -1;
}
/**
* 洗牌
*
* @param player 上輪贏家
*/
private List<Card> shuffle(Player player) {
//清除玩家手裡的牌
Arrays.stream(players).forEach(x -> {
LinkedList<Card> cards = x.cards;
cards.clear();
});
//贏家取消優先順序
if (player != null)
player.setFirst(false);
//採用隨機策略洗牌
Random random = new Random();
for (int i = 0; i < cardList.size(); i++) {
Card temp = cardList.get(i);
//要交換的索引
int changeIndex = random.nextInt(54);
cardList.set(i, cardList.get(changeIndex));
cardList.set(changeIndex, temp);
}
return cardList;
}
/**
* 分牌
*/
private void distributional() {
//留下三張給地主
for (int i = 0; i < 51; i++) {
if (i < 17) {
players[0].getCards().add(cardList.get(i));
}
if (i >= 17 && i < 34) {
players[1].getCards().add(cardList.get(i));
}
if (i >= 34) {
players[2].getCards().add(cardList.get(i));
}
}
//隨機確定地主
players[new Random().nextInt(3)].setFirst(true);
//地主拿三張
Arrays.stream(players).filter(Player::isFirst).forEach(x -> {
LinkedList<Card> cards = x.getCards();
cards.add(cardList.get(51));
cards.add(cardList.get(52));
cards.add(cardList.get(53));
System.out.println("當前地主: " + x.getUsername());
});
//按順序排序
players[0].getCards().sort(Card::compareTo);
players[1].getCards().sort(Card::compareTo);
players[2].getCards().sort(Card::compareTo);
}
/**
* 判斷出牌是否合法(根據出牌規則)
*
* @param player
* @return 個位代表出牌個數,個位相同時,值高低也代表大小級別(可以用Map優化)
*/
private int compliance(Player player) {
LinkedList<Card> cards = player.getPutCards();
LinkedList<Card> hasCards = player.getCards();
//可以不出牌
if (cards.size() == 0) {
return 0;
}
//每張牌必須是自己手裡的牌
long count = cards.stream().filter(x -> !hasCards.contains(x)).count();
if (count > 0) {
return -1;
}
//如果是單張牌,只要在牌中即可
if (cards.size() == 1) {
return 1 + (findIndex(cards.get(0)) + 1) * 10;
}
//如果是兩張,則為對子或王炸
if (cards.size() == 2 && cards.contains(new Card("彩色", "大王")) && cards.contains(new Card("黑白", "小王"))) {
return 20000002;
}
if (cards.size() == 2 && cards.get(0).value.equals(cards.get(1).value)) {
return 2 + (findIndex(cards.get(0)) + 1) * 100;
}
//如果是四張,則為三帶一,或者炸彈
//判斷是否為炸彈
boolean bomb = cards.get(0).value.equals(cards.get(1).value) && cards.get(1).value.equals(cards.get(2)
.value) && cards.get(2).value.equals(cards.get(3).value);
if (cards.size() == 4) {
if (bomb) {
return 4 + (findIndex(cards.get(0)) + 1) * 100000;
} else {
//偷懶,第一張牌不允許為單張
return 4 + (findIndex(cards.get(0)) + 1) * 1000;
}
}
//大於四張,則為連子
if (cards.size() > 4) {
for (int i = 1; i < cards.size(); i++) {
if (findIndex(cards.get(i)) != findIndex(cards.get(i - 1)) + 1) {
return -1;
}
}
return cards.size() + (findIndex(cards.get(0)) + 1) * 1000;
}
return -1;
}
/**
* 判斷出牌是否合法(根據大小規則)
*
* @param player
* @return
*/
private boolean compareSize(Player player) {
//出牌一定比上兩家大
//自己
int self = compliance(player);
String v1 = String.valueOf(self);
//上方第一家
int before1 = compliance(player.before);
String v2 = String.valueOf(self);
//上方第二家
int before2 = compliance(player.before.before);
String v3 = String.valueOf(self);
if (before1 == 0 && before2 == 0 && self > 0) {
return true;
}
if (self == 0) {
return true;
}
int amout1 = Integer.parseInt(v1.substring(v1.length() - 2));
int amout2 = Integer.parseInt(v2.substring(v2.length() - 2));
int amout3 = Integer.parseInt(v3.substring(v3.length() - 2));
if (self == 20000002) {
return true;
}
if (before1 > 0 && (amout1 == amout2 || self > 100003) && self > before1) {
return true;
}
if (before1 == 0 && before2 > 0 && (amout1 == amout3 || self > 100003) && self > before2) {
return true;
}
System.out.println("您的出牌沒能大於上家");
return false;
}
}
2.4 定義遊戲流程
玩家在控制檯輸入想要打出的牌,一行一張牌,輸入end結束。每次會顯示上兩家出牌記錄,已經自身當前持有牌組
/**
* 比賽流程
* @param players 參加的玩家
*/
public static void start(Player[] players) {
// //定義玩家
// Player p1 = new Player("一號");
// Player p2 = new Player("二號");
// Player p3 = new Player("三號");
//
// p1.setNext(p2);
// p1.setBefore(p3);
// p2.setNext(p3);
// p2.setBefore(p1);
// p3.setNext(p1);
// p3.setBefore(p2);
//開局
PlayCards play = new PlayCards(players);
//洗牌
play.shuffle(null);
//分牌
play.distributional();
Scanner sc = new Scanner(System.in);
boolean exit = play.players[0].getCards().size() > 0 && play.players[1].getCards().size() > 0 && play
.players[2].getCards().size() > 0;
while (exit) {
Player player = Arrays.stream(play.players).filter(Player::isFirst).findFirst().get();
//顯示上兩家出牌
System.out.println("上兩家出牌為");
System.out.println(player.before.getPutCards());
System.out.println(player.before.before.getPutCards());
//當前玩家輸出的一組牌
LinkedList<Card> putCards = player.getPutCards();
//從手裡清除打出的牌
player.getCards().removeAll(putCards);
//清除上次出牌記錄
putCards.clear();
System.out.println(player.getUsername() + "玩家請出牌");
System.out.println("您當前持有的卡牌:" + player.getCards());
//控制檯輸入輸出的每張牌
String line;
while (!(line = sc.nextLine()).equalsIgnoreCase("end")) {
String[] split = line.split("[:|:]");
if (split.length != 2) {
System.out.println("請按規則輸入");
continue;
}
Card card = play.new Card(split[0], split[1]);
putCards.add(card);
}
//如果當前玩家出牌符合規定,則下一位玩家出牌,否則重新出牌
if (play.compliance(player) > -1 && play.compareSize(player)) {
player.setFirst(false);
player.getNext().setFirst(true);
} else {
putCards.clear();
System.out.println("請選擇正確的牌組重新出牌");
}
}
}
三、體驗鬥地主
更多規則測試,請下載原始碼,在本地執行
四、擴充套件方向
相對於完整的網路鬥地主:
1、圖形介面:可以採用Swing做簡單開發,也可以基於安卓或IOS做App開發,當然Web介面也是不錯的
2、網路圖形:可以用socket通訊實現多人線上遊戲