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

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

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

参与游戏的玩家 除当前玩家外还有三个匹配的玩家,开局首先选择桌号,对应下不同的底注,选完桌号后每位玩家将随机获取一张牌,每位玩家根据自己牌面的大小可依次选择操作(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列表,都以私有化保护起来,外部要获取的时候是通过相应的方法来调用,实现了保护。

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

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

推荐阅读更多精彩内容