牌大小计算算法
花色不参与大小比较:
- 首先对到手的牌按照牌数字按照由大到小排序
- 牌大小按照牌型分级
- 对于普通牌型,每张牌视为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())); } }