炸金花绝对大小计算、比较及排序算法(Java),包含花色参与和不参与大小比较

昨日忽生兴趣,想起同事正在玩的一个炸金花游戏,见他们讨论略有激烈,想来蛮有趣,于是自己也写来玩玩。
因有要一次产生很多副牌的需求(可能上1000),要对所有的玩家进行一个排序,因此考虑一个能得到每幅牌的绝对大小的统一算法。

我有王炸

牌大小计算算法

花色不参与大小比较:
  • 首先对到手的牌按照牌数字按照由大到小排序,比如拿到3K6,排序为K63
  • 牌大小按照牌型分级。即普通牌、对子、顺子、同花顺、炸弹
  • 对于普通牌型,每张牌视为16进制的一个数,A对应14(e),2对应2,以此类推。牌值即为这幅16进制牌的大小
    比如最大的普通牌为AKJ,其16进制数值为edc,转为10进制则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()));
    }
}

完整代码在Github上

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • 数学中存在这样一个序列,它充满魔力,在实际工程中也有一部分的应用。今天就打算分享一下这个序列,它在 Google ...
    一缕殇流化隐半边冰霜阅读 6,771评论 34 39
  • 哲学家叔本华曾说过:“人性一个最特别的弱点就是,在意别人如何看待自己。” 对于我们这些凡夫俗子来说,永远不...
    鷺風阅读 306评论 0 2
  • 不遇明师莫枉参,不遇知音莫枉传不学空灵难为道,功夫不到总是迷道本自然一气游,空空静静最难求得来万法皆无用,身形应当...
    刘爽Terry阅读 1,336评论 0 0
  • “如止水,如明月。一波不兴,一尘不染,而无所怯惑,无所思凝,神意湛然,气息悠然之候也。” 这句话是颜回大师兄所描绘...
    Larissa阅读 524评论 0 0
  • 选择是个贯穿生命的历程! 2017.9.19 周二,晴! 今日天清气爽,周五突然收到的赴新加坡培训2周通知再次打破...
    玛雅是只猫阅读 223评论 0 0