斗地主游戏java底层实现


gitee地址

介绍

本文介绍了斗地主小游戏玩法规则,以及详细的java代码底层实现。 采用命令行模式,可实现3人斗地主,简单模拟斗地主的全部功能,玩法规则和qq斗地主类似。由于重点不在于界面效果,所以主要习java开发的基本功,比如工厂模式创建对象,ThreadLocal应用,随机算法,轮询算法实现,java8 stream编程等。感兴趣的小伙伴可以进来瞅一瞅,欢迎大家留言指教。

软件架构

软件架构说明
使用jdk1.8以上版本,maven项目构建,项目中使用了lombok插件

主要类说明

  • CardMain 程序启动类,main方法运行
  • CardApp 游戏入口,启动,结束等方法
  • BaseContext 全局参数容器,包含上家标识(身份,回答标识),上家打出的牌型和牌数,当前玩家标识(身份)
  • PlatformManager 平台管理器 初始化纸牌,洗牌,发牌,注册容器,管理玩家等方法
  • PlayerGeneral 玩家模型生成器
  • PlayerModel 玩家模型 定义接受牌,重组展示手牌,出牌等方法
  • RuleFactory 规则工厂 主要创建各种规则类
  • CardNumRule 纸牌数量规则,平台发完牌后开始检验
  • CardTypeRule 纸牌类型规则 校验出的牌是否符合设定的规则
  • CardCompareRule 大小规则 比较玩家出的牌大小

使用说明

1.规则
牌数规则
1.一共54张牌
2.每人17张
3.底牌三张



 单张以上的请用逗号分隔

 牌型规则
 1.可以出单张
 2.可以出一对
 3.可以出三连张
 4.可以出三带一
 5.可以出三带二
 6.可以出顺子
 6.可以出连对
 7.可以出飞机
 8.可以出炸弹
 9.可以出王炸

2.说明
-系统首先生成一个平台管理器,然后生成三个玩家,平台管理器负责管理整副牌以及所有玩家信息,比如洗牌,发牌,获取指定玩家,获取下一个玩家等
-玩家主要负责出牌
-当某一个玩家打出最后一张牌时,游戏结束,开始结算


3.流程
游戏启动->初始化->洗牌->发牌->抢地主->轮流出牌->结算
斗地主游戏java底层实现-LMLPHP
斗地主游戏java底层实现-LMLPHP
斗地主游戏java底层实现-LMLPHP
斗地主游戏java底层实现-LMLPHP




相关代码

  • CardApp 游戏入口,启动,结束等方法
/**
 * @description: 游戏入口
 * @author: ly
 * @date: 2020/8/20 19:10
 */
public class CardApp implements App {

    @Override
    public void start() throws Exception {
        //游戏界面打印
        printAppInfo();

        //游戏规则打印
        printAppRule();

        //平台初始化,洗牌,注册容器
        PlatformManager manager = (PlatformManager) PlatformManager.build().cardInit().shuffle().register(new PlatformContext());

        //开始发牌
        manager.giveCards(PlayerGeneral.general("小明"), PlayerGeneral.general("小帅"), PlayerGeneral.general("小王"));

        //平台发牌校验
        RuleFactory.create(CardNumRule.class).checkCardNum(manager);

        //开始抢地主
        grabDiZhu(manager);

        //轮番出牌
        roundLeave(manager);
    }

    private void roundLeave(PlatformManager manager) {
        //地主玩家开始出牌
        PlayerModel player = manager.getPlayer(BaseContext.getContext().getDiZhuId());
        //设置当前出牌玩家
        PlayerModel currPlayer;
        while (true) {
            currPlayer = StringUtils.isBlank(BaseContext.getContext().getCurrId()) ? player : manager.getNextPlayer();
            //打印提示信息
            printNoticeInfo(manager, currPlayer);
            Scanner scanner = new Scanner(System.in);
            String cards = scanner.nextLine();
            //出牌
            if (!currPlayer.leave(cards)) {
                continue;
            }
            //是否已经出完
            if (currPlayer.getCardList().size() == 0) {
                manager.settle(currPlayer);
                break;
            }
            BaseContext.getContext().setCurrId(currPlayer.getId());
            //必须放到这里,否则容易清屏多度
            this.cleanConsole();
        }
        this.finished();
    }

    private void printNoticeInfo(PlatformManager manager, PlayerModel currPlayer) {
        //预获取下一个玩家,重要
        String currId = BaseContext.getContext().getCurrId();
        BaseContext.getContext().setCurrId(currPlayer.getId());

        System.out.println("轮到玩家:" + currPlayer.getId() + "[" + currPlayer.getMark() + "]" + " 出牌" + " ===>" + currPlayer.order(currPlayer.getCardList()));
        System.out.println("提示:要不起请输入命令[pass],否则直接输入要打出的牌,多个请用空格分隔");
        if (StringUtils.isNotBlank(BaseContext.getContext().getPreId())) {
            System.out.println("提示:上一个玩家是 "+  BaseContext.getContext().getPreId() +
                    "["+ BaseContext.getContext().getPreMark() + "]"+ " 打出的牌["+ BaseContext.getContext().getPreGiveCards()+"]" +
                    " 下一轮玩家 "+manager.getNextPlayer().getId() + "["+manager.getNextPlayer().getMark()+"]");
        }
        //设置回去,重要
        BaseContext.getContext().setCurrId(currId);
    }

    private void grabDiZhu(PlatformManager manager) throws Exception {
        //随机获取一个玩家
        PlayerModel randomPlayer = manager.getRandomPlayer();
        //第一个玩家id
        String firstId = randomPlayer.getId();
        //打印玩家信息
        randomPlayer.print();
        //设置当前玩家标识
        BaseContext.getContext().setCurrId(randomPlayer.getId());
        //设置当前玩家
        PlayerModel currPlayer = null;
        while (true) {
            System.out.println("***开始抢地主 命令[yes/no]***");
            Scanner scanner = new Scanner(System.in);
            String command = scanner.nextLine();
            if (!command.equalsIgnoreCase(Command.YES) && !command.equalsIgnoreCase(Command.NO)) {
                System.out.println("!!!请输入正确的命令[yes/no]!!!");
                continue;
            }
            if (command.equalsIgnoreCase(Command.YES)) {
                if (currPlayer == null) {
                    currPlayer = randomPlayer;
                }
                //设置地主标识
                currPlayer.setDiZhu(true);
                break;
            } else {
                //获取下一个玩家
                currPlayer = manager.getNextPlayerRound(firstId);
                if (currPlayer == null) {
                    break;
                }
                //打印玩家信息
                currPlayer.print();
                //设置当前玩家标识
                BaseContext.getContext().setCurrId(currPlayer.getId());
            }
        }

        //是否抢地主成功
        if (currPlayer != null && currPlayer.isDiZhu()) {
            grabSucc(manager, currPlayer);
        } else {
            grabFail();
        }
    }

    /**
     * 抢地主成功
     *
     * @param manager    平台管理器
     * @param currPlayer 玩家
     */
    private void grabSucc(PlatformManager manager, PlayerModel currPlayer) {
        System.out.println("恭喜你,抢地主成功!");
        System.out.println("===========================================================");
        BaseContext.init();
        BaseContext.getContext().setDiZhuId(currPlayer.getId());
        //将底牌给当前玩家
        currPlayer.getCardList().addAll(manager.getBottom());
    }

    /**
     * 抢地主失败
     *
     * @throws Exception 异常
     */
    private void grabFail() throws Exception {
        //抢地主失败
        System.out.println("!没有人抢地主,是否重启游戏?[yes/no]");
        Scanner scanner = new Scanner(System.in);
        String command = scanner.nextLine();
        if (command.equalsIgnoreCase(Command.YES)) {
            start();
        } else {
            finished();
        }
    }

    private void printAppRule() throws InterruptedException {
        System.out.println("=============================开始介绍游戏规则===================================");
        System.out.println("本次游戏为三人局,包含抢地主,每人起手17张牌,底牌三张,地主可以拿到剩余底牌...");
        Thread.sleep(1000);
        System.out.println("每个玩家可以出单张,一对,三连张,三带一,三带二,顺子,连对,炸弹,王炸等牌型...");
        Thread.sleep(1000);
        System.out.println("每个玩家出的牌如果有多张,必须用空格分离...");
        Thread.sleep(1000);
        System.out.println("当上一个玩家打出牌后,下家可以根据提示选择相应命令或者打出对应牌型的牌...");
        Thread.sleep(1000);
        System.out.println("王炸>4张炸弹>其他...");
        System.out.println("=================================================================================");
        Thread.sleep(1000);
        System.out.println("游戏启动中,请稍等...");
        Thread.sleep(3000);
    }

    private void printAppInfo() throws InterruptedException {
        System.out.println("***********************欢迎进入小Y斗地主,祝您游戏体验愉快**********************");
        Thread.sleep(1000);
    }

    @Override
    public void finished() {
        System.out.println("******游戏结束******");
        System.exit(1);
    }

    @Override
    public void cleanConsole() {
        for (int i = 0; i < 20; i++) {
            System.out.println();
        }
    }
}
  • BaseContext 全局参数容器,包含上家标识(身份,回答标识),上家打出的牌型和牌数,当前玩家标识(身份)
/**
 * @description: 基础参数容器
 * @author: ly
 * @date: 2020/8/24 17:25
 */
public class BaseContext {
    public static ThreadLocal<PlatformContext> threadLocal = new ThreadLocal<>();

    public static void setContext(PlatformContext context) {
        threadLocal.set(context);
    }

    public static PlatformContext getContext() {
        return threadLocal.get();
    }

    /**
     * 移除context
     */
    public static void remove() {
        threadLocal.remove();
    }

    /**
     * 初始化context
     */
    public static void init() {
        threadLocal.remove();
        setContext(new PlatformContext());
    }
}
/**
 * @description: 平台全局参数
 * 全局变量参数,上家标识(身份,回答标识),上家打出的牌型和牌数,当前玩家标识(身份)
 * @author: ly
 * @date: 2020/8/21 10:58
 */
@Data
public class PlatformContext implements Context {
    /**
     * 上家标识
     */
    private String preId;
    /**
     * 上家身份
     */
    private String preMark;
    /**
     * 上家牌型
     */
    private CardTypeEnum preType;
    /**
     * 上家打出的牌,空格分隔
     */
    private String preGiveCards;
    /**
     * 当前玩家id
     */
    private String currId;

    /**
     * 地主id
     */
    private String diZhuId;

}
/**
 * @description: 定义全局参数容器
 * @author: ly
 * @date: 2020/8/20 17:06
 */
public interface Context {
}
  • PlatformManager 平台管理器 初始化纸牌,洗牌,发牌,注册容器,管理玩家等方法
/**
 * @description: 纸牌管理器接口
 * 定义洗牌,发牌方法,结算方法
 * @author: ly
 * @date: 2020/8/20 17:04
 */
public interface Manager {

    /**
     * 纸牌初始化
     *
     * @return Manager
     */
    Manager cardInit();

    /**
     * 洗牌
     *
     * @return Manager
     */
    Manager shuffle();

    /**
     * 给多个玩家一次发牌
     *
     * @param players 玩家数组
     * @return Manager
     */
    Manager giveCards(AbstractPlayer... players);

    /**
     * 结算
     *
     * @param player 当前玩家
     * @return Manager
     */
    Manager settle(AbstractPlayer player);

    /**
     * 注册全局参数
     *
     * @param context 全局参数
     * @return Manager
     */
    Manager register(PlatformContext context);

    /**
     * 根据id获取玩家
     *
     * @param id 玩家id
     * @return 具体玩家
     */
    PlayerModel getPlayer(String id);

    /**
     * 获取随机玩家
     *
     * @return 具体玩家
     */
    PlayerModel getRandomPlayer();

    /**
     * 获取下一个玩家
     *
     * @return 具体玩家
     */
    PlayerModel getNextPlayer();

    /**
     * 获取下一个玩家,只获取一轮
     * @param firstId 从哪个玩家开始算
     * @return 具体玩家
     */
    PlayerModel getNextPlayerRound(String firstId);
}

/**
 * @description: 平台管理器
 * 管理洗牌,发牌方法,结算方法,管理所有玩家
 * @author: ly
 * @date: 2020/8/20 18:04
 */
@Data
public class PlatformManager implements Manager {
    /**
     * 纸牌容器
     */
    private List<String> cardContainer = new ArrayList<>();
    /**
     * 底牌
     */
    private List<String> bottom = new ArrayList<>();
    /**
     * 玩家数组
     */
    private AbstractPlayer[] playerArr = new AbstractPlayer[]{};
    /**
     * 玩家Map key:id
     */
    private Map<String, Player> playerMap = new HashMap<>();

    /**
     * 初始化纸牌容器
     *
     * @return Manager
     */
    @Override
    public Manager cardInit() {
        //初始化54张牌
        cardContainer.addAll(CardConst.CARD_POINTS);
        cardContainer.addAll(CardConst.CARD_POINTS);
        cardContainer.addAll(CardConst.CARD_POINTS);
        cardContainer.addAll(CardConst.CARD_POINTS);
        cardContainer.addAll(CardConst.CARD_SPE);
        System.out.println("纸牌初始化成功!");
        return this;
    }

    /**
     * 洗牌
     *
     * @return Manager
     */
    @Override
    public Manager shuffle() {
        Collections.shuffle(cardContainer);
        System.out.println("洗牌完成!");
        return this;
    }

    /**
     * 发牌,给多个玩家发牌,轮询发
     *
     * @param players 玩家数组
     * @return Manager
     */
    @Override
    public Manager giveCards(AbstractPlayer... players) {
        System.out.println("发牌中......");
        if (cardContainer.size() > 0 && players.length > 0) {
            //定义轮询初始化值
            int lx = players.length;
            for (int i = 0; i < cardContainer.size() - CardConst.BOTTOM_NUM; i++) {
                AbstractPlayer player = players[lx++ % players.length];
                player.accept(cardContainer.get(i));
            }
            //将剩余的牌放入底牌
            bottom.addAll(cardContainer.subList(cardContainer.size() - CardConst.BOTTOM_NUM, cardContainer.size()));
            //存放玩家
            playerArr = players;
            playerMap.putAll(Arrays.stream(players).collect(Collectors.toMap(AbstractPlayer::getId, a -> a)));
            System.out.println("发牌完毕!");
        } else {
            System.out.println("!!!纸牌或玩家创建失败!!!");
        }
        return this;
    }

    /**
     * 结算
     *
     * @param player 当前玩家
     * @return Manager
     */
    @Override
    public Manager settle(AbstractPlayer player) {
        if (player.getCardNum() == 0) {
            System.out.println("玩家:" + player.getId() + " 获胜。");
        }
        return this;
    }

    /**
     * 注册全局容器
     *
     * @param context 全局参数
     * @return Manager
     */
    @Override
    public Manager register(PlatformContext context) {
        BaseContext.setContext(context);
        return this;
    }

    /**
     * 构建实体
     *
     * @return PlatformManager
     */
    public static PlatformManager build() {
        return new PlatformManager();
    }

    /**
     * 根据id获取玩家
     *
     * @param id 玩家id
     * @return 具体玩家
     */
    @Override
    public PlayerModel getPlayer(String id) {
        return (PlayerModel) this.playerMap.get(id);
    }

    /**
     * 获取随机玩家
     *
     * @return 具体玩家
     */
    @Override
    public PlayerModel getRandomPlayer() {
        return (PlayerModel) this.playerArr[(new Random().nextInt(playerArr.length))];
    }

    /**
     * 获取下一个玩家
     *
     * @return 下一个具体玩家
     */
    @Override
    public PlayerModel getNextPlayer() {
        //当前玩家id
        String currId = BaseContext.getContext().getCurrId();
        if (StringUtils.isBlank(currId)) {
            System.out.println("!!没有设置当前玩家id,随机获取一个!!");
            return getRandomPlayer();
        }
        //下一个玩家id,默认第一个
        String next = playerArr[0].getId();
        for (int i = 0; i < this.playerArr.length - 1; i++) {
            if (this.playerArr[i].getId().equalsIgnoreCase(currId)) {
                if (i == this.playerArr.length - 1) {
                    next = playerArr[0].getId();
                } else {
                    next = playerArr[i + 1].getId();
                }
                break;
            }
        }
        return getPlayer(next);
    }

    /**
     * 获取下一个玩家,只获取一轮
     *
     * @param firstId 从哪个玩家开始算
     * @return 下一个具体玩家
     */
    @Override
    public PlayerModel getNextPlayerRound(String firstId) {
        for (; ; ) {
            PlayerModel nextPlayer = getNextPlayer();
            if (nextPlayer.getId().equals(firstId)) {
                return null;
            }
            return nextPlayer;
        }
    }
}
  • PlayerGeneral 玩家模型生成器
/**
 * @description: 玩家生成器
 * @author: ly
 * @date: 2020/8/20 18:05
 */
public class PlayerGeneral {

    public static AbstractPlayer general(String id) {
        return new PlayerModel(id);
    }
}
  • PlayerModel 玩家模型 定义接受牌,重组展示手牌,出牌等方法
/**
 * @description: 玩家接口
 * 定义接收牌、清牌、出牌方法
 * @author: ly
 * @date: 2020/8/20 17:52
 */
public interface Player {
    /**
     * 接收一张牌
     *
     * @param card 牌
     */
    void accept(String card);

    /**
     * 接收集合
     *
     * @param cardList 牌集合
     */
    void accept(List<String> cardList);

    /**
     * 清理牌
     *
     * @param cardList 牌集合
     */
    List<String> order(List<String> cardList);

    /**
     * 打印自身信息
     */
    void print();

    /**
     * 出牌
     * @param cards 打出的牌
     * @return true:出牌成功
     */
    boolean leave(String cards);
}

/**
 * @description: 玩家抽象类
 * @author: ly
 * @date: 2020/8/20 17:02
 */
public abstract class AbstractPlayer implements Player{
    /**
     * 玩家标识
     */
    protected String id;

    /**
     * 玩家持有的牌
     */
    protected List<String> cardList;

    /**
     * 当前牌的数量
     */
    protected int cardNum;

    public String getId() {
        return id;
    }

    protected AbstractPlayer(String id) {
        this.id = id;
    }

    public List<String> getCardList() {
        return cardList;
    }

    public int getCardNum() {
        return cardList.size();
    }
}
/**
 * @description: 玩家
 * @author: ly
 * @date: 2020/8/20 17:39
 */
@Setter
@Getter
public class PlayerModel extends AbstractPlayer {
    /**
     * 是否地主
     */
    private boolean diZhu;

    public PlayerModel(String id) {
        super(id);
        cardList = new ArrayList<>();
    }

    @Override
    public void accept(String card) {
        cardList.add(card);
    }

    @Override
    public void accept(List<String> cardList) {
        cardList.addAll(cardList);
    }

    @Override
    public List<String> order(List<String> cardList) {
        return CardUtil.cartRecomShow(cardList);
    }

    @Override
    public void print() {
        System.out.println("玩家:" + getId() + "-" + getMark());
        System.out.println("纸牌:" + order(cardList) + ",牌数:" + getCardNum());
    }

    @Override
    public boolean leave(String cards) {
        try {
            //是否pass
            if (StringUtils.isNotBlank(cards) && cards.equalsIgnoreCase(Command.PASS)) {
                return true;
            }
            //出牌规则校验
            CardTypeEnum cardTypeEnum = RuleFactory.create(CardTypeRule.class).checkCardType(cards, this);
            if (cardTypeEnum == null) {
                System.out.println("错误:!出牌有误,请检查打出的牌!");
                return false;
            }
            PlatformContext context = BaseContext.getContext();
            //是否第一个出牌
            if (StringUtils.isBlank(context.getPreId()) || this.getId().equalsIgnoreCase(context.getPreId())) {
                //出牌成功,设置context
                updateContext(cards, cardTypeEnum, context);
                return true;
            }
            //比较出牌大小
            if (!RuleFactory.create(CardCompareRule.class).compare(context, cardTypeEnum, cards)) {
                System.out.println("错误:!出牌有误,请检查打出的牌!");
                return false;
            }
            //出牌成功,设置context
            updateContext(cards, cardTypeEnum, context);
            return true;
        } catch (Exception e) {
            System.out.println("未识别命令,请重新输入");
            return false;
        }
    }

    /**
     * 更新容器
     *
     * @param cards        打出的牌
     * @param cardTypeEnum 牌型
     * @param context      容器
     */
    private void updateContext(String cards, CardTypeEnum cardTypeEnum, PlatformContext context) {
        context.setPreId(this.getId());
        context.setPreMark(this.getMark());
        context.setPreType(cardTypeEnum);
        context.setPreGiveCards(cards);
        //除去自己手中已经打出的牌,可能将手中已有相同的牌全部清除了,比如打出3 手上有两个3,只需要去掉一张3即可
        List<String> leaveList = CardUtil.leaveConvert(cards);
        List<String> cardList = this.getCardList();
        boolean remove = false;
        for (int i = cardList.size()-1; i>=0; i--) {
            for (int j = leaveList.size()-1; j >=0; j--) {
                if (leaveList.get(j).equalsIgnoreCase(cardList.get(i))) {
                    leaveList.remove(j);
                    remove = true;
                    break;
                }
            }
            if (remove) {
                cardList.remove(i);
                remove = false;
            }

        }
    }


    @Override
    public String toString() {
        return "玩家:" + getId() + ",纸牌:" + cardList + ",牌数:" + cardNum + ",是否地主:" + diZhu;
    }

    /**
     * 获取标识
     *
     * @return 地主或农民
     */
    public String getMark() {
        return isDiZhu() ? "地主" : "农民";
    }
}
  • RuleFactory 规则工厂 主要创建各种规则类
/**
 * @description: 规则工厂
 * @author: ly
 * @date: 2020/8/21 18:25
 */
public class RuleFactory {

    public static <T> T create(Class<T> t) {
        try {
            return t.newInstance();
        } catch (Exception e) {
            System.out.println("!!!出错了!!!");
            System.exit(1);
            return null;
        }
    }
}
  • CardNumRule 纸牌数量规则,平台发完牌后开始检验
/**
 * @description: 牌数规则
 * 牌数规则 发牌完成校验,平台管理
 * 1.一共54张牌
 * 2.每人17张
 * 3.底牌三张
 * @author: ly
 * @date: 2020/8/21 16:36
 */
public class CardNumRule implements Rule {

    /**
     * 检查牌数
     *
     * @param manager 管理平台
     */
    public void checkCardNum(PlatformManager manager) {
        List<String> cardContainer = manager.getCardContainer();
        if (cardContainer == null || cardContainer.size() != CardConst.TOTAL_NUM) {
            System.out.println("!!!出错了,牌数不为54张!!!");
            System.exit(1);
        }
        AbstractPlayer[] playerArr = manager.getPlayerArr();
        boolean everyNum = Arrays.stream(playerArr).anyMatch(player -> player.getCardNum() != CardConst.EVERY_NUM);
        if (everyNum) {
            System.out.println("!!!出错了,每人初始牌数应该17张!!!");
            System.exit(1);
        }
        List<String> bottom = manager.getBottom();
        if (bottom == null || bottom.size() != CardConst.BOTTOM_NUM) {
            System.out.println("!!!出错了,底牌不为3张!!!");
            System.exit(1);
        }
    }

}
  • CardTypeRule 纸牌类型规则 校验出的牌是否符合设定的规则
/**
 * @description: 牌型规则
 * 牌型规则 出牌校验
 * 1.可以出单张 1张
 * 2.可以出一对 2张
 * 3.可以出三连张 3张
 * 4.可以出三带一 4张
 * 5.可以出三带二 5张
 * 6.可以出顺子 5-12张
 * 7.可以出连对 6-8-10-12-14-16-18
 * 8.可以出飞机
 * 9.可以出炸弹 4张
 * 10.可以出王炸 2张
 * 牌型相同 单张,一对,三连张,三带一,三带二,顺子,飞机,炸弹,王炸
 * 牌型不同,必须是炸弹
 * @author: ly
 * @date: 2020/8/21 16:50
 */
public class CardTypeRule implements Rule {

    /**
     * 检查牌的类型
     *
     * @param cards 打出的牌
     * @return 检查牌的类型
     */
    public CardTypeEnum checkCardType(String cards, PlayerModel playerModel) {
        if (StringUtils.isBlank(cards)) {
            return null;
        }
        //转成可比较点数
        List<String> cardList = CardUtil.leaveConvert(cards);
        //最多不能超过自己的牌数
        if (cardList.size() > playerModel.getCardList().size()) {
            return null;
        }
        //出的牌是否合规
        if (!cardList.stream().allMatch(this::cardOfPool)) {
            return null;
        }
        //是否是自己的牌
        if (!playerModel.getCardList().containsAll(cardList)) {
            return null;
        }
        //单张
        if (cardList.size() == 1) {
            return CardTypeEnum.SINGLE;
        }
        //两张
        if (cardList.size() == 2) {
            //一对
            if (this.sameCardNum(cardList, 2)) {
                return CardTypeEnum.PAIR;
            }
            //王炸
            if (this.kingBomb(cardList)) {
                return CardTypeEnum.KING_BOMB;
            }
            return null;
        }
        //三张
        if (cardList.size() == 3) {
            if (this.sameCardNum(cardList, 3)) {
                return CardTypeEnum.THREE_PAIR;
            }
            return null;
        }
        //四张
        if (cardList.size() == 4) {
            //三带一
            if (this.sameCardNum(cardList, 3)) {
                return CardTypeEnum.THREE_BELT_ONE;
            }
            //炸弹
            if (this.sameCardNum(cardList, 4)) {
                return CardTypeEnum.BOMB;
            }
            return null;
        }
        //5张
        if (cardList.size() == 5) {
            //三带二
            if (this.sameCardNum(cardList, 3) && this.sameCardNum(cardList, 2)) {
                return CardTypeEnum.THREE_BELT_TWO;
            }
            //顺子
            if (this.shunZi(cardList)) {
                return CardTypeEnum.SHUN_ZI;
            }
            return null;
        }
        // 大于5张
        if (cardList.size() > 5) {
            //是否顺子
            if (this.shunZi(cardList)) {
                return CardTypeEnum.SHUN_ZI;
            }
            //是否连对
            if (this.evenPair(cardList)) {
                return CardTypeEnum.EVEN_PAIR;
            }
            //是否飞机
            if (this.plane(cardList)) {
                return CardTypeEnum.PLANE;
            }
        }
        return null;
    }

    /**
     * 校验出的牌是否在点数池中
     *
     * @param card 单张牌
     * @return true:满足
     */
    private boolean cardOfPool(String card) {
        if (StringUtils.isNotBlank(card)) {
            return CardConst.POINTS_POOL.contains(card);
        }
        return false;
    }

    /**
     * 是否有n张相同的牌
     *
     * @param cardList 打出的牌集合
     * @param num      相同牌数
     * @return true:是
     */
    private boolean sameCardNum(List<String> cardList, int num) {
        Map<String, List<String>> cardMap = cardList.stream().collect(Collectors.groupingBy(String::toString));
        return cardMap.values().stream().anyMatch(list -> list.size() == num);
    }

    /**
     * 是否王炸
     *
     * @param cardList 打出的牌
     * @return true:王炸
     */
    private boolean kingBomb(List<String> cardList) {
        if (cardList.size() == 2) {
            return 16 + 17 == Integer.parseInt(cardList.get(0)) + Integer.parseInt(cardList.get(1));
        }
        return false;
    }

    /**
     * 是否顺子
     * 差=1,最大点数必须小于15
     *
     * @param cardList 转换后的牌
     * @return true:顺子
     */
    private boolean shunZi(List<String> cardList) {
        List<String> sortCard = CardUtil.sort(cardList);
        //最后一张牌必须小于15
        if (Integer.parseInt(sortCard.get(sortCard.size() - 1)) >= 15) {
            return false;
        }
        boolean shunZi = true;
        for (int i = 0; i < sortCard.size(); i++) {
            if (i < sortCard.size() - 1) {
                if ((Integer.parseInt(sortCard.get(i + 1)) - Integer.parseInt(sortCard.get(i))) != 1) {
                    shunZi = false;
                    break;
                }
            }
        }
        return shunZi;
    }

    /**
     * 连对
     * 1.最少6张
     * 2.最大点数必须小于15
     * 3.去重后是顺子
     * 4.去重后的每张牌必须在原牌中存在两张
     *
     * @param cardList 转换后的牌
     * @return true:连对
     */
    private boolean evenPair(List<String> cardList) {
        if (cardList.size() < 6 || Integer.parseInt(cardList.get(cardList.size() - 1)) >= 15) {
            return false;
        }
        //去重后的牌
        Set<String> cardSet = new HashSet<>(cardList);
        if (!shunZi(new ArrayList<>(cardSet))) {
            return false;
        }
        Map<String, List<String>> cardMap = cardList.stream().collect(Collectors.groupingBy(String::toString));
        for (String next : cardSet) {
            if (!cardMap.containsKey(next)) {
                return false;
            }
            if (cardMap.get(next).size() != 2) {
                return false;
            }
        }
        return true;
    }

    /**
     * 是否飞机
     * 1.三连去重必须是顺子
     * 2. 去掉三连后必须n个单张,或者n个一对
     *
     * @param cardList 转换后的牌
     * @return true:飞机
     */
    private boolean plane(List<String> cardList) {
        Map<String, List<String>> cardMap = cardList.stream().collect(Collectors.groupingBy(String::toString));
        //三连张容器
        List<String> threeList = new ArrayList<>();
        List<String> allThreeList = new ArrayList<>();
        cardMap.forEach((card, list) -> {
            if (list.size() == 3) {
                threeList.add(card);
                allThreeList.addAll(list);
            }
        });
        if (threeList.size() <2) {
            return false;
        }
        //三连是否是顺子
        if (!this.shunZi(threeList)) {
            return false;
        }
        //副本
        List<String> backList = new ArrayList<>(cardList);
        backList.removeAll(allThreeList);

        //剩余是否单张
        if (backList.size() == threeList.size()) {
            return true;
        }
        //剩余是否对子
        if (backList.size() == 2 * threeList.size()) {
            Map<String, List<String>> backMap = backList.stream().collect(Collectors.groupingBy(String::toString));
            return backMap.values().stream().allMatch(back -> back.size() == 2);
        }
        return false;
    }
}
  • CardCompareRule 大小规则 比较玩家出的牌大小
/**
 * @description: 大小规则 出票校验,玩家模型
 * * 单张:比大小
 * * 一对:比单张大小
 * * 三带一:比三连的单张大小
 * * 三带二:比三连的单张大小
 * * 顺子:牌数相等,并且比较起始点大小
 * * 飞机:牌数相等,比较飞机起始点
 * * 炸弹:比单张大小
 * * 王炸>4张炸弹>其他
 * @author: ly
 * @date: 2020/8/21 16:51
 */
public class CardCompareRule implements Rule {

    /**
     * 出票大小比较
     *
     * @param context  容器
     * @param typeEnum 打出的牌型
     * @param cards    打出的牌
     * @return true:当前牌大
     */
    public boolean compare(PlatformContext context, CardTypeEnum typeEnum, String cards) {
        String preGiveCards = context.getPreGiveCards();
        //转成可比较点数
        List<String> cardList = CardUtil.leaveConvert(cards);
        List<String> preList = CardUtil.leaveConvert(preGiveCards);
        if (typeEnum == KING_BOMB) {
            return true;
        }
        if (context.getPreType() == SINGLE
                || context.getPreType() == PAIR
                || context.getPreType() == THREE_PAIR
                || context.getPreType() == THREE_BELT_ONE
                || context.getPreType() == THREE_BELT_TWO
                || context.getPreType() == PLANE) {
            if (typeEnum == CardTypeEnum.BOMB) {
                return true;
            }
            return compareOne(context.getPreType(), typeEnum, getSameCard(preList), getSameCard(cardList)) && preList.size() == cardList.size();
        }
        if (context.getPreType() == SHUN_ZI || context.getPreType() == EVEN_PAIR) {
            if (typeEnum == CardTypeEnum.BOMB) {
                return true;
            }
            return compareOne(context.getPreType(), typeEnum, preList.get(0), cardList.get(0)) && preList.size() == cardList.size();
        }
        if (context.getPreType() == BOMB) {
            return compareOne(context.getPreType(), typeEnum, preList.get(0), cardList.get(0));
        }
        return false;
    }

    /**
     * 获取相同牌数多的单张牌,比如一对获取单张,三带一获取三个中的单张,三带二获取三个中的单张
     *
     * @param cardList 牌集合
     * @return 相同牌数多的单张牌
     */
    private String getSameCard(List<String> cardList) {
        Map<String, List<String>> cardMap = cardList.stream().collect(Collectors.groupingBy(String::toString));
        int size = 1;
        String currCard = null;
        for (Map.Entry<String, List<String>> entry : cardMap.entrySet()) {
            String card = entry.getKey();
            List<String> list = entry.getValue();
            if (list.size() >= size) {
                size = list.size();
                currCard = card;
            }
        }
        return cardMap.get(currCard).get(0);
    }

    /**
     * 比较单张
     *
     * @param preType  前玩家牌型
     * @param currType 当前玩家牌型
     * @param preCard  前玩家出的牌
     * @param currCard 当前玩家出的牌
     * @return true: 当前玩家大
     */
    private boolean compareOne(CardTypeEnum preType, CardTypeEnum currType, String preCard, String currCard) {
        return preType == currType && Integer.parseInt(currCard) > Integer.parseInt(preCard);
    }

}

全部代码请前往gitee地址下载


09-03 10:14