继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

JAVA入门第三季--扑克牌游戏

等时钟成长
关注TA
已关注
手记 4
粉丝 19
获赞 176

游戏规则:

  1. 三人游戏,每人摸5张牌开始游戏。

  2. 只看数字不看花色,轮流出牌,下家出牌必须大于上家,否则要不起,一旦另外两人要不起一个回合结束,回合胜利者获得下回合的优先出牌权。

  3. 直到某人先出完牌,游戏结束。


先上运行结果,以飨读者。

  • 发牌

https://img4.mukewang.com/5c0b92940001b2b601140479.jpg

  • 出牌

    https://img2.mukewang.com/5c0b92c200012d4f02410745.jpg

    https://img.mukewang.com/5c0b92e600018cf202491034.jpg

    https://img2.mukewang.com/5c0b930500013ba202580839.jpg

有点长,截图有点多。

在做的时候考虑了三国杀、斗地主这些卡牌游戏的一些理念,比如牌库、回合牌堆、弃牌堆等,主要设计了Card,Poker,Player三个类。

1. Card 是每张卡牌的具体信息,有花色(suit)、数值(number)和拥有者(player)

  • suit 有【黑,红,方,梅,“”】五种,“”代表大小王

  • number 是1,2,3,...,14,15的int数字,代表牌面值的大小

  • getValue 可以得到牌面值。就是想我们看到的【梅花K】、【大王】这样的牌面

  • player 是摸牌之后将玩家信息填入,用于记录卡牌使用记录

package pokergame;

import java.util.Arrays;

public class Card implements Comparable<Card>{
    //    private final String[] Suits = {"Spade", "Heart", "Diamond", "Club", "Joker"};
    private final String[] Suits = {"黑桃", "红桃", "方片", "梅花", ""};
    private final String[] Values = {"A","2","3","4","5","6","7","8","9","T","J","Q","K","小 王","大 王"};
    private String suit; // 花色
    private int number; // 数值
    private Player player; // 所属角色

    public Card(String suit, String number){
        int suitIndex = Arrays.asList(Suits).indexOf(suit);
        int numIndex = Arrays.asList(Values).indexOf(number);
        try {
            this.initCard(suitIndex, numIndex);
        } catch (Exception e){
            e.printStackTrace();
            System.out.println(e);
        }
    }

    private void initCard(int s, int n) throws Exception {
        if (s == -1 || n == -1 || (n != 13 && n != 14 && s == 4) ){
            throw new Exception("错误的卡牌格式!");
        } else {
            number = n;
            suit = Suits[s];
        }
    }

    // 得到花色
    public String getSuit() {
        return suit;
    }

    // 得到牌面值大小(0-12,13,14)
    public int getValueNumber() {
        return number;
    }

    // 得到牌面值
    public String getValue(){
        return Values[number];
    }

    // 得到牌名
    public String getCardName(){
        return suit + Values[number];
    }

    public Player getPlayer() {
        return player;
    }

    public void setPlayer(Player player) {
        this.player = player;
    }

    @Override
    public int compareTo(Card o) {
        return this.number - o.number;
    }
}

2. Poker像是【组牌】的概念,List<Card>,就是有card组成的一组牌,可以代表牌库、手牌等

  • getPokerSize 得到组牌的数量,可用于显示牌库剩余、手牌数目等

  • sortByValueNumber 由小大大排序

  • shufflePoker 洗牌

  • initPoker 初始化一副牌(54张)

  • getMaxValueNumber得到牌面最大值,用于判断是否有可用卡牌

package pokergame;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Poker {
//    private final String[] Suits = {"Spade", "Heart", "Diamond", "Club"};
    private final String[] Suits = {"黑桃", "红桃", "方片", "梅花"};
    private final String[] Values = {"A","2","3","4","5","6","7","8","9","T","J","Q","K"};
    private List<Card> poker; // 组牌(一组牌,多个card组成)

    public Poker(int pokerSize){
        poker = new ArrayList<>(pokerSize); // 初始化大小(手牌上限?)
    }

    // 当前扑克牌列表(牌堆,手牌)
    public List<Card> getPoker() {
        return poker;
    }
    // 打印扑克牌列表
    public void printPoker(){
        System.out.println("----------");
        for (Card c:
             poker) {
            System.out.println("|#|" + c.getCardName() + "|#|");
        }
        System.out.println("----------");
    }
    // 打印扑克牌及其使用者列表
    public void printPokerAndUser(){
        System.out.println("----------");
        List<Card> temp = poker;
        Collections.reverse(temp); // 倒序
        for (Card c:
                temp) {
            System.out.println( c.getPlayer().getName() + "出牌|#|" + c.getCardName() + "|#|");
        }
        System.out.println("----------");
    }
    // 当前扑克牌数量(牌堆,手牌)
    public Integer getPokerSize(){
        return poker.size();
    }

    // 初始化一组完整的扑克牌(牌堆)
    public void initPoker(){
        // 52张普通花色
        for(String s : Suits){
            for(String v : Values){
                poker.add(new Card(s, v));
            }
        }
        // 大小王
        poker.add(new Card("","小 王"));
        poker.add(new Card("","大 王"));
    }

    // 洗牌
    public void shufflePoker(){
        Collections.shuffle(poker);
    }

    // 按数值由小到大排序
    public void sortByValueNumber(){
        Collections.sort(poker);
    }

    // 返回牌面最大牌
    public Card getMaxValueNumber(){
        return Collections.max(poker);
    }

    // 返回牌面最小牌
    public Card getMinValueNumber(){
        return Collections.min(poker);
    }

    // 选择一张大于输入的最小牌(建议出牌) 必须先排序再使用该方法
    public Card getAdviseCard(Card card) throws NoCardUsableException{
        for (Card c:
             poker) {
            if(c.compareTo(card) > 0 ){
                return c;
            }
        }
        throw new NoCardUsableException();
    }

    public void setPoker(List<Card> c){
        poker = c;
    }
    public void addPoker(Integer index, List<Card> c){
        poker.addAll(index, c);
    }




}

3. Player玩家信息,最重要的属性owenedPoker(手牌)

  • fetchCard 摸牌

  • useCard 出牌

package pokergame;

public class Player {
    private Integer id;
    private String name;
    public Poker ownedPoker; // 拥有的手牌

    public Player(int id, String name) {
        this.id = id;
        this.name = name;
        this.ownedPoker = new Poker(0); // 新的poker对象
    }

    // 摸牌
    public void fetchCard(Poker pokerHeap) {
        try {
            // 从 牌库 移入 手牌
            Logic.moveCard(pokerHeap, 0, ownedPoker, 0);
            // 为牌添加 拥有者
            ownedPoker.getPoker().get(0).setPlayer(this);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }

    // 出牌
//    public void useCard(int position, Poker roundPoker) throws NoCardUsableException {
//        // 出牌值 大于 回合牌堆顶的值即可
//        if (roundPoker.getPokerSize() == 0 || ownedPoker.getPoker().get(position).compareTo(roundPoker.getPoker().get(0)) > 0) {
//            // 手牌 移入 回合牌堆
//            try {
//                Logic.moveCard(ownedPoker, position, roundPoker, 0);
//                System.out.println("出牌" + ownedPoker.getPoker().get(position).getCardName());
//            } catch (Exception e) {
//                e.printStackTrace();
//                System.out.println(e.getMessage());
//            }
//        } else {
//            throw new NoCardUsableException();
//        }
//    }
    public void useCard(Card srcCard, Poker roundPoker) throws NoCardUsableException {
        // 出牌值 大于 回合牌堆顶的值即可
        if (roundPoker.getPokerSize() == 0 || srcCard.compareTo(roundPoker.getPoker().get(0)) > 0) {
            // 手牌 移入 回合牌堆
            try {
                Logic.moveCard(ownedPoker, srcCard, roundPoker, 0);
                System.out.println("出牌" + srcCard.getCardName());
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(e.getMessage());
            }
        } else {
            throw new NoCardUsableException();
        }
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

4. Logic里主要实现了静态方法moveCard,我们把思想转变一下,其实整个游戏过程,摸牌、出牌、弃牌等就是把卡牌挪来挪去,从牌库挪入手牌、从手牌挪到回合牌堆、从回合牌堆挪到弃牌堆等。

package pokergame;

public class Logic {

    public Logic(){}

    /*
    系统逻辑
     */

    // 移动卡牌(静态方法)
     public static void moveCard(Poker srcPoker, int srcPosition,Poker destPoker, int destPosition) throws Exception{
        if(srcPosition > srcPoker.getPoker().size()) {
            throw new Exception("源牌堆位置超出!");
        } else if (destPosition > destPoker.getPoker().size()){
            throw new Exception("目标牌堆位置超出!");
        }else{
            // 在 源牌堆中找到,然后 加入到 目标牌堆
            destPoker.getPoker().add(destPosition ,srcPoker.getPoker().get(srcPosition));
            // 从 源牌堆 删除
            srcPoker.getPoker().remove(srcPosition);
        }
    }
    public static void moveCard(Poker srcPoker,Card srcCard,Poker destPoker, int destPosition) throws Exception{
        if(!srcPoker.getPoker().contains(srcCard)) {
            throw new Exception("不包含的卡牌!");
        } else if (destPosition > destPoker.getPoker().size()){
            throw new Exception("目标牌堆位置超出!");
        }else{
            // 在 源牌堆中找到,然后 加入到 目标牌堆
            destPoker.getPoker().add(destPosition , srcCard);
            // 从 源牌堆 删除
            srcPoker.getPoker().remove(srcCard);
        }
    }

}

5. Round类 实现了回合内的逻辑判断,玩家出牌,挪动卡牌,游戏结算等

package pokergame;

import sun.rmi.runtime.Log;

import java.util.ArrayList;
import java.util.List;

public class Round {
    private Integer roundCount; // 回合数
    private List<Player> players; // 参与出牌的角色
    private List<Poker> roundHeap; // 回合牌堆(回合内使用)
    private Poker discardHeap; // 弃牌堆(结算完毕)
    private Integer passCount; // 要不起的次数,三人游戏两次要不起则回合结束
    private Integer playerNum; // 参与出牌的角色数量
    private int winnerIndex; // 回合胜利者(即下一回合的初始出牌人)的索引

    public Round(List<Player> players, int beginnerIndex) {
        this.roundCount = 0;
        this.players = players;
        this.playerNum = players.size();
        this.passCount = 0;
        this.roundHeap = new ArrayList<>(0); // 主要是为了实现记录出牌顺序的功能
        this.discardHeap = new Poker(0);
        this.winnerIndex = beginnerIndex;
    }

    public boolean go() {
        System.out.println("-----第" + (roundCount + 1) + "回合开始-----");
        roundHeap.add(new Poker(0)); // 增加roundHeap
        passCount = 0; // 清零要不起标记
        int turnCount = 0; // 出牌圈数,区别于回合数round
        do {
            for (int i = 0; i < 3; i++) {
                if(turnCount == 0){
                    i = winnerIndex; // 每回合第一圈的起始出牌人都是上一回合的胜利者
                    turnCount ++;
                }
                if (passCount + 1 < playerNum) {
                    try {
                        players.get(i).ownedPoker.sortByValueNumber(); // 每次都会将手牌由小到大排序
                        System.out.println(players.get(i).getName() + "的 手牌: ");
                        players.get(i).ownedPoker.printPoker();
                        players.get(i).useCard(players.get(i).ownedPoker.getMaxValueNumber(), roundHeap.get(roundHeap.size() - 1));
                        // exception point
                        passCount = 0;
                        winnerIndex = i;
                        // 胜利结算
                        if(players.get(i).ownedPoker.getPokerSize() == 0) {
                            discardHeap.addPoker(0, roundHeap.get(roundCount).getPoker());
                            System.out.println("游戏结束,胜利者是:" + (i + 1) + "号玩家" + players.get(i).getName());
                            System.out.println("卡牌使用记录:");
                            discardHeap.printPokerAndUser();
                            // 剩下的如果玩家确认游戏结束了,可以将回合牌堆中的卡牌移入 牌库 重新开始游戏
                            return true;
                        }
                    } catch (NoCardUsableException e) {
                        passCount++;
                        System.out.println("要不起");
                    }
                }
            }
        } while (passCount + 1 < playerNum);
        // 回合结算
        System.out.println("当前回合牌堆:");
        roundHeap.get(roundCount).printPoker();
        System.out.println("本回合胜利者是:" + (winnerIndex + 1) + "号玩家:" + players.get(winnerIndex).getName());
        System.out.println("-----第" + (roundCount + 1) + "回合结束-----");
        discardHeap.addPoker(0, roundHeap.get(roundCount).getPoker()); // 将回合牌堆卡牌 记录到 弃牌堆(并不是移入,只是记录,卡牌对象实体还是在回合牌堆中)
        System.out.println("弃牌堆:");
        discardHeap.printPoker();
        roundCount++;
        return false;
    }


}

6. NoCardUsableException 是无牌可用异常

package pokergame;

public class NoCardUsableException extends Exception {
    public NoCardUsableException(){

    }
    public NoCardUsableException(String message){
        super(message);
        System.out.println("无可用卡牌");
    }
}

7. 最后 Client 就是运行主程序。

package pokergame;


import java.util.ArrayList;
import java.util.List;

public class Client {

    // 参数设置
    public static int FETCH_NUM = 5;

    public static void main(String[] args) {

        Poker pokerHeap = new Poker(54); // 牌库
        pokerHeap.initPoker(); // 牌堆初始化
        pokerHeap.shufflePoker(); // 洗牌

        // 玩家列表
        List<Player> players = new ArrayList<>(3);
        players.add(new Player(1, "甲"));
        players.add(new Player(2, "乙"));
        players.add(new Player(3, "丙"));

        // 依次摸5张牌
        for (int i = 0; i < FETCH_NUM; i++) {
            for (Player player :
                    players) {
                player.fetchCard(pokerHeap);
            }
        }
        System.out.println("牌堆剩余:" + pokerHeap.getPokerSize());

        // 展示手牌
        for (Player player :
                players) {
            System.out.println(player.getName() + "手牌:");
            player.ownedPoker.printPoker();
        }

        // play
        System.out.println("开始出牌!");
        Round round = new Round(players, 0);
        Boolean isGameOver = false;
        // 直到有人出完牌结束比赛
        do{
            isGameOver = round.go();
        }while(!isGameOver);

    }

}

总结: 没有用到抽象类、接口、继承等知识。但对于第三季学到的集合框架、排序、异常都有涉及。没有加入输入互动的部分,程序中出牌都是直接选择自己最大的牌打出,有兴趣的读者可以自己改造。比如我提供一些思路:

  1. 开始先选出牌顺序,决定你在玩家列表中的位置,然后每次轮到你出牌,就改成由键盘输入你想打的牌在你手牌中的索引位置。

  2. 增加【建议出牌】功能,选出当前手牌中大于上家出牌的最小牌。

  3. 由三国杀引发的思路:每个player有自己的技能?在出牌时会触发技能,比如玩家1的红桃牌视为K,玩家2的方片牌可以通吃【JQK】等等

    结束,吃饭。




打开App,阅读手记
2人推荐
发表评论
随时随地看视频慕课网APP