接上上次的游戲選擇,我們來接著對其中一個(gè)小游戲(歡樂比拼)進(jìn)行實(shí)現(xiàn)
“歡樂比拼”比牌小游戲的游戲規(guī)則大致如下:
參與游戲的玩家 除當(dāng)前玩家外還有三個(gè)匹配的玩家,開局首先選擇桌號,對應(yīng)下不同的底注,選完桌號后每位玩家將隨機(jī)獲取一張牌,每位玩家根據(jù)自己牌面的大小可依次選擇操作(1.下注 2.跟注 3.比牌 4.棄牌 5.all-in),最終牌面大的玩家獲勝并贏得桌面金額,獲得游戲勝利。
今天我們主要實(shí)現(xiàn)游戲的前期準(zhǔn)備工作,話不多說,讓我們開始~
項(xiàng)目實(shí)訓(xùn)——游戲準(zhǔn)備(歡樂比拼)
玩家選擇完游戲后即將進(jìn)入小游戲開始玩,我們就假定玩家選擇的是歡樂比拼小游戲。下面來看看這個(gè)小游戲?qū)⑷绾伍_始吧。
一、程序分析
一個(gè)游戲的開始將進(jìn)行許多相關(guān)的準(zhǔn)備活動(dòng)。比如就玩家來說,需要匹配三名玩家,開始游戲時(shí)每位玩家要分發(fā)一張牌,以及與游戲規(guī)則相關(guān)的一系列參數(shù)初的始化等等。
(1)理清功能
這里我們就直接用東哥做好的思維導(dǎo)圖,今天要做的部分主要是從歡樂比拼一直到玩家選擇操作(展示不同選擇列表)為止。

根據(jù)游戲規(guī)則我們可知,當(dāng)當(dāng)前有玩家選擇了4.allin之后的玩家只有兩個(gè)選擇——4或五,因此要根據(jù)不同狀態(tài)判斷該顯示什么操作列表
(2)找對象->抽類
從游戲大廳進(jìn)入游戲——游戲大廳(GameCenter)、游戲的基類(IGame)
進(jìn)入歡樂比拼小游戲——?dú)g樂比拼(HappyPokerGame)
匹配游戲玩家——存放玩家基本屬性(Palyer)、實(shí)現(xiàn)對所有玩家的管理(PalyerManger)
給每位玩家發(fā)牌——存放牌的基本屬性(Poker、PokerSuit、PokerNumber)、實(shí)現(xiàn)對每張牌的管理(PokerManger)
將牌發(fā)給玩家——發(fā)牌操作(ExTools)
選擇桌號、操作——提示玩家進(jìn)行選擇,展示列表(Console)
存放列表等常量——常量類(Constants)
(3)理清各個(gè)類間要實(shí)現(xiàn)的具體功能
由于該部分涉及到的功能、類較多,只進(jìn)行主要簡單的敘述
首先,在游戲中心玩家進(jìn)行完選擇操作后,游戲中心要啟動(dòng)對應(yīng)的游戲?qū)ο?,為了?shí)現(xiàn)接承游戲?qū)ο?、避免直接應(yīng)用歡樂游戲類,因此需要一個(gè)所有小游戲的抽象夫類(IGame)在游戲中心中,其有一個(gè)抽象方法是游戲開始。
開始游戲就進(jìn)入到歡樂比拼(HappyPokerGame)中,先由當(dāng)前玩家選擇桌號(chooseTable),之后對每個(gè)玩家發(fā)牌(dealCards),在拓展工具類(ExTools)中實(shí)現(xiàn)該功能,準(zhǔn)備完之后即游戲正式開始(startGame)
游戲正式開始,根據(jù)玩家當(dāng)前處境顯示對應(yīng)操作列表(showAllInMenu or showNormalMenu)
(4)畫時(shí)序圖 / 畫類圖
注:該類圖只畫出本部分要實(shí)現(xiàn)的功能的方法,對于總程序來說不算完整

二、寫代碼
根據(jù)時(shí)序圖將各個(gè)類根據(jù)其實(shí)現(xiàn)的功能、屬性歸類,可分為以下幾個(gè)包方便管理

1.poker包:Poker-PokerManger-PokerSuit-PokerNumber

PokerSuit和PokerNumber類:
package StudyDemo.poker
class PokerNumber(val number: String, val tag:Int) {
override fun toString(): String {
return number
}
}
//////////////////////////////////////////////////////
package StudyDemo.poker
class PokerSuit(val suit:String,val tag:Int){
override fun toString(): String {
return suit
}
}
Poker類
class Poker (val pokerNumber:PokerNumber,val pokerSuit:PokerSuit){
override fun toString(): String {
return "${pokerNumber}${pokerSuit}"
}
}
PokerManger類:
因?yàn)樵谝痪钟螒蚪K始終都是對一副牌進(jìn)行操作,所以牌的初始化只需要進(jìn)行一次,此除使用了單例方法來實(shí)現(xiàn),將構(gòu)造函數(shù)私有化并通過伴生對象里的方法來實(shí)現(xiàn)初始化,因?yàn)槭菓屑虞d,創(chuàng)建時(shí)才加載且只初始化一次。
該類中的getSomePoker實(shí)現(xiàn)一副牌中對指定數(shù)量牌的抽取的功能,getOnePoker實(shí)現(xiàn)將牌分發(fā)給各個(gè)玩家
class PokerManger private constructor(){
private val pokersList= arrayListOf<Poker>()//所有的撲克
init {
//創(chuàng)建一副牌
for ((i,number) in POKERNUMBER_LISR.withIndex()){
for ((j,suit) in POKERSUIT_LIST.withIndex()){
pokersList.add(
Poker(PokerNumber(number,i), PokerSuit(suit,j))
)
}
}
}
companion object{
val sharedPokerManger:PokerManger by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
PokerManger()
}
}
//獲取幾張牌
fun getSomePoker(count:Int):List<Poker>{
val pokers= arrayListOf<Poker>()
for (i in 1..count){
pokers.add(getOnePoker())
}
return pokers
}
// 獲取一張撲克
private fun getOnePoker(): Poker {
val index= Random.nextInt(pokersList.size)
// 獲取隨機(jī)數(shù) 0-牌數(shù)
val poker=pokersList[index]
// 刪除這張撲克
pokersList.removeAt(index)
return poker
}
}
2.Player包:Player、PlayerManger
Player包工程截圖:

Player類:
根據(jù)游戲玩法,玩家所具有的基本屬性包括下面四個(gè),dadi法方實(shí)現(xiàn)的是選擇桌號扣除底牌的功能
class Player(val name: String, val id: Int){
var money= DEFAULT_MONEY //金幣數(shù)
var isLive:Boolean = ONLINE //存活狀態(tài)
lateinit var poker:Poker //手上的牌
var lastBet=0//上一次下注的金幣
//
override fun toString(): String {
val flag=if (isLive== ONLINE)"?" else "?"
return "NO$id.$name-$money-$poker-$flag"
}
fun dadi(bet:Int){
money-=bet
}
}
PlayerManger類:
該處與PokerManger初始化類似,一局游戲終始終是一開始匹配的人,所以也使用單例方法實(shí)現(xiàn)只初始化一次。placeBottomBet方法實(shí)現(xiàn)對當(dāng)前卓上所有玩家下底注總和的記錄
class PalyerManger {
private var palyersList= arrayListOf<Player>()//所有玩家
init {
//初始化玩家信息
for ((i,name) in DEFAULT_PLAYER_NAME_LIAT.withIndex()){
palyersList.add(Player(name,i+1))
}
}
companion object{
// 單例對象
val sharedManger:PalyerManger by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
PalyerManger()
}
}
// 所有人打底,返回所有錢
fun placeBottomBet(bet:Int):Int{
for (player in palyersList){
player.dadi(bet)
}
return palyersList.size*bet
}
//獲取所有玩家信息
fun getPlayerList()=palyersList
}
3.utils工具類包:Console、Constent、ExTools

ExTools類:實(shí)現(xiàn)發(fā)牌dealcards方法
package StudyDemo.utils
import StudyDemo.palyer.Player
import StudyDemo.poker.Poker
//頂層屬性 頂層方法 全局 靜態(tài)
fun dealPokersToPlayer(pokers:List<Poker>, players: ArrayList<Player>){
for ((i,poker) in pokers.withIndex()){
players[i].poker=poker
}
}
Constants類中儲存本部分用到的參數(shù),包括一些列表的內(nèi)容(下注列表、兩種操作列表),getBoottomBet方法實(shí)現(xiàn)跟據(jù)玩家選擇將對應(yīng)的底注數(shù)目取出返回
const val DEFAULT_MONEY=1000
//提供固定玩家名字
val DEFAULT_PLAYER_NAME_LIAT= arrayOf("李四","張三","翠花","小王")
//撲克牌的點(diǎn)數(shù)與花色
val POKERNUMBER_LISR= arrayOf("2","3","4","5","6","7","8","9","10","J","Q","K","A")
val POKERSUIT_LIST= arrayOf("?","?","?","?")
//游戲桌列表
val TABLE_LIST= arrayOf("50","100","200","500")
fun getBoottomBet(index:Int):Int= TABLE_LIST[index].toInt()
//玩家操作列表 all-in菜單列表
val ALL_IN_BET_MENU= arrayOf("All-in","棄牌")
val NORMAL_BET_MENU= arrayOf("下注","跟注","棄牌","比牌","All-in")
Console類中本步用到的內(nèi)容:
展示所有玩家列表和當(dāng)前玩家列表,以及兩種操作菜單的顯示
fun getChoice():Int{
while (true){
"請選擇:".show()
try {
val choice=readLine()!!.toInt()
if (choice in 1..currentMenuList.size){
return choice
}
}catch (e:java.lang.Exception){
"輸入不合法 ".show()
}
}
}
//顯示桌號列表
fun shoeTableMenu(){
showLineStar()
currentMenuList= TABLE_LIST
showMenu(TABLE_LIST)
showLineStar()
}
//展示玩家信息
fun showPlayerList(players:List<Player>){
showLineStar()
players.forEach{
println(it)
}
showLineStar()
}
fun showCurrentPlayerInfo(palyer: Player){
println("當(dāng)前玩家: $palyer")
}
//重要的東西不暴露
//顯示all-in操作列表
fun showAllInMenu(){
showLineStar()
currentMenuList= ALL_IN_BET_MENU
showMenu(ALL_IN_BET_MENU)
showLineStar()
}
//顯示正常列表
fun showNormalMenu(){
showLineStar()
currentMenuList= NORMAL_BET_MENU
showMenu(NORMAL_BET_MENU)
showLineStar()
}
4.game包:GameCenter、IGame、HappyPokerGame
GameCenter類:做選擇,進(jìn)入相應(yīng)游戲
基于上次練習(xí)二,需要增添的部分如下
fun chooseGame(){
showGameMenu()
getChoice()
game=HappyPokerGame()
game.start()
}
IGame類:所有游戲的基類,聲明start()方法
abstract class IGame {
abstract fun start()
}
最后就是來具體運(yùn)用這些方法的HappyPokerGame類:
class HappyPokerGame : IGame() {
// 靈魂參數(shù) 注意“時(shí)機(jī)”
private var allInStarIndex= INVALID_NUM //allin玩家數(shù)量
private var lastbet = 0 //記錄上一位玩家下注金額
private var palyerManger=PalyerManger.sharedManger //靜態(tài)單例,全局靜態(tài)
private val pokerManger=PokerManger.sharedPokerManger
private var tableMoney=0 //桌上的錢
override fun start() {
chooseTable()
dealCards()
startGame()
}
// 選桌號,扣底注
private fun chooseTable(){
// 顯示列表
shoeTableMenu()
// 選擇對應(yīng)桌號的金幣
"請選擇桌號".showWithEnter()
val boottomBet= getBoottomBet(getChoice()-1)
// 所有人下注 記錄當(dāng)前下注金幣
tableMoney=palyerManger.placeBottomBet(boottomBet)
println("當(dāng)前金幣數(shù):$tableMoney")
}
// 發(fā)牌
fun dealCards(){
dealPokersToPlayer(pokerManger.getSomePoker(4),
palyerManger.getPlayerList()
)
}
// 開啟游戲
fun startGame(){
while (true){
// 顯示玩家列表
showPlayerList(palyerManger.getPlayerList())
//顯示當(dāng)前玩家
showCurrentPlayerInfo(palyerManger.getPlayerWithIndex(currentPalyerIndex))
println("當(dāng)前金幣數(shù):$tableMoney")
//顯示操作菜單前先判斷是不是all-in或者金幣不足
if (isAllIn() || !palyerManger.isMoneyEnough(currentPalyerIndex,lastbet)){
//顯示allin菜單
showAllInMenu()
}else{
//顯示全菜單
showNormalMenu()
}
}
//判斷是不是all-in
// fun isAllIn()=allInStarIndex != INVALIDE_NUM
fun isAllIn():Boolean{
return allInStarIndex!= INVALID_NUM
}
}
選擇完游戲后進(jìn)行桌號選擇、玩家匹配、發(fā)牌、根據(jù)玩家當(dāng)前狀態(tài)展示操作列表的運(yùn)行截圖:

三、總結(jié)
該部分實(shí)現(xiàn)的方法較多,相比起來類與類之間的關(guān)系就更復(fù)雜,代碼量也增加了不少,以面向?qū)ο蟮乃季S來寫程序,將功能都模塊化,在一定程度上大大簡潔化了一個(gè)多功能工程的實(shí)現(xiàn)。其中要注意的一些方面有:
特定的功能放在特定的包里,要盡量避免不該出現(xiàn)在此處的東西出現(xiàn)在了此處,如gamecenter類引入了游戲的基類IGame是合理的,而不是直接將HappyPokerGame類暴露于gamecenter里直接應(yīng)用;再比如對于poker列表與player列表,都以私有化保護(hù)起來,外部要獲取的時(shí)候是通過相應(yīng)的方法來調(diào)用,實(shí)現(xiàn)了保護(hù)。
作為該程序的第三部分就實(shí)現(xiàn)到這里啦,下次再見!