面向对象程序练习三——欢乐比拼比牌小游戏准备

接上上次的游戏选择,我们来接着对其中一个小游戏(欢乐比拼)进行实现

“欢乐比拼”比牌小游戏的游戏规则大致如下:

参与游戏的玩家 除当前玩家外还有三个匹配的玩家,开局首先选择桌号,对应下不同的底注,选完桌号后每位玩家将随机获取一张牌,每位玩家根据自己牌面的大小可依次选择操作(1.下注 2.跟注 3.比牌 4.弃牌 5.all-in),最终牌面大的玩家获胜并赢得桌面金额,获得游戏胜利。

今天我们主要实现游戏的前期准备工作,话不多说,让我们开始~

项目实训——游戏准备(欢乐比拼)

玩家选择完游戏后即将进入小游戏开始玩,我们就假定玩家选择的是欢乐比拼小游戏。下面来看看这个小游戏将如何开始吧。

一、程序分析

一个游戏的开始将进行许多相关的准备活动。比如就玩家来说,需要匹配三名玩家,开始游戏时每位玩家要分发一张牌,以及与游戏规则相关的一系列参数初的始化等等。

(1)理清功能

这里我们就直接用东哥做好的思维导图,今天要做的部分主要是从欢乐比拼一直到玩家选择操作(展示不同选择列表)为止。

欢乐比拼

根据游戏规则我们可知,当当前有玩家选择了4.allin之后的玩家只有两个选择——4或五,因此要根据不同状态判断该显示什么操作列表

(2)找对象->抽类

从游戏大厅进入游戏——游戏大厅(GameCenter)、游戏的基类(IGame)
进入欢乐比拼小游戏——欢乐比拼(HappyPokerGame)
匹配游戏玩家——存放玩家基本属性(Palyer)、实现对所有玩家的管理(PalyerManger)
给每位玩家发牌——存放牌的基本属性(Poker、PokerSuit、PokerNumber)、实现对每张牌的管理(PokerManger)
将牌发给玩家——发牌操作(ExTools)
选择桌号、操作——提示玩家进行选择,展示列表(Console)
存放列表等常量——常量类(Constants)

(3)理清各个类间要实现的具体功能

由于该部分涉及到的功能、类较多,只进行主要简单的叙述

首先,在游戏中心玩家进行完选择操作后,游戏中心要启动对应的游戏对象,为了实现接承游戏对象 、避免直接应用欢乐游戏类,因此需要一个所有小游戏的抽象夫类(IGame)在游戏中心中,其有一个抽象方法是游戏开始。

开始游戏就进入到欢乐比拼(HappyPokerGame)中,先由当前玩家选择桌号(chooseTable),之后对每个玩家发牌(dealCards),在拓展工具类(ExTools)中实现该功能,准备完之后即游戏正式开始(startGame)

游戏正式开始,根据玩家当前处境显示对应操作列表(showAllInMenu or showNormalMenu)

(4)画时序图 / 画类图

注:该类图只画出本部分要实现的功能的方法,对于总程序来说不算完整


类图

二、写代码

根据时序图将各个类根据其实现的功能、属性归类,可分为以下几个包方便管理

分包管理

1.poker包:Poker-PokerManger-PokerSuit-PokerNumber
Poker包程序截图

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类:
因为在一局游戏终始终都是对一副牌进行操作,所以牌的初始化只需要进行一次,此除使用了单例方法来实现,将构造函数私有化并通过伴生对象里的方法来实现初始化,因为是懒加载,创建时才加载且只初始化一次。
该类中的getSomePoker实现一副牌中对指定数量牌的抽取的功能,getOnePoker实现将牌分发给各个玩家

class PokerManger private constructor(){
    private val pokersList= arrayListOf<Poker>()//所有的扑克
    init {
        //创建一副牌
        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)
//    获取随机数 0-牌数
        val poker=pokersList[index]
//    删除这张扑克
        pokersList.removeAt(index)
        return poker
    }

}

2.Player包:Player、PlayerManger
Player包工程截图:

Player包

Player类:
根据游戏玩法,玩家所具有的基本属性包括下面四个,dadi法方实现的是选择桌号扣除底牌的功能

class Player(val name: String, val id: Int){
    var money= DEFAULT_MONEY //金币数
    var isLive:Boolean = ONLINE //存活状态
    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初始化类似,一局游戏终始终是一开始匹配的人,所以也使用单例方法实现只初始化一次。placeBottomBet方法实现对当前卓上所有玩家下底注总和的记录

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类:实现发牌dealcards方法

package StudyDemo.utils

import StudyDemo.palyer.Player
import StudyDemo.poker.Poker

//顶层属性 顶层方法 全局 静态
fun dealPokersToPlayer(pokers:List<Poker>, players: ArrayList<Player>){
    for ((i,poker) in pokers.withIndex()){
        players[i].poker=poker
    }
}

Constants类中储存本部分用到的参数,包括一些列表的内容(下注列表、两种操作列表),getBoottomBet方法实现跟据玩家选择将对应的底注数目取出返回

const val DEFAULT_MONEY=1000
//提供固定玩家名字
val DEFAULT_PLAYER_NAME_LIAT= arrayOf("李四","张三","翠花","小王")


//扑克牌的点数与花色
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类中本步用到的内容:
展示所有玩家列表和当前玩家列表,以及两种操作菜单的显示

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("当前玩家: $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类:做选择,进入相应游戏
基于上次练习二,需要增添的部分如下

fun chooseGame(){
        showGameMenu()
        getChoice()
        game=HappyPokerGame()
        game.start()
    }

IGame类:所有游戏的基类,声明start()方法

abstract class IGame {
    abstract fun start()
}

最后就是来具体运用这些方法的HappyPokerGame类:

class HappyPokerGame : IGame() {
    //   灵魂参数  注意“时机”
    private var allInStarIndex= INVALID_NUM //allin玩家数量
    private var lastbet = 0 //记录上一位玩家下注金额
    private var palyerManger=PalyerManger.sharedManger //静态单例,全局静态
    private val pokerManger=PokerManger.sharedPokerManger
    private var tableMoney=0 //桌上的钱
    override fun start() {
         chooseTable()
         dealCards()
         startGame()
    }
//    选桌号,扣底注
    private fun chooseTable(){
//        显示列表
        shoeTableMenu()
//        选择对应桌号的金币
        "请选择桌号".showWithEnter()
        val boottomBet= getBoottomBet(getChoice()-1)
//        所有人下注 记录当前下注金币
        tableMoney=palyerManger.placeBottomBet(boottomBet)
        println("当前金币数:$tableMoney")
    }
//    发牌
    fun dealCards(){
        dealPokersToPlayer(pokerManger.getSomePoker(4),
            palyerManger.getPlayerList()
        )
    }
//    开启游戏
    fun startGame(){

    while (true){
        //    显示玩家列表
        showPlayerList(palyerManger.getPlayerList())
        //显示当前玩家
        showCurrentPlayerInfo(palyerManger.getPlayerWithIndex(currentPalyerIndex))
        println("当前金币数:$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
    }
}

选择完游戏后进行桌号选择、玩家匹配、发牌、根据玩家当前状态展示操作列表的运行截图:


该部分运行截图

三、总结

该部分实现的方法较多,相比起来类与类之间的关系就更复杂,代码量也增加了不少,以面向对象的思维来写程序,将功能都模块化,在一定程度上大大简洁化了一个多功能工程的实现。其中要注意的一些方面有:

特定的功能放在特定的包里,要尽量避免不该出现在此处的东西出现在了此处,如gamecenter类引入了游戏的基类IGame是合理的,而不是直接将HappyPokerGame类暴露于gamecenter里直接应用;再比如对于poker列表与player列表,都以私有化保护起来,外部要获取的时候是通过相应的方法来调用,实现了保护。

作为该程序的第三部分就实现到这里啦,下次再见!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容