1. 程式人生 > >炸金花絕對大小計算、比較及排序演算法(Java),包含花色參與和不參與大小比較

炸金花絕對大小計算、比較及排序演算法(Java),包含花色參與和不參與大小比較

昨日忽生興趣,想起同事正在玩的一個炸金花遊戲,見他們討論略有激烈,想來蠻有趣,於是自己也寫來玩玩。
因有要一次產生很多副牌的需求(可能上1000),要對所有的玩家進行一個排序,因此考慮一個能得到每幅牌的絕對大小的統一演算法。

我有王炸

牌大小計算演算法

花色不參與大小比較:

  • 首先對到手的牌按照牌數字按照由大到小排序
  • 牌大小按照牌型分級
  • 對於普通牌型,每張牌視為16進位制的一個數,A對應14,2對應2,以此類推。牌值即為這幅此16進位制牌的大小。
    比如最大的普通牌為AKJ,其16進位制數值為AKJ=14x16x16+13x16+11=3803
  • 對於對子,先將對子放在牌的前兩位,則在最大普通牌大小的基礎上,加上對子牌的本身大小。 對子的本身大小計算方法:比如最大的對子為AAK,則AAK=14x16+13=237,加上最大的普通牌值3803,即為4040
  • 對於順子,取最小的那個數,加上最大的對子牌值,比如最大的順子AKQ=12+4040=4052。最小的順子A32,A取1,值4041
  • 對於同花,先按照普通牌型計算大小,再加上最大的對子牌值。
    比如最大的同花AKJ=3803+4052=7855
  • 對於同花順,取最小的那個數,加上最大的同花牌值,比如:
    AKQ=12+7855=7867,最小的同花順A32,A取1,A32=1+7855=7856
  • 對於炸彈,取第一個數,加上最大的同花順牌值。
    比如AAA=14+7867=7881

花色參與大小比較:

  • 比較規則:在牌數字完全一樣的情況下,從最大的牌開始比較,黑桃>紅桃>梅花>方片,遇到一個較大的,則結束比較。如:紅桃A+紅桃Q+方片3>梅花A+黑桃Q+黑桃3。如遇順子時,數字3最大,從3開始比較花色。
  • 花色值設定:黑桃=3紅桃=2梅花=1方片=0。
  • 牌值計算原理:在上面花色不參與大小比較演算法的基礎上,增加對每副計算出來的牌值乘以64再加上對三張牌花色按照4進位制進行花色值計算作為附加值。比如:不考慮花色時,紅桃6+方片4+方片2的值為6x256+4x16+2=1602,考慮花色時,紅桃+方片+方片對應的4進位制就是200,其10進位制值為32,然後這副牌的牌值即為:1602x64+32=102560。為什麼乘以64?因為三個花色4進位制值的範圍為63~0。乘以64,就是把原來每組牌值大小相鄰的牌型拉開63個的間隔,以便於讓花色值有發揮的空間哈哈,用來區別數字完全相同但花色不同的牌型。
  • 如果是炸彈,先將炸彈按花色從大到小排序,保證比如黑桃A紅桃A方片A會>紅桃A梅花A方片A

如此,就可得對所有的牌值進行了統一的大小計算

程式碼實現

主要分為四個模組
- entity 實體
- player provider 發牌器
- typevalue 牌型識別
- calculator 計算器,提供不同方式的計算方法

實體類

Card 單張牌

/**
 * 單張牌
 * 
 * @author Leon
 *
 */
public class Card {

    public static final int FLOWER_SPADE = 3;// 黑桃
    public static final int FLOWER_HEART = 2;// 紅桃
    public static final int FLOWER_CLUB = 1;// 梅花
    public static final int FLOWER_DIAMOND = 0;// 方片

    public static final int NUM_A = 14;
    public static final int NUM_K = 13;
    public static final int NUM_Q = 12;
    public static final int NUM_J = 11;
    public static final int NUM_10 = 10;
    public static final int NUM_9 = 9;
    public static final int NUM_8 = 8;
    public static final int NUM_7 = 7;
    public static final int NUM_6 = 6;
    public static final int NUM_5 = 5;
    public static final int NUM_4 = 4;
    public static final int NUM_3 = 3;
    public static final int NUM_2 = 2;

    // 單張牌大小
    private int number;
    // 花色
    private int flower;

    public Card() { }

    public Card(int flower, int number) {
        this.flower = flower;
        this.number = number;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public int getFlower() {
        return flower;
    }

    public void setFlower(int flower) {
        this.flower = flower;
    }

}

玩家

/**
 * 玩家,對應一副牌
 * 
 * @author Leon
 *
 */
public class Player {

    public Card[] cards = new Card[3];
    // 牌型別
    private int type;
    // 是否為特殊牌
    private boolean isSpecial = false;
    // A32也是順子,比花色時,從3開始比較
    private boolean isA32 = false;
    // 牌絕對值大小
    private int value;

    public Player() {
    }

    public Player(Card card0, Card card1, Card card2) {
        this.cards[0] = card0;
        this.cards[1] = card1;
        this.cards[2] = card2;
    }

    public Card[] getCards() {
        return cards;
    }

    public void setCards(Card[] cards) {
        this.cards = cards;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public boolean isSpecial() {
        return isSpecial;
    }

    public void setSpecial(boolean isSpecial) {
        this.isSpecial = isSpecial;
    }

    public boolean isA32() {
        return isA32;
    }

    public void setA32(boolean isA32) {
        this.isA32 = isA32;
    }

}

PlayerProvider

發牌器介面,負責發牌、洗牌

/**
 * 發牌器
 * 
 * @author Leon
 *
 */
public interface PlayerProvider {

    // 產生單副牌
    Player getSinglePlayer();

    // 產生多副牌
    List<Player> getPlayers(int number);

    // 發一張牌
    Card getCard();

    // 洗牌
    void shuffle();
}

其具體實現有
- LimitedPlayerProvider 有限制的發牌器,從一幅牌中發牌
- UnlimitedPlayerProvider 無玩家人數限制的發牌器,隨機產生牌,不考慮玩家牌完全相同的情況,但一個玩家手中不會出現完全相同的牌。

TypeValueSetter

負責對一副牌進行牌型鑑定,並根據牌型使用指定的計算器計算牌值

/**
 * 牌型識別器,負責鑑定牌型,並按照指定的模式計算牌大小
 * 
 * @author Leon
 *
 */
public class TypeValueSetter {

    private ValueCalculator calculator;

    public TypeValueSetter(ValueCalculator calculator) {
        this.calculator = calculator;
    }

    // 判斷牌型、計算牌型絕對值大小
    public Player regPlayerType(Player player) {
        if (isFlush(player)) {
            if (isStraight(player)) {// 同花順
                player.setType(PlayerType.STRAIGHT_FLUSH);
                player.setValue(calculator.getStraightFlushValue(player));
            } else {// 同花
                player.setType(PlayerType.FLUSH);
                player.setValue(calculator.getFlushValue(player));
            }
        } else if (isStraight(player)) {// 順子
            player.setType(PlayerType.STRAIGHT);
            player.setValue(calculator.getStraightValue(player));
        } else if (isDouble(player)) {
            if (isBmob(player)) {// 炸彈
                player.setType(PlayerType.BOMB);
                player.setValue(calculator.getBombValue(player));
            } else {// 對子
                player.setType(PlayerType.DOUBLE);
                // 將對子放到玩家牌的前兩張的位置,以便於之後的牌值計算
                PlayerUtil.moveDouble2Front(player);
                player.setValue(calculator.getDoubleValue(player));
            }
        } else {// 普通牌
            player.setType(PlayerType.NORMAL);
            player.setValue(calculator.getNormalValue(player));
            if (isSpecial(player)) {// 對於特殊牌,本演算法不提供特殊大小計算,外部呼叫者自行判斷是否有炸彈玩家產生
                player.setSpecial(true);
            }
        }
        return player;
    }

    // 是否同花
    private boolean isFlush(Player player) {
        return player.cards[0].getFlower() == player.cards[1].getFlower()
                && player.cards[1].getFlower() == player.cards[2].getFlower();
    }

    // 是否順子,A32也是同花順,是最小的同花順(參考自百度百科)
    // 花色參與比較的時候,黑桃A紅桃3黑桃2<方片A黑桃3方片2
    private boolean isStraight(Player player) {
        boolean isNomalStraight = player.cards[0].getNumber() == player.cards[1].getNumber() + 1
                && player.cards[1].getNumber() == player.cards[2].getNumber() + 1;
        boolean isA32 = player.cards[0].getNumber() == 14 && player.cards[1].getNumber() == 3
                && player.cards[2].getNumber() == 2;
        if (isA32) {
            player.setA32(true);
        }
        return isNomalStraight || isA32;
    }

    // 是否炸彈
    private boolean isBmob(Player player) {
        return player.cards[0].getNumber() == player.cards[1].getNumber()
                && player.cards[1].getNumber() == player.cards[2].getNumber();
    }

    // 是否對子
    private boolean isDouble(Player player) {
        return player.cards[0].getNumber() == player.cards[1].getNumber()
                || player.cards[1].getNumber() == player.cards[2].getNumber();
    }

    // 是否特殊牌
    private boolean isSpecial(Player player) {
        return player.cards[0].getNumber() == 5 && player.cards[1].getNumber() == 3 && player.cards[2].getNumber() == 2;
    }
}

ValueCalculator

牌值計算器介面,負責提供對一副牌的計算方法,

/**
 * 牌值計算器
 * 
 * @author Leon
 *
 */
public interface ValueCalculator {
    // 獲取炸彈牌值絕對大小
    int getBombValue(Player player);

    // 獲取同花順牌值絕對大小
    int getStraightFlushValue(Player player);

    // 獲取同花牌值絕對大小
    int getFlushValue(Player player);

    // 獲取順子牌值絕對大小
    int getStraightValue(Player player);

    // 獲取對子牌值絕對大小
    // 在判斷牌型時,如果是對子,則將對子放在陣列前面兩位
    int getDoubleValue(Player player);

    // 獲取普通牌值絕對大小
    int getNormalValue(Player player);

}

具體實現有:
NonFlowerValueCalculator
花色不參與大小比較的計算器,牌越大,牌值越大


/**
 * 花色不參與牌大小比較的計算器
 * 牌值越大,牌越大
 * @author Leon
 *
 */
 public class NonFlowerValueCalculator implements  ValueCalculator {

    // 獲取炸彈牌值絕對大小
    public int getBombValue(Player player) {
        return player.cards[0].getNumber() + PlayerType.STRAIGHT_FLUSH_MAX_VALUE;
    }

    // 獲取同花順牌值絕對大小,A32也是同花順,是最小的同花順(參考自百度百科)
    public int getStraightFlushValue(Player player) {
        if (player.isA32()) {
            //此時A就等於是1
            return 1 + PlayerType.FLUSH_MAX_VALUE;
        }
        return player.cards[2].getNumber() + PlayerType.FLUSH_MAX_VALUE;
    }

    // 獲取同花牌值絕對大小
    public int getFlushValue(Player player) {
        return player.cards[0].getNumber() * 256 + player.cards[1].getNumber() * 16 + player.cards[2].getNumber()
                + PlayerType.STRAIGHT_MAX_VALUE;
    }

    // 獲取順子牌值絕對大小
    public int getStraightValue(Player player) {
        if (player.isA32()) {
            //此時A就等於是1
            return 1 + PlayerType.DOUBLE_MAX_VALUE;
        }
        return player.cards[2].getNumber() + PlayerType.DOUBLE_MAX_VALUE;
    }

    // 獲取對子牌值絕對大小
    // 在判斷牌型時,如果是對子,則將對子放在陣列前面兩位
    public int getDoubleValue(Player player) {
        return player.cards[1].getNumber() * 16 + player.cards[2].getNumber() + PlayerType.NORMAL_MAX_VALUE;
    }

    // 獲取普通牌值絕對大小
    public int getNormalValue(Player player) {
        return player.cards[0].getNumber() * 256 + player.cards[1].getNumber() * 16 + player.cards[2].getNumber();
    }
}

FlowerValueCalculator
花色參與大小比較的計算器,牌越大,牌值越大

 /**
 * 花色參與牌值大小比較的計算器,牌值越大,牌越大
 *
 * @author Leon
 *
 */
public class FlowerValueCalculator implements ValueCalculator {

    private int getFlowerValue(Player player) {
        return player.cards[0].getFlower() * 16 + player.cards[1].getFlower() * 4 + player.cards[2].getFlower();
    }

    private int getA32FlowerValue(Player player){
        return player.cards[1].getFlower() * 16 + player.cards[2].getFlower() * 4 + player.cards[0].getFlower();
    }

    // 獲取炸彈牌值絕對大小
    public int getBombValue(Player player) {
        // 炸彈需要先對牌按花色大小排序,保證比如黑桃A紅桃A方片A會>紅桃A梅花A方片A
        PlayerUtil.sortPlayerByFlower(player);
        return (player.cards[0].getNumber() + PlayerType.STRAIGHT_FLUSH_MAX_VALUE) * 64 + getFlowerValue(player);
    }

    // 獲取同花順牌值絕對大小,A32也是同花順,是最小的同花順(參考自百度百科)
    public int getStraightFlushValue(Player player) {
        if (player.isA32()) {
            return (1 + PlayerType.FLUSH_MAX_VALUE) * 64 + getA32FlowerValue(player);
        }
        return (player.cards[2].getNumber() + PlayerType.FLUSH_MAX_VALUE) * 64 + getFlowerValue(player);
    }

    // 獲取同花牌值絕對大小
    public int getFlushValue(Player player) {
        return (player.cards[0].getNumber() * 256 + player.cards[1].getNumber() * 16 + player.cards[2].getNumber()
                + PlayerType.STRAIGHT_MAX_VALUE) * 64 + getFlowerValue(player);
    }

    // 獲取順子牌值絕對大小
    public int getStraightValue(Player player) {
        if (player.isA32()) {
            return (1 + PlayerType.DOUBLE_MAX_VALUE) * 64 + getA32FlowerValue(player);
        }
        return (player.cards[2].getNumber() + PlayerType.DOUBLE_MAX_VALUE) * 64 + getFlowerValue(player);
    }

    // 獲取對子牌值絕對大小
    // 在判斷牌型時,如果是對子,則將對子放在陣列前面兩位
    public int getDoubleValue(Player player) {
        // 在花色參與計算大小時,將對子中的花色大的換到前面
        PlayerUtil.exchangeSortedDoubleFlower(player);
        return (player.cards[1].getNumber() * 16 + player.cards[2].getNumber() + PlayerType.NORMAL_MAX_VALUE) * 64
                + getFlowerValue(player);
    }

    // 獲取普通牌值絕對大小
    public int getNormalValue(Player player) {
        return (player.cards[0].getNumber() * 256 + player.cards[1].getNumber() * 16 + player.cards[2].getNumber()) * 64
                + getFlowerValue(player);
    }
}

PlayerType類

/**
 * 對牌型分類,並提供牌大小值的演算法,和已經計算好的牌型最大值 
 * 
 * @author Leon
 *
 */
public class PlayerType {
    // 炸彈
    public static final int BOMB = 5;
    // 最大值AAA=14,加上同花順6867=7881
    public static final int BOMB_MAX_VALUE = 7881;

    // 同花順,A32也是順子,是最小的同花順(參考自百度百科)
    public static final int STRAIGHT_FLUSH = 4;
    // 最大值AKQ=12,加上同花7855=7867
    public static final int STRAIGHT_FLUSH_MAX_VALUE = 7867;

    // 同花
    public static final int FLUSH = 3;
    // 最大值AKJ,14*16*16+13*16+11=3803,加上順子4052=7855
    public static final int FLUSH_MAX_VALUE = 7855;

    // 順子,A32也是順子,是最小的同花順(參考自百度百科)
    public static final int STRAIGHT = 2;
    // 最大值AKQ=12,加上對子的最大值基數4040=4052
    public static final int STRAIGHT_MAX_VALUE = 4052;

    // 對子
    public static final int DOUBLE = 1;
    // 最大值AAK=14*16+13=237,加上普通牌的基數3803=4040
    public static final int DOUBLE_MAX_VALUE = 4040;

    // 普通牌,裡面包含一種特殊牌532不同花色
    // 對於特殊牌,本演算法不提供特殊大小計算,但會將這副牌標記為特殊牌
    // 外部呼叫者自行判斷是否有炸彈玩家產生
    public static final int NORMAL = 0;
    // 最大值AKJ=14*16*16+13*16+11=3803
    public static final int NORMAL_MAX_VALUE = 3803;

}

還有兩個計算器就不貼程式碼了:

Low2HeighCalculator
花色不參與大小比較的計算器,牌越大,牌值越小
FlowerLow2HeighCalculator
花色參與大小比較的計算器,牌越大,牌值越小

PlayerComparator 總指揮

負責對牌初始化、牌型識別、牌值計算以及排序。你給我一副或者一組凌亂的牌,我讓你整潔有序。

/**
 * 牌型判斷比較器,負責對所有玩家的牌大小進行計算和排序
 * 
 * @author Leon
 *
 */
public class PlayerComparator implements Comparator<Player> {

    private TypeValueSetter recognizer;

    public PlayerComparator(ValueCalculator calculator) {
        this.recognizer = new TypeValueSetter(calculator);
    }

    /**
     * 對玩家牌型進行牌型識別和牌值計算 這副牌三張都已經按照數字從大到小排好序
     * 
     * @param player
     *            一副牌
     */
    public void setupRegularPlayer(Player player) {
        recognizer.regPlayerType(player);
    }

    /**
     * 對玩家牌型進行牌型識別和牌值計算 這副牌沒有按照數字從大到小排好序
     * 
     * @param player
     *            一副牌
     */
    public void setupUnRegularPlayer(Player player) {
        PlayerUtil.sortPlayerByNumber(player);
        recognizer.regPlayerType(player);
    }

    /**
     * 對玩家列表進行牌型判斷、值獲取及排序 每副牌的三張牌都已經按照數字從大到小排序
     * 
     * @param playersInput
     *            一組牌
     */
    public void sortRegularPlayers(List<Player> playersInput) {
        for (Player player : playersInput) {
            recognizer.regPlayerType(player);
        }
        Collections.sort(playersInput, this);
    }

    /**
     * 對玩家列表進行牌型判斷、值獲取及排序 每副牌的三張牌沒有按照從大到小排序
     * 
     * @param playersInput
     *            一組牌
     */
    public void sortUnRegularPlayers(List<Player> playersInput) {
        for (Player player : playersInput) {
            PlayerUtil.sortPlayerByNumber(player);
            recognizer.regPlayerType(player);
        }
        Collections.sort(playersInput, this);
    }

    @Override
    public int compare(Player player1, Player player2) {
        return Integer.valueOf(player1.getValue()).compareTo(Integer.valueOf(player2.getValue()));
    }
}

PS:本文原本發表在簡書上,後搬到了csdn哈