iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 10
1
Modern Web

三十天路邊賭場上線了!系列 第 10

DAY10 百家樂遊戲核心、牌局生成

前言

目前大致已經構思好我們的架構了!那關鍵就是遊戲,因此我們今天就來做百家樂的遊戲核心吧!需要了解的當然就是百家樂遊戲規則囉!
百家樂wiki
百家樂遊戲規則

遊戲規則

首先說明百家樂有莊閒兩家,那簡易規則就是比大小,但是為了確保勝率,有一些奇怪規則,先說明當牌得到10 J Q K都是歸0點,其他則是補牌規則比較複雜。以下圖片可以大致知道補牌規則。
https://ithelp.ithome.com.tw/upload/images/20190911/20109783aHvZk3D6l4.png

上面半部是補牌細節,下半部是補牌總表,莊家部分點數會看閒家點數進行補牌,所以才有下張補牌總表。

下注區域

下注區域分為這八種,分別為莊、閒、莊對、閒對、莊天王、閒天王、平、平對。
https://ithelp.ithome.com.tw/upload/images/20190911/20109783RdlAyzpmkw.png

  • 莊: 莊家贏
  • 閒: 閒家瑩
  • 莊對: 莊家兩張一樣點數
  • 閒對: 閒家兩張一樣點數
  • 莊天王: 裝家點數大於八點
  • 閒天王: 閒家點數大於八點
  • 平:兩方點數相同
  • 平對:兩方點數相同且各為對子

以上有可能同時發生,像是莊雖然贏,但是閒是對子,因此兩區域都判定為贏。

賠率水錢

以上面那張桌面來看,像是莊賠率即為1:2,也就是你丟1000可以拿回2000且賺1000,某些高賠率的也就是因為比較難出現,那就是看運氣了!
另外在外面娛樂城要注意有水錢就是手續費的感覺或是傭金,但因為我們是有丟籌碼的,水錢會造成有零碎的數字,非常難計算,所以就作罷,畢竟已經是電子娛樂了,小手段已經可以使出夠多了還要抽水錢,太惡劣了!

遊戲核心規劃

一般牌組都是4-8副牌下去洗,所以我們要準備牌組、洗牌、發牌、計算輸贏、計算玩家輸贏結果。因為我希望我可以記錄總彩池,且不希望我還要透過資料去算,我會新增兩個部分。

core

  • Game 這一方法裡面會創建Round,Game意旨整場遊戲。
  • Round 這是每一局的運算跟結果,Round意旨是每次的局。
    lib
  • preparePoker 這一方法會產生牌組,我們放入牌組A-K、牌組對應點數、牌組幾副。
  • fanPi 傳入牌組、補牌規則,這一方法會幫我們傳出牌局結果。
  • grabRandomPoker 丟入牌組,回傳一隨機牌。
  • shouldBankerSupplyPoker 傳入規則,確定是否要補牌。
  • shouldPlayerSupplyPoker 傳入規則,確定是否要補牌。
  • calcPokerPoint 計算牌局結果點數。
  • timeClock 計時器。

遊戲核心

我們先來看我們的Round,就是每一局的產生牌局的方法。其實都是call lib內的方法派牌。

var { shallowObject } = require('../utils')
/**
 * 引入
 */

var Round = function () {
  this.pokerList = null
  this.timeClock = null
  this.notify = null
  this.playerSupplyRule = null
  this.bankererSupplyRule = null
  this.fanPi = null
  this.odds = null
  this.roundTime = 0
  this.roundPool = {}
  this.users = {}
  this.state = 0 // 0 不可投注 1 可投注
  this.data = {}

  var _completeCbs = []
  var _changeCbs = []

  this.initPokerList = function (pokerList) { this.pokerList = pokerList }
  this.initRoundTime = function (time) { this.roundTime = time }
  this.initTimeClock = function (timeClock) { this.timeClock = timeClock() }
  this.initNotify = function (notify) { this.notify = notify }
  this.initPlayerSupplyRule = function (playerSupplyRule) { this.playerSupplyRule = playerSupplyRule }
  this.initBankererSupplyRule = function (bankererSupplyRule) { this.bankererSupplyRule = bankererSupplyRule }
  this.initFanPi = function (fanPi) { this.fanPi = fanPi }
  this.initOdds = function (odds) { this.odds = odds}

  this.checkInit = function () {
    // 省略太多了!
    // 檢查初始化的都有沒有進來,方便抽換。
  }
  this.onComplete = function (cb) { _completeCbs.push(cb) }
  this.onChange = function (cb) { _changeCbs.push(cb) }

  var emitComplete = function () {
    this.state = 0
    setTimeout(() => { this.fanPiProcess() }, 1000)
    // 倒數開始發牌
    setTimeout(() => { this.calcResultProcess() }, 2000)
    // 倒數開始計算輸贏結果
    setTimeout(() => { _completeCbs.map(e => e(this.data)) }, 3000)
    // 完成通知掛載的
  }

  var emitChange = function (c) { _changeCbs.map(e => e(c)) }

  this.startCountdown = function () {
    this.state = 1
    this.timeClock.onComplete(() => emitComplete.bind(this)())
    this.timeClock.onChange(c => { emitChange.bind(this)(c) })
    this.timeClock.start(this.roundTime)
    // 計時器開始計時,變化時都會emitChange讓外部可以知道內部情況,並發送訊息。
    // 完成十會通知emitComplete
  }

  this.userJoin = function (user) {
    this.users[user.id] = user
    // 使用者進到這個牌局
  }

  this.userBetout = function (user) {
   // 省略太多了!
   // 使用者下注
  }

  this.fanPiProcess = function () {
   // 省略太多了!
   // 翻牌!得到牌局!
  }

  this.calcResultProcess = function () {
    // 省略 太多了! 
    // 計算牌局結果,輸贏。
  }

  this.start = function () {
    this.checkInit()
    this.startCountdown()
  }
}
module.exports = Round

再來看Game!

var Game = function () {
  // Round
  this.roundTemplate = null
  this.round = null
  this.pokerList = null
  this.notify = null
  this.timeClock = null
  this.playerSupplyRule = null
  this.bankererSupplyRule = null
  this.fanPi = null

  // 牌組
  this.pokerList = null
  this.notify = null

  // 內部game data
  this.pool = {}
  this.userCount = 0
  this.state = 0 // 0等於暫停 1等於開始

  this.initRound = function (roundtmp) { this.roundTemplate = roundtmp }

  this.initPokerList = function (pokerList) { this.pokerList = pokerList }

  this.initNotify = function (notify) { this.notify = notify }

  this.initTimeClock = function (timeClock) { this.timeClock = timeClock }

  this.initPlayerSupplyRule = function (playerSupplyRule) { this.playerSupplyRule = playerSupplyRule }

  this.initBankererSupplyRule = function (bankererSupplyRule) { this.bankererSupplyRule = bankererSupplyRule }

  this.initFanPi = function (fanPi) { this.fanPi = fanPi }

  this.initOdds = function (odds) { this.odds = odds }

  this.checkInit = function () {
    // 檢查初始化
  }

  this.userJoin = function (user) {
    try {
      this.userCount++
      this.reStartCheck()
      this.round.userJoin(user)
    } catch (err) {
      user.emit('MSG', { type: 'RES_BET_JOIN', msg: err })
    }
  }

  this.userBetout = function (user) {
    try {
      this.round.userBetout(user)
    } catch (err) {
      user.emit('MSG', { type: 'RES_BET_OUT', msg: err })
    }
  }

  this.startGame = function () {
    this.checkInit()
    this.startNewRound()
  }

  this.startNewRound = function () {
    if (this.state) return
    this.state = 1
    this.round = null
    this.round = new this.roundTemplate()
    this.round.initPokerList(this.pokerList)
    this.round.initTimeClock(this.timeClock)
    this.round.initNotify(this.notify)
    this.round.initPlayerSupplyRule(this.playerSupplyRule)
    this.round.initBankererSupplyRule(this.bankererSupplyRule)
    this.round.initFanPi(this.fanPi)
    this.round.initOdds(this.odds)

    this.round.initRoundTime(1000)
    this.round.onChange((c) => { console.log('剩下' + c) })
    this.round.onComplete((res) => {
      console.log('倒數結束')
      console.log(res.users_result)
      this.userCount = 0
      this.state = 0
      setTimeout(this.reStartCheck.bind(this), 1000)
    })
    this.round.start()
  }

  this.reStartCheck = function () {
  // 如果加入玩家不為0,就繼續開牌,為0就不開牌,代表沒人
    if (this.userCount !== 0) {
      this.startNewRound()
    }
  }
}

module.exports = Game

其實兩個差不多,會多新增一個Game的原因是因為,我想要紀錄整個彩池的狀況,且全部封裝在這core內,只要我掛上去監聽就可以知道內容,並傳送訊息給使用者。

判斷補牌設定

其實判斷補牌是由設定檔來判斷,只是簡單的對應而已,以下為設定規則檔,很像上半部的圖片吧!

{
  "time": 30,
  "banker_rule": { 
    "9": ["S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"],
    "8": ["S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"],
    "7": ["S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"],
    "6": ["S", "S", "S", "S", "S", "S", "H", "H", "S", "S", "S"],
    "5": ["S", "S", "S", "S", "H", "H", "H", "H", "S", "S", "H"],
    "4": ["S", "S", "H", "H", "H", "H", "H", "H", "S", "S", "H"],
    "3": ["H", "H", "H", "H", "H", "H", "H", "H", "S", "H", "H"],
    "2": ["H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"],
    "1": ["H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"],
    "0": ["H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"]
  },
  "player_rule": {
    "0": "H",
    "1": "H",
    "2": "H",
    "3": "H",
    "4": "H",
    "5": "H",
    "6": "S",
    "7": "S",
    "8": "S",
    "9": "S"
  },
  "odds": {
    "banker": 2,
    "player": 2,
    "bankerking": 3,
    "playerking": 3,
    "tie": 9,
    "tiepair": 33,
    "bpair": 12,
    "ppair": 12
  },
  "betResult": {
    "banker": "莊",
    "player": "閒",
    "bankerking": "莊天王",
    "playerking": "閒天王",
    "tie": "平",
    "tiepair": "平對",
    "bpair": "莊對",
    "ppair": "閒對"
  }
}

後記

這是大概的遊戲內容,那複雜的地方在於我和前端溝通的時機點,以及斷線等等狀態的同步問題,其實有時候在思考前端的價值所在,我現在感受得很清楚,串API、串SOCKET真的很辛苦,但這可能就是前端的價值所在吧?想參考內容的可以到Github看看哦,可能還沒同步上去,但可以關注!

參考

GITHUB
百家樂wiki
百家樂遊戲規則


上一篇
DAY09 遊戲平台、站台API、會員系統
下一篇
DAY11 遊戲畫面、籌碼選單
系列文
三十天路邊賭場上線了!30

尚未有邦友留言

立即登入留言