真·干货!!!(扑克牌项目) 它来了它来了!!!

之前说好的一定要研究透彻扑克牌项目,小弟说到做到。在下面我将把每一段代码的功能都尽可能描述清楚,一方面是帮助我理解这个项目的执行过程,另一方面也是希望能对大家有所帮助。

一.总

项目层级

AGameCenter是抽象类并实现IGameInitListener接口,主要是用来“被继承”
Constants接口里面保存一些常量,比如玩家姓名,扑克牌花色等等
IGameInitListener是接口,用来监听初始化是否成功。
PokerGameCenter继承AGameCenter,主要负责控制整个游戏的进行
Util是工具类
Player是玩家类,里面主要有单个玩家的数据信息和操作
PlayerManager是玩家管理类,里面主要涉及操作控制所有玩家的操作
Poker是扑克类,里面主要是涉及单个扑克的操作
PokerManager是扑克管理类,主要涉及操作控制所有扑克的操作。
Main里面是程序入口(这里放的包的不合适,还望大家谅解)

二.分

为了大家能够容易看懂,我们由简到难,一个类一个类地进行说明讲解

1.Constants接口

代码如下

package game;

import Poker1.Poker;
import Poker1.Poker.PicType;

//常量
public interface Constants {
    //下面是玩家提示说明
    interface IBet{
        String[] NORMAL = new String[] {"下注","跟注","all-in","比牌","弃牌"};//正常情况
        String[] LESS = new String[] {"all-in","弃牌"};
    }
    //默认筹码是1000
    interface IPlayer{
        int CHAPS = 1000;//筹码
    }
    //玩家姓名常量
    interface IPlayerName{
        //姓名
        String[] NAMES_XING = {"王","李","张","彭"};
        String[] NAMES_MING_M = {"宏","涛","国","东","建","强"};
        String[] NAMES_MING_L = {"高","晓","博","督","黎"};
    }
    //玩家状态常量
    interface IPlayerState{ 
        int HAND = 0;//在手上
        int DISCARD = 1;//弃牌
    }
    
    //扑克牌中的常量
    interface IPoker{
        String[] DOTS = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"};//点数
        //里面保存了四个花色对象,具体看后面说明
        Poker.PicType[] PIC_TYPES = {
                Poker.PicType.SPADE,Poker.PicType.HRARTS,
                Poker.PicType.CLUBS,Poker.PicType.DIAMONDS
        };
        
    }
}

这个类总体理解起来不难,重点留意一下IPlayerName即玩家姓名常量和IPoker这两个接口,具体什么功能后面会说。
既然涉及Poker类里面的东西,那下面就介绍Poker

2.Poker类

package Poker1;

import game.Constants;

public class Poker {

    public String dot;//牌的点数
    public PicType type;//花色对象,这里花色是用了内部类来保存的,类的名称是PicType
    
    //构造方法
    public Poker(String dot,PicType type) {
        this.dot = dot;
        this.type = type;
    }
    //难点一
    public static class PicType{

        public String pic;//花色
        public int tag;//花色对应的tag值
        
        //由于是涉及花色,所以应该是不可更改的,所以都用final修饰
        public static final PicType SPADE = new PicType("♠黑桃",4);//黑桃
        public static final PicType HRARTS = new PicType("♥红桃",3);//红桃
        public static final PicType CLUBS = new PicType("♣梅花",2);//梅花
        public static final PicType DIAMONDS = new PicType("♦方片",1);//方片
        
        public PicType(String pic,int tag) {
            this.pic = pic;
            this.tag = tag;         
        }       
    }

    //难点二
    //比较两张牌的大小
    public int compareTo(Poker another) {
        int index_m = Util.indexOfObject(this.dot,Constants.IPoker.DOTS);
        int index_a = Util.indexOfObject(another.dot,Constants.IPoker.DOTS);
        if(index_a == index_m) {
             if(this.type.tag > another.type.tag) {
                    return 1;
                }else {
                    return -1;
                }
        }else {
            if(index_m > index_a) {
                return 1;
            }else {
                return -1;
            }
        }
       
    }
    public String toString() {
        return dot+type.pic;
    }
}

这个类有两个难点,大家不要急,在这里我会尽可能解释明白

难点①,内部类PicType:

由于比花色是无法直接比较的,所以我们为每个花色都设置了一种映射关系,即黑桃-4 红桃-3 梅花-2 方片-1,把它们封装在内部类PicType中,就可以实现花色的比较。
如何实现?
以SPADE为例,这是PicType的一个对象,这个对象里面有两个数据成员,一个是pic,一个是tag,其中tag就是其映射的数字4,通过SPADE.tag可以取出tag,进而可以和其他花色的tag进行比较。

难点②比较两张牌的大小:

这里使用了Util类里面的一个方法,我在这里先把这个方法单独拿出来

     public static int indexOfObject(String object, String ...array){
            for (int i = 0; i < array.length; i++){
                if (array[i].equals(object)){
                    return i;
                }
            }
            return -1;
     }

这个方法有两个参数,第一个参数String类的object其实传的是这张牌的点数(从这个方法的调用可以看出),然后第二个参数array其实传的是Constants类里面的DOTS数组,这个数组是所有点数按照从小到大的顺序来排列的,这一点尤为重要,其实也是为什么传递这个数组的原因。因为牌游戏的点数比大小不是简单的2<3,其中还涉及A和JQK这些的比较,而这些不是数字,所以无法与纯数字2345678910直接比较,所以就需要借用它们所在数组的索引值来比较大小,而这个indexOfObject方法其实就是的到这个点数的索引值。
解释到这里,indexOfObject函数就可以先扔一边了,继续看Poker类,我们用了两个变量index_m和index_a来分别保存此牌和另外一张牌点数的索引值。比较两张牌的话,肯定首先比较点数,如果两张牌索引值一样,那么就说明它们点数相同,点数相同就要比较花色,如何比较花色呢?其实前面已经说了,分别取出这两张牌花色的tag值来代替花色进行比较,如果这张牌的花色大,就返回1,如果另外一张牌的花色大,就返回-1.
如果点数不相同,就更好说了,如果这张牌的点数大,就返回1,如果另外一张牌的点数大,就返回-1

3.PokerManager类

package Poker1;

import java.util.*;

import game.Constants;
import game.IGameInitListener;

public class PokerManager {
    //监听器,这个东西的功能我一直很迷糊,知道前几天才理解透彻,在这里与大家分享
    private IGameInitListener listener;
    private ArrayList<Poker> pokers;//保存扑克牌
    
    //创建单例
    private static PokerManager manager;//注意这里要是静态
    private PokerManager() {
        
    }
    //获得PokerManager对象的方法,注意这里需要加锁,这里一定要会写,属于基本功。
    public static PokerManager getManager() {
        if(manager == null) {
            synchronized(PokerManager.class) {
                if(manager == null) {
                    manager = new PokerManager();
                }
            }
        }
        return manager;
    }
    
    //初始化所有扑克牌
    public void initPokers() {
        //初始化数组
        pokers = new ArrayList<>();//这一步是经常忘掉的,一定要对数组进行初始化
        
        //创建扑克牌
        for(String dot:Constants.IPoker.DOTS) {
            //难点一:type对象是Poker.PicType类的一个对象,这个对象从Constants.IPoker.PIC_TYPES数组里面去取
            for(Poker.PicType type: Constants.IPoker.PIC_TYPES) {
                //创建一张牌
                Poker poker = new Poker(dot,type);
                //添加到数组中保存
                pokers.add(poker);
            }
        }
        
        //打乱顺序
        Collections.shuffle(pokers);
        
        //输出牌
        //System.out.println(pokers);
        
        //当扑克牌初始化成功,就回调成功的事件给listener
        if(listener != null) {//难点二:为什么这里listener不是null了呢?后面会给大家做详细解释的。
            //成功的计数加1
            listener.onInitSuccess();
        }
    }
    
    //获取一张牌,然后从数组里面将这张牌删掉
    //难点三:这里为什么要将这张牌删掉?后面会有解释。
    public Poker getOnePoker() {
        if(pokers.size() > 0) {
            //获取第一张牌
            Poker poker = pokers.get(0);
            //将这张牌从数组里面删除
            pokers.remove(0);
            
            return poker;
        }
        return null;
    }
    //难点四:这个东西是干嘛的呢?
    public void setListener(IGameInitListener listener) {
        this.listener = listener;
    }
}

这个类难点相对多一点,不过大家也不用慌,我还是会一一地详细解释的

难点①:创建扑克牌。

其实这里的操作也不是很难理解:利用两个for循环,先确定点数,然后分别给每个点数附上花色。但是稍微有点难度的是这里的增强for循环和PIC_TYPES数组。这个数组里面存了四个元素,这四个元素都是花色对象,而花色对象的类型是Poker.PicType类型,所以代码就出来了。

难点②:为何listener不是空的呢?我也没看见对它进行赋值呀?

关于这一点,得需要后面的类介绍完了才能解释。

难点③:为什么取出这张牌了之后要把这张牌删除呢?

就跟实际玩牌一样,一个玩家取出来了一张牌,那么牌堆儿里面就没有那张牌了。很好理解

难点④:setListener函数是用来干嘛的?

和难点②一样,后面都会有解释

4.Player

package player;

import java.util.*;

import Poker1.Poker;
import game.Constants;

public class Player {
   public int id;//编号
   public String name;//名字
   public int chip;//筹码
   public Poker poker;//玩家手上的一张牌
   public ArrayList<Poker> pokers;//多张牌
   public int playerState;//游戏状态
   public int currentBet;//当前下注金额
   
   //构造方法
   public Player(int id,String name,int chip) {
       this.id = id;
       this.name = name;
       this.chip = chip;
       
       //初始化状态
       playerState = Constants.IPlayerState.HAND;
   }
   
   //扣钱方法
   public void lostMoney(int count) {
       chip -= count;
   }
   
   //加钱方法
   public void winMoney(int count) {
       chip += count;
   }
   
 
   
   public String toString() {
       return "id:"+id+" name:"+name+" "+poker.dot+poker.type.pic+"("+chip+")";
   }
    //退钱方法
    public void returnMoney(int count) {
        chip += count;
        
    }
}

这个类无难点,继续往下说

5.PlayerManager

package player;

import java.util.*;

import game.*;

public class PlayerManager {

    private IGameInitListener listener;
    public ArrayList<Player> players;
    
    //创建单例
    private static PlayerManager manager;//注意这里要是静态
    private PlayerManager() {
        
    }
    public static PlayerManager getManager() {
        if(manager == null) {
            synchronized(PlayerManager.class) {
                if(manager == null) {
                    manager = new PlayerManager();
                }
            }
        }
        return manager;
    }
    
    //初始化所有玩家
    public void initPlayers(int totalPlayer) {
        //创建玩家数组
        players = new ArrayList<>();
        
        for(int i = 0;i < totalPlayer;i++) {
            //获取玩家名字(难点一:如何获取的玩家名字)
            String name = Util.autoGenerateName();
            //创建玩家对象
            Player player = new Player(i+1,name,Constants.IPlayer.CHAPS);
            //保存玩家
            players.add(player);            
        }
        
        //当玩家初始化成功,就回调成功的事件给监听者/listener/游戏中心(难点二:listener为何是null?)
        if(listener != null) {
            listener.onInitSuccess();
        }
        
    }
    
    //获取玩家人数
    public int getPlayerCount() {
        return players.size();
    }
    
    //扣底注钱的方法(打底),所有玩家都扣
    public void deDuctMoney(int count) {
        for(Player player:players) {
             player.lostMoney(count);
        }
    }
    
    //获取一个玩家
    public Player getPlayer(int index) {
        return players.get(index);
    }
    //难点二:money,smallestAllinBet是什么意思,整个方法的逻辑是什么?
    public void awardPlayer(int money,int smallestAllinBet) {
        Player max = null;
        
        for(Player player:players) {
            if(player.playerState == Constants.IPlayerState.HAND) {
                if(max == null) {
                    //找到第一个手上有牌的人/没有弃牌的人
                    max = player;
                }else {
                    int result = max.poker.compareTo(player.poker);
                    if(result == -1) {
                        //max对应的牌比player的牌要小
                        //max记录最大的那个玩家
                        max = player;
                    }
                }
            }
        }
        
        //最大的人赢钱
        max.winMoney(money);
        
        //没有人all-in
        if(smallestAllinBet == 0) {
            return;
        }
        //将max之外的所有all-in的人多于的钱返还
        int totalReturn = 0;
        for(Player player:players) {
            //找到没有弃牌 并且 不是当前最大的那个人
            if(!player.equals(max) && player.playerState == Constants.IPlayerState.HAND) {
                player.returnMoney(player.currentBet-smallestAllinBet);//此人当前下注金额减去最小的all-in值就是需要返还的金额
                totalReturn += (player.currentBet - smallestAllinBet);
            }
        }
        //从max中退回多余的钱(max多拿了totalReturn)
        max.lostMoney(totalReturn);
    }
    //难点三:这个方法的意义?
    public void setListener(IGameInitListener listener) {
        this.listener = listener;
    }
    
    
    
}
难点①:获取玩家姓名方法如何执行的。

首先来看Util类中获取姓名的方法

 public static String autoGenerateName() {
        Random random = new Random();
        
        //姓名的随机数
        int f_index = Math.abs(random.nextInt()%Constants.IPlayerName.NAMES_XING.length);
        int m_index = Math.abs(random.nextInt()%Constants.IPlayerName.NAMES_MING_M.length);
        int l_index = Math.abs(random.nextInt()%Constants.IPlayerName.NAMES_MING_L.length);
        
        String f = Constants.IPlayerName.NAMES_XING[f_index];
        String m = Constants.IPlayerName.NAMES_MING_M[m_index];
        String l = Constants.IPlayerName.NAMES_MING_L[l_index];
        
        return f+m+l;
    }

random.nextInt()%Constants.IPlayerName.NAMES_XING.length的作用是想要获取数组所有索引值之一,但是有负数。Math.abs(x) 函数返回指定数字 “x“ 的绝对值。,也就是获得了真真正正的索引值,即0~数组最后一个元素的索引值。然后再将随机产生的姓,中间的名,和末尾的名,拼接起来(假设名字有三个汉字),就生成了名字。

难点②:public void awardPlayer(int money,int smallestAllinBet)这个方法是用来干啥的?

后面会有相应解释

难点③: public void setListener(IGameInitListener listener) 有什么作用?

和PokerManager一样的难点,后面都有相应解释,请大家耐心阅读。

6.PokerGameCenter

package game;

import java.util.Scanner;

import Poker1.Poker;
import Poker1.PokerManager;
import player.Player;
import player.PlayerManager;

public class PokerGameCenter extends AGameCenter{

    private int liveCount;//生存的玩家数目
    private int currentPlayerIndex;//当前玩家索引值
    private int lastPlayerBet;//上一个玩家下注的金额
    private boolean isAllIn = false;//是否all-in
    private int allInPosition;//从哪开始all-in的
    private int smallestAllinBet;
    
    //创建单例
    private static PokerGameCenter instance;
    private PokerGameCenter() {
        
    }
    public static PokerGameCenter getInstance() {
        if(instance == null) {
            synchronized(AGameCenter.class) {
                if(instance == null) {
                    instance = new PokerGameCenter();
                }
            }
        }
        return instance;
    }
    
    @Override
    protected void initGame() {
        //创建对象
        playerManager = PlayerManager.getManager();
        pokerManager = PokerManager.getManager();
        ante = 0;//台面的总金额
        totalPlayer = 3;//三个玩家
        bottom = 5;//底注
        liveCount = totalPlayer;
        currentPlayerIndex = 1;
        
        //设置监听者
        playerManager.setListener(this);//这里就知道为什么listener不是null了
        pokerManager.setListener(this);
        
        //初始化完毕,成功的计数器+1
        setTotalSuccess(getTotalSuccess() + 1);//难点一:这个计数器是干嘛的? 
    }

    @Override
    protected void initPokers() {
        //System.out.println("initpokers!");        
        pokerManager.initPokers();
    }

    @Override
    protected void initPlayers() {
        //System.out.println("initPlayers!");       
        playerManager.initPlayers(totalPlayer);
    }

    @Override
    protected void start() {
        System.out.println("start!");   
        //1.先扣底注钱
        playerManager.deDuctMoney(bottom);
        ante = totalPlayer * bottom;
        
        //2.发牌
        dealCards();
        
        //3.开始下注
        startBets();
        
    }

    //发牌方法
    private void dealCards() {
        int count = playerManager.getPlayerCount();
        for(int i = 0;i < count;i++) {
            //从扑克中心获取一张牌
            Poker poker = pokerManager.getOnePoker();
            //将这张牌发给对应的人
            Player player = playerManager.getPlayer(i);
            player.poker = poker;
        }
        
        System.out.println(playerManager.players);
    }
    
    //下注方法
    //难点二:关于all-in的部分,到底是怎么执行的?
    private void startBets() {
        while(liveCount > 1) {
            if(currentPlayerIndex == allInPosition) {
                //直接比大小
                break;
                
            }
            Player player = playerManager.getPlayer(currentPlayerIndex - 1);
            //判断当前这个人是否已经弃牌(根据上一轮的选择,判断这一轮他是不是弃牌了)
            if(player.playerState == Constants.IPlayerState.DISCARD) {
                //这个人已经弃牌,下面不需要做
                changeCurrentIndex();
                continue;
            }
            if(player.chip <= lastPlayerBet || isAllIn) {
                //显示操作列表
                //难点三:show方法是怎么执行的
                Util.show(true,true,Constants.IBet.LESS);
                
                //提示信息
                Util.show("请"+player.id+"号玩家选择操作("+player.name+" $"+player.chip+"):");
                
                //接收用户的输入
                int choice = input(Constants.IBet.LESS.length,1);//难点四:input的封装
                switch(choice) {
                case 1:
                    //all-in
                    allIn(player);
                    break;
                case 2:
                    //弃牌
                    giveUp(player);
                    break;
                }
            }else {
                Util.show(true,true,Constants.IBet.NORMAL);
                    
                //提示信息
                Util.show("请"+player.id+"号玩家选择操作("+player.name+" $"+player.chip+"):");
                
                //接收用户的输入
                int choice = input(Constants.IBet.NORMAL.length,1);
                switch(choice) {
                case 1:
                    //下注
                    bet(player);
                    break;
                case 2:
                    //跟注
                    follow(player);
                    break;
                case 3:
                    allIn(player);
                    break;
                case 4:
                    break;
                case 5:
                    //弃牌
                    giveUp(player);         
                    break;
                }
                
            }

            //当前玩家索引指向下一个
            changeCurrentIndex();
        }
        
        //游戏结束
        playerManager.awardPlayer(ante,smallestAllinBet);//难点四,awardPlayer方法如何执行的
        System.out.println(playerManager.players);
    }
    
    //all-in
    //当一个人选择all-in时,只比较一次,最大的赢,然后结束
    private void allIn(Player player) {
        if(isAllIn) {
            //之前已经有人AllIn过了,所以要比较当前allin值
            if(player.chip < smallestAllinBet) {
                smallestAllinBet = player.chip;
            }           
        }else {
            //这人第一次allin
            isAllIn = true;
            //记录当前allin最小值
            smallestAllinBet = player.chip;
            //记录当前allin起始点
            allInPosition = currentPlayerIndex;
        }
        
        //当前这个人all-in
        player.currentBet = player.chip;
        //总金额
        ante += player.chip;
        //下注所有
        player.lostMoney(player.chip);
    }
    
    //跟注
    private void follow(Player player) {
        //总金额增加
        ante += lastPlayerBet;
        //扣除该玩家筹码
        player.lostMoney(lastPlayerBet);

    }
    
    //弃牌
    private void giveUp(Player player) {
        System.out.println("您已弃牌");
        //弃牌
        player.playerState = Constants.IPlayerState.DISCARD;
        //活的人少一个
        liveCount--;    
    }
    
    //下注方法
    private void bet(Player player) {       
            Util.show("请输入下注金额");
            int total = input(player.chip,lastPlayerBet);
            //总金额增加
            ante += total;
            //扣除该玩家筹码
            player.lostMoney(total);
            //记录这次下注金额
            lastPlayerBet = total;//lastPlayerBet是在这里记录的
    }
    
    //接收用户输入
    private int input(int max,int min) {
        Scanner scanner = new Scanner(System.in);
        while(1 > 0) {
            
            int num = scanner.nextInt();
            if(num >= min && num <= max) {
                return num;
            }
            Util.show("数据不正确,请重新输入");               
        }
        
    }
    
    //当前玩家索引值指向下一个
    private void changeCurrentIndex() {
        currentPlayerIndex++;
        if(currentPlayerIndex > totalPlayer) {
            currentPlayerIndex = 1;
        }
    }

    @Override
    public void onInitFailure() {
        // TODO 自动生成的方法存根
        
    }

}

该类难点最多,不过莫慌

难点①:setTotalSuccess(getTotalSuccess() + 1);这个计数器是干嘛的?

其实这个方法在AGameCenter里面,这里我单独把它拿出来,大家看一下,

    public void onInitSuccess() {
        //对成功的计数器加一
        setTotalSuccess(getTotalSuccess() + 1); 
    }
    //totalSuccess的set和get方法
    public void setTotalSuccess(int totalSuccess) {
        this.totalSuccess = totalSuccess;
        if(this.totalSuccess == 3) {
            start();//totalSuccess会被加三次,因为有三次初始化
        }
    }
    public int getTotalSuccess() {
        return this.totalSuccess;
    }

其实之前在PlayerManager和PokerManager两个类里面都有listener.onInitSuccess()方法。在游戏开始之前,有三次初始化,分别是PlayerManager类、PokerManager类和PokerGameCenter类,这三个类若有一个初始化成功,那么onInitSuccess方法就会被执行一次,然后totalSuccess就会+1,等加到三的时候,说明一共有三次初始化成功,也就是达到了游戏开始的条件,那么就会执行start(),使游戏开始。看一下setTotalSuccess方法的代码,大家就可以验证我刚才说的这些了。

难点②:下注方法中关于all-in部分到底是如何执行的。

我们首先来熟悉一下all-in的规则。比如1号玩家选择下注,2号玩家选择all-in,那么3号玩家就只能选择all-in或者弃牌,不管选择哪一个,下一个玩家(也就是1号玩家)也是只能选择all-in或者弃牌。提示1号玩家选择all-in还是弃牌之后,游戏就可以结束了,因为又到了2号玩家,而2号玩家已经选择了all-in。也可以把第一个all-in的玩家看作新一轮游戏的起点,也是新一轮游戏的终点。
那么回到代码上来,为查阅方便,这里把all-in方法再单独拿过来讲解

    private void allIn(Player player) {
        if(isAllIn) {
            //之前已经有人AllIn过了,所以要比较当前allin值
            if(player.chip < smallestAllinBet) {
                smallestAllinBet = player.chip;
            }           
        }else {
            //第一个人第一次allin
            isAllIn = true;
            //记录当前allin最小值
            smallestAllinBet = player.chip;
            //记录当前allin起始点
            allInPosition = currentPlayerIndex;
        }
        
        //当前这个人all-in
        player.currentBet = player.chip;
        //总金额
        ante += player.chip;
        //下注所有
        player.lostMoney(player.chip);
    }
    

这里我描述一下执行过程:一上来先把这个all-in的玩家传递过来,先判断之前是不是有人all-in过了,如果有,那么拿这个人总共的筹码(也就是all-in的钱)和之前all-in的值进行比较,哪个小,就把smallestAllinBet赋值成哪一个。然后执行后面的。如果这个人是所有玩家里面第一位all-in的,那么先设置isAllIn为true,再把当前玩家的筹码赋值给smallestAllinBet,然后,也是最重要的一点,就是把这个玩家的序号记作allin的起点。(比如2号玩家第一次all-in,那么记录allInPosition为2,等又轮到2号玩家的时候,就直接进行比较然后游戏结束。
注意:当currentPlayerIndex为2的时候,指的是2号玩家,索引值为1,也就是get(1)才能得到2号玩家。

难点③:show方法的执行。

这里我单独拿出show方法来给大家看一下

    //输出语句
    public static void show(boolean nextLine,boolean needNumber,String...args) {
        StringBuilder builder = new StringBuilder();
        
        if(needNumber) {//是否需要序号,比如1.自由下注的“1.”是否需要
            System.out.println("----------");
            for(int i = 0;i < args.length;i++) {
                String content = (i+1)+"."+args[i]+" ";
                builder.append(content);
            }
            builder.append("\n----------");
        }else {
            for(String content:args) {
                builder.append(content+" ");
            }
        }
        
        if(nextLine) {
            System.out.println(builder.toString());
        }else {
            System.out.print(builder.toString());
        }
        
    }
    
    //输出一行,不换行,不需要编号的数据
    public static void show(String content) {
        show(false,false,new String[] {content});
    }

如果是执行的needNumber里面的,那么显示结果就是类似
1.下注 2.跟注 3.all-in 4.比牌 5.弃牌
反之就输出类似
下注 跟注 all-in 比牌 弃牌 这样的结果。相信大家看到结果就知道show怎么执行的了,我就不再多说。

难点④:awardPlayer方法执行

这也是上一个类的难点,当时我还没看懂,但是反复看过多遍之后终于理解了。这里再把awardPlayer方法单独拿出来讲解一下

public void awardPlayer(int money,int smallestAllinBet) {
        Player max = null;
        
        for(Player player:players) {
            if(player.playerState == Constants.IPlayerState.HAND) {
                if(max == null) {
                    //找到第一个手上有牌的人/没有弃牌的人
                    max = player;
                }else {
                    int result = max.poker.compareTo(player.poker);
                    if(result == -1) {
                        //max对应的牌比player的牌要小
                        //max记录最大的那个玩家
                        max = player;
                    }
                }
            }
        }
        
        //最大的人赢钱
        max.winMoney(money);
        
        //没有人all-in
        if(smallestAllinBet == 0) {
            return;
        }
        //将max之外的所有all-in的人多于的钱返还
        int totalReturn = 0;
        for(Player player:players) {
            //找到没有弃牌 并且 不是当前最大的那个人
            if(!player.equals(max) && player.playerState == Constants.IPlayerState.HAND) {
                player.returnMoney(player.currentBet-smallestAllinBet);
                totalReturn += (player.currentBet - smallestAllinBet);
            }
        }
        //从max中退回多余的钱
        max.lostMoney(totalReturn);
    }

这里的money是桌面上的总筹码ante,smallestAllinBet是all-in的最小值。比如有两个玩家all-in了,第一个all-in的40,第二个all-in了50,那么肯定要取少的,也就是40,然后还要把第二个玩家多all-in的10返还给第二个玩家。max保存的是牌最大的玩家。当利用比牌方法找到max玩家之后,要把桌面上的所有筹码(ante)赏给max玩家(记住,如果有人all-in,那么这时max玩家其实是多拿了,因为还没有取all-in最小值,现在还是把所有玩家的all-in都给max了)如果没有人all-in,那直接比赛结束。如果有人all-in了,还要一个一个地返回差值,并记录返回的所有差值的和,再从max玩家里面扣所有差值的和,也就是这里的totalReturn(刚刚说了如果有人all-in那么max玩家相当于多拿了所有差值的和)。这就是这个方法的执行过程。

7.IGameInitListener

这个类是接口,就是用来被实现的,发挥着监听初始化是否成功的作用

package game;
//监听初始化是否成功
public interface IGameInitListener {
    void onInitSuccess();
    void onInitFailure();
}

调用它的方法就是调用实现类的相应的重写之后的方法,所以下面来讲一下它的实现类。

8.AGameCenter

package game;

import Poker1.PokerManager;
import player.PlayerManager;

public abstract class AGameCenter implements IGameInitListener{

    protected PlayerManager playerManager;
    protected PokerManager pokerManager;
    protected int ante;//台面的总金额
    protected int totalPlayer;//玩家人数
    protected int bottom;//底注
    
    protected static AGameCenter instance;
    private int totalSuccess;
    
    protected AGameCenter() {
        //初始化游戏本身的数据
        initGame();
        //初始化玩家
        initPlayers();
        //初始化扑克
        initPokers();
    }
    
    //下面这四个方法都在上面的PokerGameCenter类继承之后重写了。调用的时候就执行子类的重写之后的方法
    protected abstract void initGame(); 
    protected abstract void initPokers();   
    protected abstract void initPlayers();
    protected abstract void start();
    
    public void onInitSuccess() {
        //对成功的计数器加一
        setTotalSuccess(getTotalSuccess() + 1); 
    }
    
    @Override
    public void onInitFailure() {

    }

    //totalSuccess的set和get方法
    public void setTotalSuccess(int totalSuccess) {
        this.totalSuccess = totalSuccess;
        if(this.totalSuccess == 3) {
            start();//totalSuccess会被加三次,因为有三次初始化
        }
    }
    public int getTotalSuccess() {
        return this.totalSuccess;
    }
}

介绍完PokerGameCenter之后,这个类就没啥可说的了。
还有工具类,工具类也已经分块介绍完了,这里再拿过来总体看一下

9.Util

package game;

import java.util.Random;

public class Util {
      //生成名字
      public static String autoGenerateName() {
        Random random = new Random();
        
        //姓名的随机数
        int f_index = Math.abs(random.nextInt()%Constants.IPlayerName.NAMES_XING.length);
        int m_index = Math.abs(random.nextInt()%Constants.IPlayerName.NAMES_MING_M.length);
        int l_index = Math.abs(random.nextInt()%Constants.IPlayerName.NAMES_MING_L.length);
        
        String f = Constants.IPlayerName.NAMES_XING[f_index];
        String m = Constants.IPlayerName.NAMES_MING_M[m_index];
        String l = Constants.IPlayerName.NAMES_MING_L[l_index];
        
        return f+m+l;
    }
      
    //输出语句
    public static void show(boolean nextLine,boolean needNumber,String...args) {
        StringBuilder builder = new StringBuilder();
        
        if(needNumber) {
            System.out.println("----------");
            for(int i = 0;i < args.length;i++) {
                String content = (i+1)+"."+args[i]+" ";
                builder.append(content);
            }
            builder.append("\n----------");
        }else {
            for(String content:args) {
                builder.append(content+" ");
            }
        }
        
        if(nextLine) {
            System.out.println(builder.toString());
        }else {
            System.out.print(builder.toString());
        }
        
    }
    
    //输出一行,不换行,不需要编号的数据
    public static void show(String content) {
        show(false,false,new String[] {content});
    }
    
     public static int indexOfObject(String object, String ...array){
            for (int i = 0; i < array.length; i++){
                if (array[i].equals(object)){
                    return i;
                }
            }
            return -1;
     }
    

}

最后就是main方法

10.Main

package Poker1;

import game.PokerGameCenter;

public class Main {

    public static void main(String[] args) {
        PokerGameCenter center = PokerGameCenter.getInstance();
    }
}
    

三.梳理

这里简单地说一下程序的执行顺序。
首先在main方法中得到PokerGameCenter的对象,在new PokerGameCenter()的时候会调用父类的无参构造


而父类的无参构造就是

三个都初始化完了之后,totalSuccess就等于3,然后就自动调用start()方法,然后游戏就开始了。

四.总结

本来一直都有打算研究一下这个程序的,因为很多地方地方我还没有搞清楚,不过因为懒惰拖延耽搁了。经过五六个小时的研究,终于写成了这篇博客,对于我来说收获是非常大的,因为自己之前也写过一个这样的,不过功能也不全面还有很多bug,老师讲的时候我也没有完全听懂,经过写这篇博客,我终于明白了这个项目的执行过程。同时也希望这篇文章能对大家有所帮助!加油!!!

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

推荐阅读更多精彩内容