【Java学习】单例设计模式|ArrayList的使用|扑克牌游戏编写超详细过程!!!

心得体会

这一个游戏的编写复杂度有点超乎我的想象,跟着老师写了一个早上+下午,虽说最后还有点懵,但也懂了很多之前没懂的东西,比如什么时候需要新建一个类,三目运算符的使用等。又想起古文里说的“求其上者,得其中;求其中者,得其下”,就是说得到的东西总会比自己理想的差一层次,编写超出自己实力程序肯定会使自己有所收获,加油吧。


1. 目的

  • 一、了解单例设计模式
  • 二、进一步熟悉数组的使用
  • 三、完成扑克游戏的编写

2. 内容简概

  • 一、了解单例设计模式
  • 二、了解数组的引用

(以下为扑克牌游戏编写的具体步骤)

  • 三、用封装实现文本输出
  • 四、定义Poker类
  • 五、生成一副牌
  • 六、初始化玩家信息
  • 七、实现管理底注功能
  • 八、实现发牌功能
  • 九、实现弃牌和下注功能
  • 十、奖励获胜玩家
  • 十一、游戏主程序
  • 十二、游戏主程序

3. 具体内容

  • 一、了解单例设计模式

1. 什么是单例设计模式?
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
具体实现需要:
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。
(3)定义一个静态方法返回这个唯一对象。

2. 为什么要用它?什么时候用到它呢?
(1)一般情况下,当我们想使用这个类时,会使用new关键字,而new这个关键字是比较耗资源的,所以,如果在某一次实例化new好后不用再重复该动作,就能节省很多资源了。
(2)需要频繁的进行创建和销毁的对象。
(3)创建对象时耗时过多或耗费资源过多,但又经常用到的对象。

3.单例模式分为饿汉单例模式和懒汉单例模式
(1)饿汉单例模式:静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例模式。
(2)懒汉单例模式:要在第一次被引用时,才会将自己实例化,所以就被称为懒汉式单例模式。

4.如何编写?
(1)饿汉单例模式
   定义:使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故又被称为“饿汉模式”),常见的实现办法就是直接new实例化。
   优点:实现起来简单,没有多线程同步问题。
   缺点:在某些特定条件下会耗费内存。

class Poker{
    //定义一个静态的成员变量,并用static、final修饰
    public static final Poker shared = new Poker();

    //构造方法私有化
    private Poker(){}

    // 提供给外部一个访问的方法——静态方法返回该实例
    public static Poker getInstance() {
        return shared;
}

(2)懒汉单例设计模式
   定义:调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急,故又称为“懒汉模式”),常见的实现方法就是在get方法中进行new实例化。
   优点:在某些特定条件下会节约了内存。
   缺点:在多线程情形下,synchronized方法通常效率低

class Player{
    public int count;

    //定义一个静态的成员变量,并用static修饰
    private static Player shared = null;

    //构造方法私有化
    private Player(){}

    //提供给外部一个访问的方法——静态方法返回该实例
    public static Player getInstance(){
        Object b = new Object();
        synchronized (b){
            if (shared == null){
                //在多线程情形下,保证了“懒汉模式”的线程安全
                shared = new Player();
            }
        }
        return shared;
    }
}
  • 二、了解数组(ArrayList)的引用

1. 什么是ArrayList?
ArrayList<E>中,E表示泛型,ArrayList是一个泛型类。
ArrayList相当于C++ 的指针,用于存储对象。与数组不同,数组一旦创建,长度固定,但是ArrayList的长度是动态的,不受限制,可以存储任意多的对象,但是只能存储对象,不能存储基本数据类型。

2. 如何使用ArrayList类?

class Test2{
    public static void main(String[] args){
        ArrayList<Person> people = new ArrayList<>();

        //获取数组元素个数
        people.size();

        //添加数据
        Person xw = new Person();
        people.add(xw);

        Person zs = new Person();
        people.add(zs);

        //访问数据
        Person xw2 = people.get(0);
        xw2.name = "小王";

        System.out.println(xw.name);
      //System.out.println(xw2.name);
    }
}
class Person{
    public String name;
}

3.运行结果分析
不论是xw.name还是xw2.name,输出结果都为“小王”,这是因为改变数组里面对象的属性变量,原始对象的值也跟着改变,因为大家都是指向同一个内存空间。


   

             游戏编写

游戏规则:

(1)由玩家选择游戏人数(由于平台和水平限制,需要一人扮演多人玩游戏)。
(2)游戏的底注为10,每个人初始筹码为1000。
(3)功能有看牌、弃牌和下注,一人下注后则显示为跟注。
(4)下注金额不能大于筹码,若跟注金额大于筹码,系统默认为弃牌
(5)弃牌后只剩一人时该玩家为赢家,游戏剩两人时最多局数为4局,游戏为三人时不限制局数。

  我们再来先来回顾一下这个扑克牌游戏的程序结构

程序结构分析

  • 三、用封装实现文本输出

1.分析一下我们可能会用到的文本类型:
可以看出,我们可能需要单行或多行文字,可能需要换行和分隔符,故我们想到为此新建一个Utils类,专门管理一些常用功能。

界面效果图

2. 接收用户输入
在显示游戏界面后,用户肯定要进行多次操作,接收用户输入也是一个常用功能,故我们也将其写入Utils类.

public class Utils {
    //如果不需要保存数据 没有成员变量
    //提供静态方法 访问方便
    public static void showText(boolean hasStar,boolean lineBreak,String...contents){
        //判断是否需要显示分割线
        System.out.print(hasStar?"*******************************\n":"" );
        //判断输出的内容是多行还是一行
        if (contents.length == 1){
            System.out.print(contents[0]);

            //有分割线换行
            System.out.print(hasStar?"\n":"");
        }
        else{
            //输出带编号的多行数据
            for (int i = 0; i < contents.length; i++){
                System.out.println((i+1)+"."+contents[i]);
            }
        }
        System.out.print(hasStar?"*******************************\n":"" );

        //判断是否需要换行
        System.out.print(lineBreak?"\n":"");
    }
    //接收用户输入
    public static int getInput(){
        Scanner scanner = new Scanner(System.in);
        return scanner.nextInt();
    }
}
  • 四、定义Poker类

1. 用Constant类管理常量数据
欢迎界面后,就想到应该要有一副牌,故我们新建一个Poker类,用来管理牌的点数和花色。由于点数和花色是固定的,不变的常量,故我们再新建一个Constant类用来管理常量数据。我们先在Constant类中写好扑克的点数和花色。

public class Constant {
    //用数组保存牌的点数和花色
    public static final String[] DOTS = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"};

    //保存固定的几个花色 黑红梅方
    public static final PokerType[] TYPES = {PokerType.SPADES,PokerType.HEARTS,PokerType.CLUBS,PokerType.DIAMONDS};

    //保存默认的玩家姓名
    public static final String[] DEFAULT_NAMES = {"刘德华","周润发","张家辉","周星驰"};

    //设置默认的金币
    public static final int MONEY = 1000;

    //每局消耗的金币数
    public static final int BASE = 10;
}

2. 用PokerType类管理花色
由于花色的ASCII码值不连贯,若用其来比较大小不方便,故我们新建一个PokerType类,专门用来管理和比较它的花色,使用setter/getter方法虽然有点麻烦,但能使其封装性更好。关于如何快速创建setter/getter方法,在我之前的文章有讲到 ☛ setter/getter方法

public class PokerType {
    public static final PokerType SPADES = new PokerType("♠",4);
    public static final PokerType HEARTS = new PokerType("♥",3);
    public static final PokerType CLUBS = new PokerType("♣",2);
    public static final PokerType DIAMONDS = new PokerType("♦",1);
    //用简单的数字表示,使其比较更简单
    private String pic;
    private int id;

    //不让默认的构造方法被覆盖
    public PokerType(){}

    //提供一个构造方法
    //默认的构造方法就被屏蔽了
    public PokerType(String pic,int id){
        this.pic = pic;
        this.id = id;
    }

    public String getPic() {
        return pic;
    }

    public void setPic(String pic) {
        this.pic = pic;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

3. 用Poker类管理每一张牌的点数和花色
编写好点数和花色后,我们需要将其在这个类拼接起来,使其输出如“K♥”“A♣”的效果

public class Poker {
    private String dot;
    private PokerType type;

    public Poker(){}

    public Poker(String dot,PokerType type){
        this.dot = dot;
        this.type = type;
    }

    //setter/getter方法
    public void setDot(String dot){
        this.dot = dot;
    }
    public String getDot(){
        return dot;
    }
    public PokerType getType() {
        return type;
    }
    public void setType(PokerType type) {
        this.type = type;
    }
    public Player compareTo(Player player){
        this.type = type;
    }
    public boolean bigerThan(Poker poker){
        int mIndex = Arrays.binarySearch(Contant.DOTS,this.dot);
        int oIndex = Arrays.binarySearch(Contant.DOTS,poker.dot);

        if (mIndex != oIndex){
            //点数不同 直接比较
            return mIndex > oIndex;
        }
        else {
            //点数相同 比较花色
            return this.type.getId() > poker.type.getId();
        }
    }
}
  • 五、生成一副牌并洗牌

1.用PokerManager类管理牌的操作
我们讲过PokerManager类用来管理生成一副牌、洗牌和发牌,上面我们已经用Poker类生成了52张单牌(无鬼牌),先在我们要生成一副完整的牌,则需要新建一个PokerManager类。

public class PokerManager {
    //保存一副牌
    private ArrayList<Poker> pokers = new ArrayList<>();

    //创建静态的变量
    public static final PokerManager manager = new PokerManager();

    //私有化构造方法
    private PokerManager(){}

    //定义一个方法 生成一副牌
    public void deal(){
        //遍历整个点数的数组
        for (int i = 0; i < Contant.DOTS.length; i++){
            //获取对应的点数
            String dot = Contant.DOTS[i];

            //生成四种花色
            for (int j = 0; j < Contant.TYPES.length; j++){
                //创建一张牌
                Poker poker = new Poker(dot,Contant.TYPES[j]);
                //将这张牌保存起来
                pokers.add(poker);
            }
        }
        //洗牌
        Collections.shuffle(pokers);
    }
    //显示一副牌
    public void show(){
        for (Poker poker:pokers){
            //K♣
            System.out.print(poker.getDot()+poker.getType().getPic()+" ");
        }
        System.out.println();
    }
  • 六、初始化玩家信息

1. 用Player类管理玩家属性
在生成一副牌后,我们想到这个游戏还需要玩家,故我们先来写好玩家的各个属性,使其输出效果如“1号玩家 :张家辉 筹码1000 10♠”

public class Player {
    public String name;
    public int id;
    public int money;
    public Poker poker;
    public boolean hasDiscard;//是否弃牌

    public Player(){

    }
    public Player(String name,int id,int money){
        this.name = name;
        this.id = id;
        this.money = money;
    }

    @Override
    //当打印一个对象的时候 就会默认去调用对象的toString方法
    //如果当前类里面没有实现这个方法 就到父类里面去查找
    //object里面默认实现就是打印对象的首地址
    public String toString() {
        //1号玩家 张家辉 金币:1000
        String pkString = "";
        if (poker != null){
            pkString = " "+poker.getDot()+poker.getType().getPic();
        }
        return id +"号玩家 " + name + " 筹码:"+ money + " " + getPokerString();
    }

    public String getPokerString(){
        String pkString = "";
        if (poker != null){
            pkString = poker.getDot()+poker.getType().getPic();
        }
        return pkString;
    }
  • 七、实现管理底注功能

1. 用GameCenter类管理底注、记录每局金额
玩家和牌都配备好后,先不着急发牌,我们现来设置底注,在这里,我们规定底注为10,新建一个GameCenter类。

public class GameCeter {
    //记录这一局的筹码
    private int totalMoney;

    //开始游戏
    public void start(){
        System.out.println("游戏开始,请打底:");
        PlayerManager manager = PlayerManager.manager;
        //扣除底注
        manager.betAll(Contant.BASE);

        manager.show();
  • 八、实现发牌功能

1. 发牌
现在我们可以发牌了,发牌和洗牌是PokerManager类管理的,故我们在PokerManager类中编写方法。

public void dealCards(ArrayList<Player> players){
        for (int i = 0; i < players.size(); i++){
            Player player = players.get(i);

            //将数组里面对应的扑克牌给对应的玩家
            player.poker = pokers.get(i);
        }
    }
}
  • 九、实现弃牌和下注功能

1. 弃牌和下注
这两个功能联系比较紧密,故在此放到一起讲。若上一个玩家弃牌,则轮到下一个玩家下注,涉及到选择和循环,我们用for循环和switch语句来实现该功能。
(1)Player类中
需要判断玩家的下注金额是否足够

/**打底 &下注
     * @param count 下注金额
     * @return -1:失败 >0 成功
     */
    public int bet(int count){
        //判断自己的金币 是否大于下注金额
        if (money >= count){
            money -= count;
            return count;
        }
        else {
            return -1;
        }
    }
    public void add(int count){
        money += count;
    }
}

(2)PlayerManager类
需要记录每局总金额、判断是否下注成功(可能余额不足)、使每个玩家轮流下注以及统计剩余玩家人数

/**下注总金额
     * @param count 每局消耗的金币
     * @return -1:失败 >0 成功
     */
    public int betAll(int count){
        for (Player player:players){
            int result = player.bet(count);
            if (result == -1){
                return -1;
            }
        }
        //返回总共的下注金额
        return count * players.size();
    }

    /**
     * 获取当前下注的玩家
     * @return 玩家对象
     */
    public Player currentPlayer(){
        return players.get(currentPlayerIndex);//获取玩家编号
    }

    /**
     * 当前剩余玩家数
     * @return
     */
    public int leftPlayerCount(){
        int total = 0;
        for (int i = 0; i < players.size(); i++){
            Player player = players.get(i);
            //没弃牌且有钱 才能玩
            if (player.hasDiscard == false && player.money > 0){
                total++;
            }
        }
        return  total;
    }

    /**
     * 查找下一个下注的人
     */
    public void changeNext(){
        int i = currentPlayerIndex;
        if (i == players.size()-1){
            i = 0;
        }
        else{
            i++;
        }
        //查找下一个可以参与的玩家
        for (; i < players.size(); i++){
            Player player = players.get(i);
            if (player.hasDiscard == false && player.money > 0){
               currentPlayerIndex = i;
               return;
            }
        }
    }
  • 十、奖励获胜玩家

1.用PlayerManager类判断获胜玩家
奖励获胜玩家属于玩家管理系统的工作,故我们在PlayerManager类中写一个奖励方法

public void awardWinner(int total){
        Player winner;
        int available = leftPlayerCount();
        if (available == 1){
            //只有一个玩家了 这就是赢家
            changeNext();
            winner = currentPlayer();
        }
        else {
            //需要比较这两个玩家的牌
            Player w1 = null;
            Player w2 = null;
            for (int i = 0; i < players.size(); i++){
                Player player = players.get(i);
                if (player.hasDiscard == false){
                    if (w1 == null){
                        w1 = player;
                    }
                    else {
                        w2 = player;
                    }
                }
            }
            boolean result = w1.poker.bigerThan(w2.poker);
            if (result){
                winner = w1;
            }
            else {
                winner = w2;
            }
        }
        System.out.println(winner.id+"号玩家赢得胜利!赢得:"+ total +"金币");

        winner.add(total);
    }
}
  • 十一、将PlayerManager类和PokerManager类联合

1.用GameCenter类将管理类拼接
根据结构分析可知,GameCenter类管理PlayerManager类和PokerManager类,新建一个GameCenter类。

public class GameCeter {
    //记录这一局的筹码
    private int totalMoney;

    //开始游戏
    public void start(){
        System.out.println("游戏开始,请打底:");
        PlayerManager manager = PlayerManager.manager;
        //扣除底注
        manager.betAll(Contant.BASE);

        manager.show();

        //发牌
        System.out.println("开始发牌");
        PokerManager.manager.dealCards(manager.players);

        PlayerManager.manager.show();

        int time = 0;//记录如果是两个人的次数
        boolean isFirst = true;
        int betMoney = 0;

        while (true){
            //获取当前玩家信息
            Player player = manager.currentPlayer();

            //提示选择操作
            System.out.println("请" + player.id + "号玩家选择操作:");
            Utils.showText(true,true,new String[] {"看牌","弃牌",isFirst?"下注":"跟注"});
            int choice = Utils.getInput();

            boolean flag = false;
            switch (choice){
                case 1:
                    //看牌
                    System.out.println(player.getPokerString());
                    flag = true;
                    break;
                case 2:
                    //弃牌
                    System.out.println(player.id + "号玩家弃牌!");
                    player.hasDiscard = true;
                    break;
                default:
                    //下注
                    if (isFirst){
                        while (true) {
                            System.out.print("请输入下注金额:");
                            betMoney = Utils.getInput();

                            int result = player.bet(betMoney);
                            if (result == -1) {
                                //下注不成功
                                System.out.print("余额不足 ");
                            } else {
                                //下注成功
                                isFirst = false;
                                totalMoney += betMoney;
                                break;
                            }
                        }
                    }
                    else {
                        //跟注
                        int result = player.bet(betMoney);
                        if (result == -1){
                            player.hasDiscard = true;
                        }
                        else {
                            System.out.println("下注成功!");
                            totalMoney += betMoney;
                        }
                    }
                    break;
            }
            if (flag == false){
                //计算当前还有多少人可以参与
                int available = manager.leftPlayerCount();

                if (available == 1){
                    //本局结束
                    break;
                }
                if (available == 2){
                    time++;
                    if (time == 4){
                        //两个回合结束 结束游戏
                        break;
                    }
                }
                //time == 3 切换到下一个人
                manager.changeNext();

                manager.show();
            }
        }
        manager.awardWinner(totalMoney);
    }
}
  • 十二、游戏主程序

MyClass类管理游戏主程序
其实MyClass类大可不必最后创建,在编写欢迎界面时就可以创建了,这样我们可以写一点运行一次,防止程序出现多处错误。但在这里不便于分开讲,所以才汇总到最后。

public class MyClass {
    public static void main(String[] args){
        //欢迎界面
        Utils.showText(true,true,new String[]{"欢迎参加扑克游戏"});

        //生成一副牌
        PokerManager.manager.deal();
        //显示一副牌
        PokerManager.manager.show();


        //提示输入玩家人数
        Utils.showText(false,false,new String[]{"请输入参与人数:"});
        //接收用户输入
        int count = Utils.getInput();

        //初始化玩家
        PlayerManager.manager.initPlayer(count);
        //显示玩家
        //PlayerManager.manager.show();

        //开始游戏
        GameCeter ceter = new GameCeter();
        ceter.start();
    }
}

4.扑克牌游戏运行效果

运行效果1
运行效果2

结语

通过运行结果可以看出,这个游戏还是有不少BUG的,但因为是初步学习,暂时先不考虑太多,以免程序太复杂,待日后能力够了后,我会再继续完善这一个程序。麻雀虽小,五脏俱全,这也算是一个完整的游戏,难度中等偏上,适合新手磨练。

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

推荐阅读更多精彩内容