接上上次的游戏选择,我们来接着对其中一个小游戏(欢乐比拼)进行实现
“欢乐比拼”比牌小游戏的游戏规则大致如下:
参与游戏的玩家 除当前玩家外还有三个匹配的玩家,开局首先选择桌号,对应下不同的底注,选完桌号后每位玩家将随机获取一张牌,每位玩家根据自己牌面的大小可依次选择操作(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
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类:
根据游戏玩法,玩家所具有的基本属性包括下面四个,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列表,都以私有化保护起来,外部要获取的时候是通过相应的方法来调用,实现了保护。
作为该程序的第三部分就实现到这里啦,下次再见!