真·干貨!J缫怼7惨痢(撲克牌項(xiàng)目) 它來(lái)了它來(lái)了!V现邸!

之前說(shuō)好的一定要研究透徹?fù)淇伺祈?xiàng)目诵盼,小弟說(shuō)到做到惠豺。在下面我將把每一段代碼的功能都盡可能描述清楚,一方面是幫助我理解這個(gè)項(xiàng)目的執(zhí)行過(guò)程风宁,另一方面也是希望能對(duì)大家有所幫助洁墙。

一.總

項(xiàng)目層級(jí)

AGameCenter是抽象類并實(shí)現(xiàn)IGameInitListener接口,主要是用來(lái)“被繼承”
Constants接口里面保存一些常量戒财,比如玩家姓名热监,撲克牌花色等等
IGameInitListener是接口,用來(lái)監(jiān)聽初始化是否成功饮寞。
PokerGameCenter繼承AGameCenter孝扛,主要負(fù)責(zé)控制整個(gè)游戲的進(jìn)行
Util是工具類
Player是玩家類列吼,里面主要有單個(gè)玩家的數(shù)據(jù)信息和操作
PlayerManager是玩家管理類,里面主要涉及操作控制所有玩家的操作
Poker是撲克類苦始,里面主要是涉及單個(gè)撲克的操作
PokerManager是撲克管理類寞钥,主要涉及操作控制所有撲克的操作。
Main里面是程序入口(這里放的包的不合適陌选,還望大家諒解)

二.分

為了大家能夠容易看懂理郑,我們由簡(jiǎn)到難,一個(gè)類一個(gè)類地進(jìn)行說(shuō)明講解

1.Constants接口

代碼如下

package game;

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

//常量
public interface Constants {
    //下面是玩家提示說(shuō)明
    interface IBet{
        String[] NORMAL = new String[] {"下注","跟注","all-in","比牌","棄牌"};//正常情況
        String[] LESS = new String[] {"all-in","棄牌"};
    }
    //默認(rèn)籌碼是1000
    interface IPlayer{
        int CHAPS = 1000;//籌碼
    }
    //玩家姓名常量
    interface IPlayerName{
        //姓名
        String[] NAMES_XING = {"王","李","張","彭"};
        String[] NAMES_MING_M = {"宏","濤","國(guó)","東","建","強(qiáng)"};
        String[] NAMES_MING_L = {"高","曉","博","督","黎"};
    }
    //玩家狀態(tài)常量
    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"};//點(diǎn)數(shù)
        //里面保存了四個(gè)花色對(duì)象咨油,具體看后面說(shuō)明
        Poker.PicType[] PIC_TYPES = {
                Poker.PicType.SPADE,Poker.PicType.HRARTS,
                Poker.PicType.CLUBS,Poker.PicType.DIAMONDS
        };
        
    }
}

這個(gè)類總體理解起來(lái)不難您炉,重點(diǎn)留意一下IPlayerName即玩家姓名常量和IPoker這兩個(gè)接口,具體什么功能后面會(huì)說(shuō)役电。
既然涉及Poker類里面的東西赚爵,那下面就介紹Poker

2.Poker類

package Poker1;

import game.Constants;

public class Poker {

    public String dot;//牌的點(diǎn)數(shù)
    public PicType type;//花色對(duì)象,這里花色是用了內(nèi)部類來(lái)保存的宴霸,類的名稱是PicType
    
    //構(gòu)造方法
    public Poker(String dot,PicType type) {
        this.dot = dot;
        this.type = type;
    }
    //難點(diǎn)一
    public static class PicType{

        public String pic;//花色
        public int tag;//花色對(duì)應(yīng)的tag值
        
        //由于是涉及花色囱晴,所以應(yīng)該是不可更改的,所以都用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;         
        }       
    }

    //難點(diǎn)二
    //比較兩張牌的大小
    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;
    }
}

這個(gè)類有兩個(gè)難點(diǎn)瓢谢,大家不要急畸写,在這里我會(huì)盡可能解釋明白

難點(diǎn)①,內(nèi)部類PicType:

由于比花色是無(wú)法直接比較的氓扛,所以我們?yōu)槊總€(gè)花色都設(shè)置了一種映射關(guān)系枯芬,即黑桃-4 紅桃-3 梅花-2 方片-1,把它們封裝在內(nèi)部類PicType中采郎,就可以實(shí)現(xiàn)花色的比較千所。
如何實(shí)現(xiàn)?
以SPADE為例蒜埋,這是PicType的一個(gè)對(duì)象淫痰,這個(gè)對(duì)象里面有兩個(gè)數(shù)據(jù)成員,一個(gè)是pic整份,一個(gè)是tag待错,其中tag就是其映射的數(shù)字4,通過(guò)SPADE.tag可以取出tag烈评,進(jìn)而可以和其他花色的tag進(jìn)行比較火俄。

難點(diǎn)②比較兩張牌的大小:

這里使用了Util類里面的一個(gè)方法讲冠,我在這里先把這個(gè)方法單獨(dú)拿出來(lái)

     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;
     }

這個(gè)方法有兩個(gè)參數(shù)瓜客,第一個(gè)參數(shù)String類的object其實(shí)傳的是這張牌的點(diǎn)數(shù)(從這個(gè)方法的調(diào)用可以看出),然后第二個(gè)參數(shù)array其實(shí)傳的是Constants類里面的DOTS數(shù)組,這個(gè)數(shù)組是所有點(diǎn)數(shù)谱仪,按照從小到大的順序來(lái)排列的玻熙,這一點(diǎn)尤為重要,其實(shí)也是為什么傳遞這個(gè)數(shù)組的原因芽卿。因?yàn)榕朴螒虻狞c(diǎn)數(shù)比大小不是簡(jiǎn)單的2<3,其中還涉及A和JQK這些的比較揭芍,而這些不是數(shù)字,所以無(wú)法與純數(shù)字2345678910直接比較卸例,所以就需要借用它們所在數(shù)組的索引值來(lái)比較大小称杨,而這個(gè)indexOfObject方法其實(shí)就是的到這個(gè)點(diǎn)數(shù)的索引值。
解釋到這里筷转,indexOfObject函數(shù)就可以先扔一邊了姑原,繼續(xù)看Poker類,我們用了兩個(gè)變量index_m和index_a來(lái)分別保存此牌和另外一張牌點(diǎn)數(shù)的索引值呜舒。比較兩張牌的話锭汛,肯定首先比較點(diǎn)數(shù),如果兩張牌索引值一樣袭蝗,那么就說(shuō)明它們點(diǎn)數(shù)相同唤殴,點(diǎn)數(shù)相同就要比較花色,如何比較花色呢到腥?其實(shí)前面已經(jīng)說(shuō)了朵逝,分別取出這兩張牌花色的tag值來(lái)代替花色進(jìn)行比較,如果這張牌的花色大乡范,就返回1配名,如果另外一張牌的花色大,就返回-1.
如果點(diǎn)數(shù)不相同晋辆,就更好說(shuō)了渠脉,如果這張牌的點(diǎn)數(shù)大,就返回1瓶佳,如果另外一張牌的點(diǎn)數(shù)大芋膘,就返回-1

3.PokerManager類

package Poker1;

import java.util.*;

import game.Constants;
import game.IGameInitListener;

public class PokerManager {
    //監(jiān)聽器,這個(gè)東西的功能我一直很迷糊霸饲,知道前幾天才理解透徹索赏,在這里與大家分享
    private IGameInitListener listener;
    private ArrayList<Poker> pokers;//保存撲克牌
    
    //創(chuàng)建單例
    private static PokerManager manager;//注意這里要是靜態(tài)
    private PokerManager() {
        
    }
    //獲得PokerManager對(duì)象的方法,注意這里需要加鎖贴彼,這里一定要會(huì)寫,屬于基本功埃儿。
    public static PokerManager getManager() {
        if(manager == null) {
            synchronized(PokerManager.class) {
                if(manager == null) {
                    manager = new PokerManager();
                }
            }
        }
        return manager;
    }
    
    //初始化所有撲克牌
    public void initPokers() {
        //初始化數(shù)組
        pokers = new ArrayList<>();//這一步是經(jīng)常忘掉的器仗,一定要對(duì)數(shù)組進(jìn)行初始化
        
        //創(chuàng)建撲克牌
        for(String dot:Constants.IPoker.DOTS) {
            //難點(diǎn)一:type對(duì)象是Poker.PicType類的一個(gè)對(duì)象,這個(gè)對(duì)象從Constants.IPoker.PIC_TYPES數(shù)組里面去取
            for(Poker.PicType type: Constants.IPoker.PIC_TYPES) {
                //創(chuàng)建一張牌
                Poker poker = new Poker(dot,type);
                //添加到數(shù)組中保存
                pokers.add(poker);
            }
        }
        
        //打亂順序
        Collections.shuffle(pokers);
        
        //輸出牌
        //System.out.println(pokers);
        
        //當(dāng)撲克牌初始化成功,就回調(diào)成功的事件給listener
        if(listener != null) {//難點(diǎn)二:為什么這里listener不是null了呢精钮?后面會(huì)給大家做詳細(xì)解釋的威鹿。
            //成功的計(jì)數(shù)加1
            listener.onInitSuccess();
        }
    }
    
    //獲取一張牌,然后從數(shù)組里面將這張牌刪掉
    //難點(diǎn)三:這里為什么要將這張牌刪掉轨香?后面會(huì)有解釋忽你。
    public Poker getOnePoker() {
        if(pokers.size() > 0) {
            //獲取第一張牌
            Poker poker = pokers.get(0);
            //將這張牌從數(shù)組里面刪除
            pokers.remove(0);
            
            return poker;
        }
        return null;
    }
    //難點(diǎn)四:這個(gè)東西是干嘛的呢?
    public void setListener(IGameInitListener listener) {
        this.listener = listener;
    }
}

這個(gè)類難點(diǎn)相對(duì)多一點(diǎn)臂容,不過(guò)大家也不用慌科雳,我還是會(huì)一一地詳細(xì)解釋的

難點(diǎn)①:創(chuàng)建撲克牌。

其實(shí)這里的操作也不是很難理解:利用兩個(gè)for循環(huán)脓杉,先確定點(diǎn)數(shù)糟秘,然后分別給每個(gè)點(diǎn)數(shù)附上花色。但是稍微有點(diǎn)難度的是這里的增強(qiáng)for循環(huán)和PIC_TYPES數(shù)組球散。這個(gè)數(shù)組里面存了四個(gè)元素尿赚,這四個(gè)元素都是花色對(duì)象,而花色對(duì)象的類型是Poker.PicType類型蕉堰,所以代碼就出來(lái)了凌净。

難點(diǎn)②:為何listener不是空的呢?我也沒(méi)看見對(duì)它進(jìn)行賦值呀屋讶?

關(guān)于這一點(diǎn)冰寻,得需要后面的類介紹完了才能解釋。

難點(diǎn)③:為什么取出這張牌了之后要把這張牌刪除呢丑婿?

就跟實(shí)際玩牌一樣性雄,一個(gè)玩家取出來(lái)了一張牌,那么牌堆兒里面就沒(méi)有那張牌了羹奉。很好理解

難點(diǎn)④:setListener函數(shù)是用來(lái)干嘛的秒旋?

和難點(diǎn)②一樣,后面都會(huì)有解釋

4.Player

package player;

import java.util.*;

import Poker1.Poker;
import game.Constants;

public class Player {
   public int id;//編號(hào)
   public String name;//名字
   public int chip;//籌碼
   public Poker poker;//玩家手上的一張牌
   public ArrayList<Poker> pokers;//多張牌
   public int playerState;//游戲狀態(tài)
   public int currentBet;//當(dāng)前下注金額
   
   //構(gòu)造方法
   public Player(int id,String name,int chip) {
       this.id = id;
       this.name = name;
       this.chip = chip;
       
       //初始化狀態(tài)
       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;
        
    }
}

這個(gè)類無(wú)難點(diǎn)诀拭,繼續(xù)往下說(shuō)

5.PlayerManager

package player;

import java.util.*;

import game.*;

public class PlayerManager {

    private IGameInitListener listener;
    public ArrayList<Player> players;
    
    //創(chuàng)建單例
    private static PlayerManager manager;//注意這里要是靜態(tài)
    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) {
        //創(chuàng)建玩家數(shù)組
        players = new ArrayList<>();
        
        for(int i = 0;i < totalPlayer;i++) {
            //獲取玩家名字(難點(diǎn)一:如何獲取的玩家名字)
            String name = Util.autoGenerateName();
            //創(chuàng)建玩家對(duì)象
            Player player = new Player(i+1,name,Constants.IPlayer.CHAPS);
            //保存玩家
            players.add(player);            
        }
        
        //當(dāng)玩家初始化成功迁筛,就回調(diào)成功的事件給監(jiān)聽者/listener/游戲中心(難點(diǎn)二:listener為何是null?)
        if(listener != null) {
            listener.onInitSuccess();
        }
        
    }
    
    //獲取玩家人數(shù)
    public int getPlayerCount() {
        return players.size();
    }
    
    //扣底注錢的方法(打底)耕挨,所有玩家都扣
    public void deDuctMoney(int count) {
        for(Player player:players) {
             player.lostMoney(count);
        }
    }
    
    //獲取一個(gè)玩家
    public Player getPlayer(int index) {
        return players.get(index);
    }
    //難點(diǎn)二:money细卧,smallestAllinBet是什么意思,整個(gè)方法的邏輯是什么筒占?
    public void awardPlayer(int money,int smallestAllinBet) {
        Player max = null;
        
        for(Player player:players) {
            if(player.playerState == Constants.IPlayerState.HAND) {
                if(max == null) {
                    //找到第一個(gè)手上有牌的人/沒(méi)有棄牌的人
                    max = player;
                }else {
                    int result = max.poker.compareTo(player.poker);
                    if(result == -1) {
                        //max對(duì)應(yīng)的牌比player的牌要小
                        //max記錄最大的那個(gè)玩家
                        max = player;
                    }
                }
            }
        }
        
        //最大的人贏錢
        max.winMoney(money);
        
        //沒(méi)有人all-in
        if(smallestAllinBet == 0) {
            return;
        }
        //將max之外的所有all-in的人多于的錢返還
        int totalReturn = 0;
        for(Player player:players) {
            //找到?jīng)]有棄牌 并且 不是當(dāng)前最大的那個(gè)人
            if(!player.equals(max) && player.playerState == Constants.IPlayerState.HAND) {
                player.returnMoney(player.currentBet-smallestAllinBet);//此人當(dāng)前下注金額減去最小的all-in值就是需要返還的金額
                totalReturn += (player.currentBet - smallestAllinBet);
            }
        }
        //從max中退回多余的錢(max多拿了totalReturn)
        max.lostMoney(totalReturn);
    }
    //難點(diǎn)三:這個(gè)方法的意義贪庙?
    public void setListener(IGameInitListener listener) {
        this.listener = listener;
    }
    
    
    
}
難點(diǎn)①:獲取玩家姓名方法如何執(zhí)行的。

首先來(lái)看Util類中獲取姓名的方法

 public static String autoGenerateName() {
        Random random = new Random();
        
        //姓名的隨機(jī)數(shù)
        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的作用是想要獲取數(shù)組所有索引值之一翰苫,但是有負(fù)數(shù)止邮。Math.abs(x) 函數(shù)返回指定數(shù)字 “x“ 的絕對(duì)值这橙。,也就是獲得了真真正正的索引值导披,即0~數(shù)組最后一個(gè)元素的索引值屈扎。然后再將隨機(jī)產(chǎn)生的姓,中間的名撩匕,和末尾的名鹰晨,拼接起來(lái)(假設(shè)名字有三個(gè)漢字),就生成了名字止毕。

難點(diǎn)②:public void awardPlayer(int money,int smallestAllinBet)這個(gè)方法是用來(lái)干啥的模蜡?

后面會(huì)有相應(yīng)解釋

難點(diǎn)③: public void setListener(IGameInitListener listener) 有什么作用?

和PokerManager一樣的難點(diǎn)滓技,后面都有相應(yīng)解釋哩牍,請(qǐng)大家耐心閱讀。

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;//生存的玩家數(shù)目
    private int currentPlayerIndex;//當(dāng)前玩家索引值
    private int lastPlayerBet;//上一個(gè)玩家下注的金額
    private boolean isAllIn = false;//是否all-in
    private int allInPosition;//從哪開始all-in的
    private int smallestAllinBet;
    
    //創(chuàng)建單例
    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() {
        //創(chuàng)建對(duì)象
        playerManager = PlayerManager.getManager();
        pokerManager = PokerManager.getManager();
        ante = 0;//臺(tái)面的總金額
        totalPlayer = 3;//三個(gè)玩家
        bottom = 5;//底注
        liveCount = totalPlayer;
        currentPlayerIndex = 1;
        
        //設(shè)置監(jiān)聽者
        playerManager.setListener(this);//這里就知道為什么listener不是null了
        pokerManager.setListener(this);
        
        //初始化完畢令漂,成功的計(jì)數(shù)器+1
        setTotalSuccess(getTotalSuccess() + 1);//難點(diǎn)一:這個(gè)計(jì)數(shù)器是干嘛的膝昆? 
    }

    @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.發(fā)牌
        dealCards();
        
        //3.開始下注
        startBets();
        
    }

    //發(fā)牌方法
    private void dealCards() {
        int count = playerManager.getPlayerCount();
        for(int i = 0;i < count;i++) {
            //從撲克中心獲取一張牌
            Poker poker = pokerManager.getOnePoker();
            //將這張牌發(fā)給對(duì)應(yīng)的人
            Player player = playerManager.getPlayer(i);
            player.poker = poker;
        }
        
        System.out.println(playerManager.players);
    }
    
    //下注方法
    //難點(diǎn)二:關(guān)于all-in的部分,到底是怎么執(zhí)行的叠必?
    private void startBets() {
        while(liveCount > 1) {
            if(currentPlayerIndex == allInPosition) {
                //直接比大小
                break;
                
            }
            Player player = playerManager.getPlayer(currentPlayerIndex - 1);
            //判斷當(dāng)前這個(gè)人是否已經(jīng)棄牌(根據(jù)上一輪的選擇荚孵,判斷這一輪他是不是棄牌了)
            if(player.playerState == Constants.IPlayerState.DISCARD) {
                //這個(gè)人已經(jīng)棄牌,下面不需要做
                changeCurrentIndex();
                continue;
            }
            if(player.chip <= lastPlayerBet || isAllIn) {
                //顯示操作列表
                //難點(diǎn)三:show方法是怎么執(zhí)行的
                Util.show(true,true,Constants.IBet.LESS);
                
                //提示信息
                Util.show("請(qǐng)"+player.id+"號(hào)玩家選擇操作("+player.name+" $"+player.chip+"):");
                
                //接收用戶的輸入
                int choice = input(Constants.IBet.LESS.length,1);//難點(diǎn)四: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("請(qǐng)"+player.id+"號(hào)玩家選擇操作("+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;
                }
                
            }

            //當(dāng)前玩家索引指向下一個(gè)
            changeCurrentIndex();
        }
        
        //游戲結(jié)束
        playerManager.awardPlayer(ante,smallestAllinBet);//難點(diǎn)四纬朝,awardPlayer方法如何執(zhí)行的
        System.out.println(playerManager.players);
    }
    
    //all-in
    //當(dāng)一個(gè)人選擇all-in時(shí)收叶,只比較一次,最大的贏共苛,然后結(jié)束
    private void allIn(Player player) {
        if(isAllIn) {
            //之前已經(jīng)有人AllIn過(guò)了判没,所以要比較當(dāng)前allin值
            if(player.chip < smallestAllinBet) {
                smallestAllinBet = player.chip;
            }           
        }else {
            //這人第一次allin
            isAllIn = true;
            //記錄當(dāng)前allin最小值
            smallestAllinBet = player.chip;
            //記錄當(dāng)前allin起始點(diǎn)
            allInPosition = currentPlayerIndex;
        }
        
        //當(dāng)前這個(gè)人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;
        //活的人少一個(gè)
        liveCount--;    
    }
    
    //下注方法
    private void bet(Player player) {       
            Util.show("請(qǐng)輸入下注金額");
            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("數(shù)據(jù)不正確,請(qǐng)重新輸入");               
        }
        
    }
    
    //當(dāng)前玩家索引值指向下一個(gè)
    private void changeCurrentIndex() {
        currentPlayerIndex++;
        if(currentPlayerIndex > totalPlayer) {
            currentPlayerIndex = 1;
        }
    }

    @Override
    public void onInitFailure() {
        // TODO 自動(dòng)生成的方法存根
        
    }

}

該類難點(diǎn)最多隅茎,不過(guò)莫慌

難點(diǎn)①:setTotalSuccess(getTotalSuccess() + 1);這個(gè)計(jì)數(shù)器是干嘛的澄峰?

其實(shí)這個(gè)方法在AGameCenter里面,這里我單獨(dú)把它拿出來(lái)辟犀,大家看一下俏竞,

    public void onInitSuccess() {
        //對(duì)成功的計(jì)數(shù)器加一
        setTotalSuccess(getTotalSuccess() + 1); 
    }
    //totalSuccess的set和get方法
    public void setTotalSuccess(int totalSuccess) {
        this.totalSuccess = totalSuccess;
        if(this.totalSuccess == 3) {
            start();//totalSuccess會(huì)被加三次,因?yàn)橛腥纬跏蓟?        }
    }
    public int getTotalSuccess() {
        return this.totalSuccess;
    }

其實(shí)之前在PlayerManager和PokerManager兩個(gè)類里面都有listener.onInitSuccess()方法堂竟。在游戲開始之前魂毁,有三次初始化,分別是PlayerManager類出嘹、PokerManager類和PokerGameCenter類席楚,這三個(gè)類若有一個(gè)初始化成功,那么onInitSuccess方法就會(huì)被執(zhí)行一次税稼,然后totalSuccess就會(huì)+1烦秩,等加到三的時(shí)候刁赦,說(shuō)明一共有三次初始化成功,也就是達(dá)到了游戲開始的條件闻镶,那么就會(huì)執(zhí)行start(),使游戲開始丸升∶看一下setTotalSuccess方法的代碼,大家就可以驗(yàn)證我剛才說(shuō)的這些了狡耻。

難點(diǎn)②:下注方法中關(guān)于all-in部分到底是如何執(zhí)行的墩剖。

我們首先來(lái)熟悉一下all-in的規(guī)則。比如1號(hào)玩家選擇下注夷狰,2號(hào)玩家選擇all-in岭皂,那么3號(hào)玩家就只能選擇all-in或者棄牌,不管選擇哪一個(gè)沼头,下一個(gè)玩家(也就是1號(hào)玩家)也是只能選擇all-in或者棄牌爷绘。提示1號(hào)玩家選擇all-in還是棄牌之后,游戲就可以結(jié)束了进倍,因?yàn)橛值搅?號(hào)玩家土至,而2號(hào)玩家已經(jīng)選擇了all-in。也可以把第一個(gè)all-in的玩家看作新一輪游戲的起點(diǎn)猾昆,也是新一輪游戲的終點(diǎn)陶因。
那么回到代碼上來(lái),為查閱方便垂蜗,這里把a(bǔ)ll-in方法再單獨(dú)拿過(guò)來(lái)講解

    private void allIn(Player player) {
        if(isAllIn) {
            //之前已經(jīng)有人AllIn過(guò)了楷扬,所以要比較當(dāng)前allin值
            if(player.chip < smallestAllinBet) {
                smallestAllinBet = player.chip;
            }           
        }else {
            //第一個(gè)人第一次allin
            isAllIn = true;
            //記錄當(dāng)前allin最小值
            smallestAllinBet = player.chip;
            //記錄當(dāng)前allin起始點(diǎn)
            allInPosition = currentPlayerIndex;
        }
        
        //當(dāng)前這個(gè)人all-in
        player.currentBet = player.chip;
        //總金額
        ante += player.chip;
        //下注所有
        player.lostMoney(player.chip);
    }
    

這里我描述一下執(zhí)行過(guò)程:一上來(lái)先把這個(gè)all-in的玩家傳遞過(guò)來(lái),先判斷之前是不是有人all-in過(guò)了贴见,如果有烘苹,那么拿這個(gè)人總共的籌碼(也就是all-in的錢)和之前all-in的值進(jìn)行比較,哪個(gè)小蝇刀,就把smallestAllinBet賦值成哪一個(gè)螟加。然后執(zhí)行后面的。如果這個(gè)人是所有玩家里面第一位all-in的吞琐,那么先設(shè)置isAllIn為true捆探,再把當(dāng)前玩家的籌碼賦值給smallestAllinBet,然后站粟,也是最重要的一點(diǎn)黍图,就是把這個(gè)玩家的序號(hào)記作allin的起點(diǎn)。(比如2號(hào)玩家第一次all-in奴烙,那么記錄allInPosition為2助被,等又輪到2號(hào)玩家的時(shí)候剖张,就直接進(jìn)行比較然后游戲結(jié)束。
注意:當(dāng)currentPlayerIndex為2的時(shí)候揩环,指的是2號(hào)玩家搔弄,索引值為1,也就是get(1)才能得到2號(hào)玩家丰滑。

難點(diǎn)③:show方法的執(zhí)行顾犹。

這里我單獨(dú)拿出show方法來(lái)給大家看一下

    //輸出語(yǔ)句
    public static void show(boolean nextLine,boolean needNumber,String...args) {
        StringBuilder builder = new StringBuilder();
        
        if(needNumber) {//是否需要序號(hào),比如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());
        }
        
    }
    
    //輸出一行褒墨,不換行炫刷,不需要編號(hào)的數(shù)據(jù)
    public static void show(String content) {
        show(false,false,new String[] {content});
    }

如果是執(zhí)行的needNumber里面的,那么顯示結(jié)果就是類似
1.下注 2.跟注 3.all-in 4.比牌 5.棄牌
反之就輸出類似
下注 跟注 all-in 比牌 棄牌 這樣的結(jié)果郁妈。相信大家看到結(jié)果就知道show怎么執(zhí)行的了浑玛,我就不再多說(shuō)。

難點(diǎn)④:awardPlayer方法執(zhí)行

這也是上一個(gè)類的難點(diǎn)噩咪,當(dāng)時(shí)我還沒(méi)看懂顾彰,但是反復(fù)看過(guò)多遍之后終于理解了。這里再把a(bǔ)wardPlayer方法單獨(dú)拿出來(lái)講解一下

public void awardPlayer(int money,int smallestAllinBet) {
        Player max = null;
        
        for(Player player:players) {
            if(player.playerState == Constants.IPlayerState.HAND) {
                if(max == null) {
                    //找到第一個(gè)手上有牌的人/沒(méi)有棄牌的人
                    max = player;
                }else {
                    int result = max.poker.compareTo(player.poker);
                    if(result == -1) {
                        //max對(duì)應(yīng)的牌比player的牌要小
                        //max記錄最大的那個(gè)玩家
                        max = player;
                    }
                }
            }
        }
        
        //最大的人贏錢
        max.winMoney(money);
        
        //沒(méi)有人all-in
        if(smallestAllinBet == 0) {
            return;
        }
        //將max之外的所有all-in的人多于的錢返還
        int totalReturn = 0;
        for(Player player:players) {
            //找到?jīng)]有棄牌 并且 不是當(dāng)前最大的那個(gè)人
            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的最小值拘央。比如有兩個(gè)玩家all-in了,第一個(gè)all-in的40书在,第二個(gè)all-in了50灰伟,那么肯定要取少的,也就是40儒旬,然后還要把第二個(gè)玩家多all-in的10返還給第二個(gè)玩家栏账。max保存的是牌最大的玩家。當(dāng)利用比牌方法找到max玩家之后栈源,要把桌面上的所有籌碼(ante)賞給max玩家(記住挡爵,如果有人all-in,那么這時(shí)max玩家其實(shí)是多拿了甚垦,因?yàn)檫€沒(méi)有取all-in最小值茶鹃,現(xiàn)在還是把所有玩家的all-in都給max了)如果沒(méi)有人all-in,那直接比賽結(jié)束艰亮。如果有人all-in了闭翩,還要一個(gè)一個(gè)地返回差值,并記錄返回的所有差值的和迄埃,再?gòu)膍ax玩家里面扣所有差值的和疗韵,也就是這里的totalReturn(剛剛說(shuō)了如果有人all-in那么max玩家相當(dāng)于多拿了所有差值的和)。這就是這個(gè)方法的執(zhí)行過(guò)程侄非。

7.IGameInitListener

這個(gè)類是接口蕉汪,就是用來(lái)被實(shí)現(xiàn)的流译,發(fā)揮著監(jiān)聽初始化是否成功的作用

package game;
//監(jiān)聽初始化是否成功
public interface IGameInitListener {
    void onInitSuccess();
    void onInitFailure();
}

調(diào)用它的方法就是調(diào)用實(shí)現(xiàn)類的相應(yīng)的重寫之后的方法,所以下面來(lái)講一下它的實(shí)現(xiàn)類者疤。

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;//臺(tái)面的總金額
    protected int totalPlayer;//玩家人數(shù)
    protected int bottom;//底注
    
    protected static AGameCenter instance;
    private int totalSuccess;
    
    protected AGameCenter() {
        //初始化游戲本身的數(shù)據(jù)
        initGame();
        //初始化玩家
        initPlayers();
        //初始化撲克
        initPokers();
    }
    
    //下面這四個(gè)方法都在上面的PokerGameCenter類繼承之后重寫了福澡。調(diào)用的時(shí)候就執(zhí)行子類的重寫之后的方法
    protected abstract void initGame(); 
    protected abstract void initPokers();   
    protected abstract void initPlayers();
    protected abstract void start();
    
    public void onInitSuccess() {
        //對(duì)成功的計(jì)數(shù)器加一
        setTotalSuccess(getTotalSuccess() + 1); 
    }
    
    @Override
    public void onInitFailure() {

    }

    //totalSuccess的set和get方法
    public void setTotalSuccess(int totalSuccess) {
        this.totalSuccess = totalSuccess;
        if(this.totalSuccess == 3) {
            start();//totalSuccess會(huì)被加三次,因?yàn)橛腥纬跏蓟?        }
    }
    public int getTotalSuccess() {
        return this.totalSuccess;
    }
}

介紹完P(guān)okerGameCenter之后驹马,這個(gè)類就沒(méi)啥可說(shuō)的了竞漾。
還有工具類,工具類也已經(jīng)分塊介紹完了窥翩,這里再拿過(guò)來(lái)總體看一下

9.Util

package game;

import java.util.Random;

public class Util {
      //生成名字
      public static String autoGenerateName() {
        Random random = new Random();
        
        //姓名的隨機(jī)數(shù)
        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;
    }
      
    //輸出語(yǔ)句
    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());
        }
        
    }
    
    //輸出一行,不換行鳞仙,不需要編號(hào)的數(shù)據(jù)
    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();
    }
}
    

三.梳理

這里簡(jiǎn)單地說(shuō)一下程序的執(zhí)行順序寇蚊。
首先在main方法中得到PokerGameCenter的對(duì)象,在new PokerGameCenter()的時(shí)候會(huì)調(diào)用父類的無(wú)參構(gòu)造


而父類的無(wú)參構(gòu)造就是

三個(gè)都初始化完了之后棍好,totalSuccess就等于3仗岸,然后就自動(dòng)調(diào)用start()方法,然后游戲就開始了借笙。

四.總結(jié)

本來(lái)一直都有打算研究一下這個(gè)程序的扒怖,因?yàn)楹芏嗟胤降胤轿疫€沒(méi)有搞清楚,不過(guò)因?yàn)閼卸柰涎拥R了业稼。經(jīng)過(guò)五六個(gè)小時(shí)的研究盗痒,終于寫成了這篇博客,對(duì)于我來(lái)說(shuō)收獲是非常大的低散,因?yàn)樽约褐耙矊戇^(guò)一個(gè)這樣的俯邓,不過(guò)功能也不全面還有很多bug,老師講的時(shí)候我也沒(méi)有完全聽懂熔号,經(jīng)過(guò)寫這篇博客稽鞭,我終于明白了這個(gè)項(xiàng)目的執(zhí)行過(guò)程。同時(shí)也希望這篇文章能對(duì)大家有所幫助引镊!加油k獭!弟头!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吩抓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子亮瓷,更是在濱河造成了極大的恐慌琴拧,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘱支,死亡現(xiàn)場(chǎng)離奇詭異蚓胸,居然都是意外死亡挣饥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門沛膳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扔枫,“玉大人,你說(shuō)我怎么就攤上這事锹安《碳觯” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵叹哭,是天一觀的道長(zhǎng)忍宋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)风罩,這世上最難降的妖魔是什么糠排? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮超升,結(jié)果婚禮上入宦,老公的妹妹穿的比我還像新娘。我一直安慰自己室琢,他們只是感情好乾闰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盈滴,像睡著了一般涯肩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巢钓,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天宽菜,我揣著相機(jī)與錄音,去河邊找鬼竿报。 笑死铅乡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烈菌。 我是一名探鬼主播阵幸,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼芽世!你這毒婦竟也來(lái)了挚赊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤济瓢,失蹤者是張志新(化名)和其女友劉穎荠割,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蔑鹦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年夺克,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚎朽。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铺纽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哟忍,到底是詐尸還是另有隱情狡门,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布锅很,位于F島的核電站其馏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏爆安。R本人自食惡果不足惜尝偎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹏控。 院中可真熱鬧,春花似錦肤寝、人聲如沸当辐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缘揪。三九已至,卻和暖如春义桂,著一層夾襖步出監(jiān)牢的瞬間找筝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工慷吊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袖裕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓溉瓶,卻偏偏與公主長(zhǎng)得像急鳄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子堰酿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容