Browse Source

limited texas optimize code

master
tangmingyou 4 years ago
parent
commit
e9ea0df8a1
  1. 2
      api/poker.proto
  2. 31
      cmd/main.go
  3. 274
      internal/game/actions.go
  4. 10
      internal/game/hand.go
  5. 2
      internal/game/player.go
  6. 68
      internal/game/table.go
  7. 373
      internal/service/table.go
  8. 47
      tool/collect/collect.go
  9. 4
      tool/collect/validator.go

2
api/poker.proto

@ -32,7 +32,7 @@ message ResSuccess {
//
message ResFail {
int32 code = 1;
int32 code = 1; // 401,402,403
string msg = 3; //
}

31
cmd/main.go

@ -21,18 +21,31 @@ func startServer() {
func main() {
startServer()
//hand := [2]*game.Card{
//public := [5]*game.Card{
// {Dot: game.Poker10, Suit: game.Club},
// {Dot: game.Poker6, Suit: game.Spade},
// {Dot: game.Poker7, Suit: game.Diamond},
// {Dot: game.Poker5, Suit: game.Heart},
// {Dot: game.PokerK, Suit: game.Diamond},
// {Dot: game.Poker10, Suit: game.Heart},
//}
//public := [5]*game.Card{
// {Dot: game.Poker4, Suit: game.Diamond},
//h1 := [2]*game.Card{
// {Dot: game.PokerA, Suit: game.Spade},
// {Dot: game.Poker5, Suit: game.Spade},
//}
//h2 := [2]*game.Card{
// {Dot: game.Poker6, Suit: game.Club},
// {Dot: game.Poker3, Suit: game.Diamond},
//}
//h3 := [2]*game.Card{
// {Dot: game.Poker9, Suit: game.Club},
// {Dot: game.PokerJ, Suit: game.Diamond},
// {Dot: game.Poker2, Suit: game.Diamond},
// {Dot: game.Poker2, Suit: game.Spade},
// {Dot: game.Poker10, Suit: game.Heart},
//}
//maxHand, err := game.AnalyzeMaxHand(hand, public)
//fmt.Println(err, maxHand)
//hand1, err := game.AnalyzeMaxHand(h1, public)
//hand2, err := game.AnalyzeMaxHand(h2, public)
//hand3, err := game.AnalyzeMaxHand(h3, public)
//fmt.Println(err, hand1)
//fmt.Println(err, hand2)
//fmt.Println(err, hand3)
}

274
internal/game/actions.go

@ -0,0 +1,274 @@
package game
import (
"errors"
"fmt"
"github.com/golang/protobuf/proto"
"texas-poker-bk/api"
"texas-poker-bk/tool/collect"
)
type PlayerBetting struct {
betHandlers map[int32]func(player *Player, betChip int32) proto.Message
SetNextPlayer func(roundStart bool, t *Table, current *Player)
IsRoundOver func(current *Player, currentBetOp int32) bool
}
// GetBetHandler 获取下注类型对应 handler
// betOp: 1跟注,2加注,3All-In,4弃牌,5过牌
func (betting *PlayerBetting) GetBetHandler(betOp int32) (func(player *Player, betChip int32) proto.Message, error) {
handler := betting.betHandlers[betOp]
if handler == nil {
return nil, errors.New(fmt.Sprintf("未知操作#%d", betOp))
}
return handler, nil
}
var (
LimitedBetting *PlayerBetting
)
// var LimitedActions map[int32]func(player *Player, betChip int32) proto.Message
func init() {
// 1跟注,2加注,3All-In,4弃牌,5过牌
LimitedActions := make(map[int32]func(player *Player, betChip int32) proto.Message, 7)
LimitedActions[1] = Call4Limited
LimitedActions[2] = Raise4Limited
LimitedActions[3] = AllIn4Limited
LimitedActions[4] = Discard4Limited
LimitedActions[5] = Check4Limited
LimitedBetting = &PlayerBetting{
betHandlers: LimitedActions,
SetNextPlayer: SetNextPlayer4Limited,
IsRoundOver: IsRoundOver4Limited,
}
}
func Call4Limited(player *Player, betChip int32) proto.Message {
if betChip > player.BetMin {
return &api.ResFail{Msg: fmt.Sprintf("投注金额大于跟注金额:%d", player.BetMin)}
}
return Raise4Limited(player, betChip)
}
// Raise4Limited 限注加注处理
func Raise4Limited(player *Player, betChip int32) proto.Message {
table := player.GameTable
if betChip < player.BetMin {
return &api.ResFail{Msg: fmt.Sprintf("小于当前最低投注额:%d", player.BetMin)}
}
if player.BetMax > 0 && betChip > player.BetMax {
return &api.ResFail{Msg: fmt.Sprintf("大于当前最大投注额:%d", player.BetMax)}
}
// 限注式加注3次判断
if table.TexasType == 1 && betChip > player.BetMin && table.RoundRaiseTimes >= 3 {
return &api.ResFail{Msg: "限加注3次,当前只能跟注"}
}
var raise bool // 是否加注
betNotice := &api.ResNoticePlayerLine{PlayerId: player.Id}
//if table.Stage == 2 && player.RoundBetTimes == 0 {
// // 如果是第一圈第一次大小盲注玩家下注,下注额算加上盲注
// switch player.PosIndex {
// case table.SmallBlindPos:
// playerBettingChip += table.SmallBlindChip
// case table.BigBlindPos:
// playerBettingChip += table.BigBlindChip
// }
//}
// 加注判断
raise = betChip > player.BetMin
if raise {
table.RoundRaiseTimes++
table.LastPosBetOp = 2
} else {
table.LastPosBetOp = 1
}
player.RoundBetTimes++
table.LastPosBetChip = betChip
table.Chip += betChip
player.Chip -= betChip
player.TotalBetChip += betChip
betNotice.Line1 = fmt.Sprintf("+%d", betChip)
if betChip == player.BetMin {
betNotice.Line2 = fmt.Sprintf("跟注%d", player.BetMin)
} else {
betNotice.Line2 = fmt.Sprintf("加注:%d", betChip-player.BetMin)
if player.BetMin > 0 {
betNotice.Line3 = fmt.Sprintf("跟注:%d", player.BetMin)
}
}
// 通知下注结果
table.NoticeAllPlayer(betNotice)
return &api.ResSuccess{}
}
func AllIn4Limited(player *Player, betChip int32) proto.Message {
return &api.ResFail{Msg: "限注牌桌不可AllIn"}
}
func Discard4Limited(player *Player, betChip int32) proto.Message {
table := player.GameTable
table.LastPosBetOp = 4
table.LastPosBetChip = 0
betNotice := &api.ResNoticePlayerLine{PlayerId: player.Id}
player.SetStatus(7)
betNotice.Line2 = "弃牌"
// 剩余玩家数为1则结束
leftPlayers := collect.Filter(player.GameTable.Players, func(i int, p *Player) bool {
return p != nil && p.Status != 7
})
if len(leftPlayers) == 1 {
err := table.RoundOver()
if err != nil {
return &api.ResFail{Code: 500, Msg: err.Error()}
}
player.GameTable.NoticeAllPlayer(betNotice)
return &api.ResSuccess{}
}
player.GameTable.NoticeAllPlayer(betNotice)
return nil
}
func Check4Limited(player *Player, betChip int32) proto.Message {
table := player.GameTable
table.LastPosBetOp = 5
table.LastPosBetChip = 0
betNotice := &api.ResNoticePlayerLine{PlayerId: player.Id}
player.RoundBetTimes++
betNotice.Line2 = "过牌"
// 第一轮只有未加注时大盲注可过牌,大盲注了不算只过牌
player.RoundCheckRaiseOnly = table.Stage != 2
player.GameTable.NoticeAllPlayer(betNotice)
return nil
}
// IsRoundOver4Limited 判断回合是否结束
func IsRoundOver4Limited(current *Player, currentBetOp int32) bool {
table := current.GameTable
// 当前玩家弃牌, 判断剩余玩家数1结束
if currentBetOp == 4 {
if 1 == len(table.HoldingCardPlayers()) {
return true
}
}
// 判断下注轮 和 下注金额
if collect.IsEmptyMap(table.RoundBetOps) ||
collect.IsEmptyMap(table.RoundBetChips) {
return false
}
// 当前玩家加注, 继续
if currentBetOp == 2 {
return false
}
var totalTemp int32 = -1 // 所有玩家投入相同筹码回合结束
for _, p := range table.Players {
if p == nil || p.Status == 7 {
continue
}
// 还有玩家未下注
if collect.IsEmptySlice(table.RoundBetOps[p.Id]) {
return false
}
// 计算玩家下注额
if totalTemp == -1 {
totalTemp = table.RoundBetTotal[p.Id]
continue
}
if totalTemp != table.RoundBetTotal[p.Id] {
return false
}
}
// 判断当前玩家和下一玩家 回合是否结束
nextPlayerPos := table.NextHoldingCardPlayerPos(current.PosIndex)
nextPlayer := table.Players[nextPlayerPos]
if table.Stage == 2 && nextPlayerPos == table.BigBlindPos &&
len(table.RoundBetOps[nextPlayer.Id]) == 1 { // 大盲注玩家还下注一次
return false
}
//// 投注金额相同
//currentBetTotal := table.RoundBetTotal[current.Id]
//if currentBetTotal == 0 {
// if currentBetOp == 5 && // 当前玩家过牌且下一位玩家已过牌
// collect.Tail(table.RoundBetOps[nextPlayer.Id], 0) == 5 {
// return true
// }
//
//} else if table.RoundBetTotal[nextPlayer.Id] == currentBetTotal {
// // 下一玩家已投入等量筹码
// return true
//}
//var total int32 = -1
//for uid, betTotal := range table.RoundBetTotal {
// player := table.GetPlayerById(uid)
// if player == nil || player.Status == 7 {
// continue
// }
// if total == -1 {
// total = betTotal
// continue
// }
// if total != betTotal {
// return false
// }
//}
// TODO 判断剩余牌手All-In情况
return true
}
func SetNextPlayer4Limited(roundStart bool, t *Table, current *Player) {
var nextPos int = t.SmallBlindPos
if !roundStart {
// 非回合开始
nextPos = t.NextHoldingCardPlayerPos(current.PosIndex)
} else {
// 回合开始
if t.Stage == 2 { // 第一轮由大盲注后一个玩家下注
nextPos = t.NextPlayerPos(t.BigBlindPos)
} else if t.Players[nextPos].Status == 7 { // 小盲注弃牌,其后第一个未弃牌玩家开始
nextPos = t.NextHoldingCardPlayerPos(nextPos)
}
}
// 重置其他玩家状态为等待
for _, p := range t.Players {
if p == nil || p.Status == 7 {
continue
}
if p.PosIndex != nextPos {
p.WaitBet()
}
}
// 下一玩家操作项和下注限额
nextP := t.Players[nextPos]
nextP.SetStatus(6)
nextP.BetOpts = []int32{4}
// 回合最大下注额 减 下一玩家总下注额
roundMaxBet := collect.Max(collect.MapValues(t.RoundBetTotal), 0)
nextP.BetMin = roundMaxBet - t.RoundBetTotal[nextP.Id]
// 第1,2回合跟注加注需和大盲注相同, 3,4回合两倍
nextP.BetMax = t.BigBlindChip * (t.Stage/2 + 1)
if nextP.BetMin == 0 { // 过牌
nextP.BetOpts = append(nextP.BetOpts, 5)
} else if nextP.BetMin > 0 { // 跟注
nextP.BetOpts = append(nextP.BetOpts, 1)
}
if nextP.BetMax > nextP.BetMin { // 加注
nextP.BetOpts = append(nextP.BetOpts, 2)
}
}

10
internal/game/hand.go

@ -16,7 +16,7 @@ type Hand struct {
func (h *Hand) Init(cards [5]*Card) *Hand {
SortCards(&cards)
// 牌型排序降序
face := GetCardFace(cards)
face := GetCardFace(&cards) // TODO 牌权重排序
// 计算牌型
switch true {
case isRoyalFlush(cards):
@ -147,12 +147,12 @@ func SortCards(cards *[5]*Card) {
// GetCardFace 分析相同点数的牌个数
// return 0:单牌个数 1:对子个数 2:三条个数 3四条个数
func GetCardFace(cards [5]*Card) [4]int {
func GetCardFace(cards *[5]*Card) [4]int {
var faceMap = make(map[PokerDot]int, 5)
for _, c := range cards {
faceMap[c.Dot]++
}
// 0:单牌个数 1:对子个数 2:三条个数 3四条个数
// 0:单牌个数 1:对子个数 2:三条个数 3四条个数 TODO 将对子三条四条排序到前面
var face = [4]int{}
for dot := range faceMap {
face[faceMap[dot]-1]++
@ -219,11 +219,13 @@ func isOnePair(face [4]int) bool {
}
// getHighCardPoint 高牌,返回散牌点数
// max = 4432 = 13<<8 + 13<<6 + 13<<4 + 13<<2 + 12
// example: (1<<8 + 1<<6 + 13<<4)对2+A < (2<<8 + 2<<6 + 1<<4)对3+2
func getHighCardPoint(cards [5]*Card) int {
totalPoint := 0
for i, c := range cards {
// 牌权重降序
totalPoint += int(c.Dot) << (4 - i)
totalPoint += int(c.Dot+1) << (8 - i*2)
}
return totalPoint
}

2
internal/game/player.go

@ -20,7 +20,7 @@ type Player struct {
Chip int32 // 玩家筹码
TotalBetChip int32 // 本次游戏已下注筹码
Status int32 // 状态: 1待准备开始,2已准备开始,3等待其他玩家动作,[4第一回合小盲注跟注,5待大盲注,5待小盲注,] 6待跟注,7已弃牌,8结算中
BetOpts []int32 // status=[4,6]时下注可操作按钮: 1跟注,2加注(-[0]+),3All-In,4弃牌,5过牌
BetOpts []int32 // status=[4,6]时下注可操作按钮: (-2大盲注,-1小盲注) 1跟注,2加注(-[0]+),3All-In,4弃牌,5过牌
BetMin int32 // 最低下注额
BetMax int32 // 最高下注额 -1不限
RoundBetTimes int32 // 当前回合下注次数

68
internal/game/table.go

@ -14,6 +14,8 @@ type Table struct {
GameTimes int32 // 游戏次数
Stage int32 // 游戏阶段: 1等待玩家准备;2游戏开始(自动扣大小盲注,发手牌,轮流下注); 3:发3张公共牌 4:发第四张公共牌,轮流下注;5:发第五张公共牌,轮流下注,7比牌结算,9已解散
PlayerBetting *PlayerBetting // 限注/不限制 规则处理
Chip int32 // 牌桌当前筹码
robotChip int32 // 牌桌当前机器人下注筹码
Dealer *Dealer // 发牌员
@ -30,13 +32,17 @@ type Table struct {
TexasType int32 // 德州扑克类型: (1限注德州扑克:你只能增加与大盲注相同的投注额; 2底池限制德州扑克:你只能增加当时台面最大额的投注额(已经完成所有投注); 3无限制德州扑克:你可在手持额度下,增加任何额度的投注额,如果你投入所有筹码,就是“全押”)
LimitInAmount int32 // 最低入场金额
LastPosBetType int32 // 上家下注类型(弃牌不记录跳过) -> player.BetOpts
LastPosBetOp int32 // 上家下注类型(弃牌不记录跳过) -> player.BetOpts
LastPosBetChip int32 // 上家下注金额
RoundRaiseTimes int32 // 该轮押注圈加注次数
RoundBetTotal map[int64]int32 // 该轮玩家投注金额
RoundBetChips map[int64][]int32 // 该轮玩家投注队列
RoundBetOps map[int64][]int32 // 该轮投注玩家选项队列
RoundBetLogs [4][2]map[int64][]int32 // 投注日志,回合结束可存到DB [[op,chip],[op,chip]]
Lock *sync.Mutex // 牌桌锁
PlayersLock *sync.Mutex
// TODO game logs7
}
// InitGameAndPlayerStatus 初始化桌面
@ -53,7 +59,14 @@ func (t *Table) InitGameAndPlayerStatus() {
} else {
t.Dealer.Init()
}
// 重置一些状态
t.Stage = 1
t.LastPosBetOp = 0
t.LastPosBetChip = 0
t.RoundRaiseTimes = 0
t.RoundBetOps = nil
t.RoundBetChips = nil
t.RoundBetTotal = nil
for _, p := range t.Players {
if p != nil {
@ -62,6 +75,38 @@ func (t *Table) InitGameAndPlayerStatus() {
}
}
func (t *Table) PlayerBet(player *Player, betOp int32, betChip int32) {
if t.RoundBetOps == nil {
t.RoundBetOps = make(map[int64][]int32)
}
if t.RoundBetChips == nil {
t.RoundBetChips = make(map[int64][]int32)
}
if t.RoundBetTotal == nil {
t.RoundBetTotal = make(map[int64]int32)
}
if t.RoundBetOps[player.Id] == nil {
t.RoundBetOps[player.Id] = []int32{}
}
if t.RoundBetChips[player.Id] == nil {
t.RoundBetChips[player.Id] = []int32{}
}
t.RoundBetOps[player.Id] = append(t.RoundBetOps[player.Id], betOp)
t.RoundBetChips[player.Id] = append(t.RoundBetChips[player.Id], betChip)
t.RoundBetTotal[player.Id] += betChip
//fmt.Println("betChip:", player.Id, betOp, betChip)
}
func (t *Table) GetPlayerById(id int64) *Player {
for _, p := range t.Players {
if p.Id == id {
return p
}
}
return nil
}
func (t *Table) PlayerCount() int32 {
var count int32 = 0
for _, p := range t.Players {
@ -222,6 +267,13 @@ func (t *Table) NoticeAllPlayer(message proto.Message) {
}
}
// HoldingCardPlayers 当前未弃牌玩家
func (t *Table) HoldingCardPlayers() []*Player {
return collect.Filter(t.Players, func(i int, p *Player) bool {
return p != nil && p.Status != 7
})
}
// SetNextPlayerWithRoundStart 回合开始, 设置下一个下注玩家
func (t *Table) SetNextPlayerWithRoundStart() {
nextPos := t.SmallBlindPos
@ -306,7 +358,7 @@ func (t *Table) SetNextPlayer(current int) {
nextP.BetMin = t.LastPosBetChip
nextP.BetOpts = []int32{4}
// 可过牌
if t.LastPosBetType == 5 {
if t.LastPosBetOp == 5 {
nextP.BetMin = t.BigBlindChip
nextP.BetOpts = append(nextP.BetOpts, 5)
} else {
@ -358,6 +410,8 @@ func (t *Table) SetNextPlayer(current int) {
// RoundOver 当前回合结束
func (t *Table) RoundOver() error {
// TODO 记录 t.RoundBetLogs[]
// 游戏结束,开牌结算
if t.Stage == 5 || 1 == len(collect.Filter(t.Players, func(i int, p *Player) bool {
return p != nil && p.Status != 7
@ -386,11 +440,17 @@ func (t *Table) RoundOver() error {
}
// 重置一些状态
t.LastPosBetOp = 0
t.LastPosBetChip = 0
t.RoundRaiseTimes = 0
t.RoundBetOps = nil
t.RoundBetChips = nil
t.RoundBetTotal = nil
// 由小盲注或小盲注后第一个未弃牌玩家开始
t.SetNextPlayerWithRoundStart()
t.PlayerBetting.SetNextPlayer(true, t, nil)
// t.SetNextPlayerWithRoundStart()
return nil
}

373
internal/service/table.go

@ -25,9 +25,6 @@ func HandleReqCreateTable(account *session.NetAccount, msg *api.ReqCreateTable)
if playerNum+robotNum <= 0 {
return &api.ResFail{Msg: "至少添加一个玩家或机器人"}, nil
}
if !collect.In(msg.TexasType, 1, 2, 3) {
return &api.ResFail{Msg: fmt.Sprintf("未知游戏类型%d", msg.TexasType)}, nil
}
if msg.BigBlind < 2 || msg.BigBlind > 200 || msg.BigBlind%2 == 1 {
return &api.ResFail{Msg: "大盲注金额需在2-200之间,且为偶数"}, nil
}
@ -48,6 +45,15 @@ func HandleReqCreateTable(account *session.NetAccount, msg *api.ReqCreateTable)
LimitInAmount: msg.BigBlind * 100,
TexasType: msg.TexasType,
}
// 游戏类型 对应 下注处理流程
switch msg.TexasType {
case 1:
table.PlayerBetting = game.LimitedBetting
case 2:
case 3:
default:
return &api.ResFail{Msg: fmt.Sprintf("未知游戏类型%d", msg.TexasType)}, nil
}
table.InitGameAndPlayerStatus()
// 房主 [0]
@ -135,16 +141,20 @@ func HandleReqJoinTable(account *session.NetAccount, msg *api.ReqJoinTable) (pro
if account.Player != nil {
return &api.ResFail{Msg: fmt.Sprintf("当前已加入#%d牌桌", account.Player.GameTable.TableNo)}, nil
}
table := store.LobbyTables[msg.TableNo]
table.Lock.Lock()
defer table.Lock.Unlock()
if table == nil {
return &api.ResFail{Msg: "牌桌不存在"}, nil
}
table.Lock.Lock()
defer table.Lock.Unlock()
if !collect.In(table.Stage, 1, 7) {
return &api.ResFail{Msg: "牌桌正在进行中"}, nil
}
if table.PlayerCount() == table.PlayerNum {
return &api.ResFail{Msg: "牌桌人数已满"}, nil
}
// 账户余额限制
if account.GetBalance() < table.LimitInAmount {
return &api.ResFail{Msg: fmt.Sprintf("余额小于入场金额[%d]", table.LimitInAmount)}, nil
@ -374,6 +384,9 @@ func HandleReqGameStart(player *game.Player, msg *api.ReqGameStart) (proto.Messa
table.Players[table.BigBlindPos].TotalBetChip += table.BigBlindChip
// 桌面筹码
table.Chip = table.BigBlindChip + table.SmallBlindChip
// 记录投注
table.PlayerBet(table.Players[table.SmallBlindPos], -1, table.SmallBlindChip)
table.PlayerBet(table.Players[table.BigBlindPos], -2, table.BigBlindChip)
// 回合下注次数
// table.Players[table.BigBlindPos].RoundBetTimes++
@ -409,7 +422,8 @@ func HandleReqGameStart(player *game.Player, msg *api.ReqGameStart) (proto.Messa
}
// 待大盲注位下一位玩家下注
table.SetNextPlayerWithRoundStart()
table.PlayerBetting.SetNextPlayer(true, table, nil)
// table.SetNextPlayerWithRoundStart()
// 广播牌桌状态
table.NoticeGameFullStatus()
@ -417,23 +431,8 @@ func HandleReqGameStart(player *game.Player, msg *api.ReqGameStart) (proto.Messa
}
// HandleReqBetting 下注
// 网易德州扑克:http://sports.163.com/special/poker_rule/?ivk_sa=1025883k
// 天天德州: http://game.people.com.cn/n/2014/0220/c40130-24413078.html
// wiki: https://zh.wikipedia.org/wiki/%E5%BE%B7%E5%B7%9E%E6%92%B2%E5%85%8B
// 加注:增加下注额。 2、一人结束行动后按顺时针方向下一玩家获得行动权,直到不再有人弃牌,且每人已向奖池投入相同注额。
// 德州扑克多名玩家ALL IN之后的筹码 如何分配?https://www.zhihu.com/question/32187371
// All-in技巧:https://bac88.net/%E5%BE%B7%E5%B7%9E%E6%92%B2%E5%85%8B%E6%8A%80%E5%B7%A7-all-in%E7%9A%84%E6%8A%80%E5%B7%A7/
// 起手牌组合概率:https://www.moshike.com/a/1011.html
// 各圈喊注将持续直至每个牌手都已为以下三种状态之一:
// 盖牌(fold),即舍弃并覆盖手中的牌,放弃已投入底池的筹码退出该局。
// 跟注(call)投入了与所有其他未盖牌的牌手等量的筹码。
// 全下(all-in),投入了尚余的全部筹码。
// TODO 过牌轮第一个加注玩家 需跟注次数,下注金额错误
func HandleReqBetting(player *game.Player, msg *api.ReqBetting) (proto.Message, error) {
player.Lock.Lock()
defer player.Lock.Unlock()
player.GameTable.Lock.Lock()
defer player.GameTable.Lock.Unlock()
if player.Status != 6 {
return &api.ResFail{Msg: "当前未轮到您下注"}, nil
}
@ -444,145 +443,231 @@ func HandleReqBetting(player *game.Player, msg *api.ReqBetting) (proto.Message,
return &api.ResFail{Msg: fmt.Sprintf("当前阶段不可下注#%d", msg.BetType)}, nil
}
// 投注结束广播牌桌状态
defer player.GameTable.NoticeGameFullStatus()
table := player.GameTable
var raise bool // 是否加注
playerBettingChip := msg.BetChip // 当前玩家加注/跟注金额
betNotice := &api.ResNoticePlayerLine{PlayerId: player.Id}
switch msg.BetType {
case 1: // 跟注
if msg.BetChip > player.BetMin {
return &api.ResFail{Msg: fmt.Sprintf("投注大于应跟注金额#%d", player.BetMin)}, nil
}
fallthrough
case 2: // 加注
//if table.LastPosBetChip < 0 {
// return &api.ResFail{Msg: "您当前只能All-In或弃牌"}, nil
//}
if msg.BetChip < player.BetMin {
return &api.ResFail{Msg: fmt.Sprintf("小于当前最低投注额#%d", player.BetMin)}, nil
}
if player.BetMax > 0 && msg.BetChip > player.BetMax {
return &api.ResFail{Msg: fmt.Sprintf("大于当前最大投注额#%d", player.BetMax)}, nil
}
// 限注式加注3次判断
if table.TexasType == 1 && msg.BetChip > player.BetMin && table.RoundRaiseTimes >= 3 {
return &api.ResFail{Msg: "该轮不可再加注"}, nil
}
if table.Stage == 2 && player.RoundBetTimes == 0 {
// 如果是第一圈第一次大小盲注玩家下注,下注额算加上盲注
switch player.PosIndex {
case table.SmallBlindPos:
playerBettingChip += table.SmallBlindChip
case table.BigBlindPos:
playerBettingChip += table.BigBlindChip
}
}
// 加注判断
raise = msg.BetChip > player.BetMin
if raise {
table.RoundRaiseTimes++
table.LastPosBetType = 2
} else {
table.LastPosBetType = 1
}
player.RoundBetTimes++
table.LastPosBetChip = playerBettingChip
table.Chip += msg.BetChip
player.Chip -= msg.BetChip
player.TotalBetChip += msg.BetChip
betNotice.Line1 = fmt.Sprintf("+%d", msg.BetChip)
if playerBettingChip == player.BetMin {
betNotice.Line2 = fmt.Sprintf("跟注%d", player.BetMin)
} else {
betNotice.Line2 = fmt.Sprintf("加注:%d", msg.BetChip-player.BetMin)
if player.BetMin > 0 {
betNotice.Line3 = fmt.Sprintf("跟注:%d", player.BetMin)
handler, err := table.PlayerBetting.GetBetHandler(msg.BetType)
if err != nil {
return &api.ResFail{Msg: err.Error()}, nil
}
res := handler(player, msg.BetChip)
if res == nil {
res = &api.ResSuccess{}
}
// 记录操作
table.PlayerBet(player, table.LastPosBetOp, table.LastPosBetChip)
case 3: // All-In TODO 边池
table.LastPosBetType = 3
player.RoundBetTimes++
table.LastPosBetChip = player.Chip
table.Chip += player.Chip
player.Chip = 0
table.LastPosBetChip = playerBettingChip
// 投注结束广播牌桌状态
defer player.GameTable.NoticeGameFullStatus()
case 4: // 弃牌
player.SetStatus(7)
betNotice.Line2 = "弃牌"
// 剩余玩家数为1则结束
leftPlayers := collect.Filter(player.GameTable.Players, func(i int, p *game.Player) bool {
return p != nil && p.Status != 7
})
if len(leftPlayers) == 1 {
// 判断回合是否结束
if table.PlayerBetting.IsRoundOver(player, table.LastPosBetOp) {
// 开启下一轮
err := table.RoundOver()
if err != nil {
return &api.ResFail{Code: 500, Msg: err.Error()}, nil
}
player.GameTable.NoticeAllPlayer(betNotice)
return &api.ResSuccess{}, nil
}
case 5: // 过牌
table.LastPosBetType = 5
player.RoundBetTimes++
betNotice.Line2 = "过牌"
default:
return &api.ResFail{Msg: fmt.Sprintf("未知操作#%d", msg.BetType)}, nil
return res, nil
}
// 第一轮只有未加注时大盲注可过牌,大盲注了不算只过牌
player.RoundCheckRaiseOnly = msg.BetType == 5 && table.Stage != 2
// 查找下一位下注玩家
table.PlayerBetting.SetNextPlayer(false, table, player)
//nextPlayerPos := table.NextHoldingCardPlayerPos(player.PosIndex)
//nextPlayer := table.Players[nextPlayerPos]
//if table.LastPosBetOp != 2 {
// // 当前玩家未加注, 判断是否结束本轮下注
// if nextPlayer.RoundBetTimes == player.RoundBetTimes {
// // 5 true -> next; !5 false -> next; !5 true -> continue
// if !nextPlayer.RoundCheckRaiseOnly || table.LastPosBetOp == 5 {
// // 开启下一轮
// err := table.RoundOver()
// if err != nil {
// return &api.ResFail{Code: 500, Msg: err.Error()}, nil
// }
// return res, nil
// } else {
// // 下个玩家为过牌玩家,当前玩家已下注
// nextPlayer.RoundBetTimes--
// }
// }
//}
//// 查找下一位下注玩家
//table.SetNextPlayer(player.PosIndex)
// 通知下注结果
if betNotice.Line1 != "" || betNotice.Line2 != "" {
player.GameTable.NoticeAllPlayer(betNotice)
return res, nil
}
// 下注完成:若还有玩家未下注、加注,下一玩家继续下注;若该轮下注完成,发牌开启下一轮下注
nextPlayerPos := table.NextHoldingCardPlayerPos(player.PosIndex)
nextPlayer := table.Players[nextPlayerPos]
//if nextPlayer.Id == player.Id {
// HandleReqBettingDep 下注
// 网易德州扑克:http://sports.163.com/special/poker_rule/?ivk_sa=1025883k
// 天天德州: http://game.people.com.cn/n/2014/0220/c40130-24413078.html
// wiki: https://zh.wikipedia.org/wiki/%E5%BE%B7%E5%B7%9E%E6%92%B2%E5%85%8B
// 加注:增加下注额。 2、一人结束行动后按顺时针方向下一玩家获得行动权,直到不再有人弃牌,且每人已向奖池投入相同注额。
// 德州扑克多名玩家ALL IN之后的筹码 如何分配?https://www.zhihu.com/question/32187371
// All-in技巧:https://bac88.net/%E5%BE%B7%E5%B7%9E%E6%92%B2%E5%85%8B%E6%8A%80%E5%B7%A7-all-in%E7%9A%84%E6%8A%80%E5%B7%A7/
// 起手牌组合概率:https://www.moshike.com/a/1011.html
// 各圈喊注将持续直至每个牌手都已为以下三种状态之一:
// 盖牌(fold),即舍弃并覆盖手中的牌,放弃已投入底池的筹码退出该局。
// 跟注(call)投入了与所有其他未盖牌的牌手等量的筹码。
// 全下(all-in),投入了尚余的全部筹码。
//func HandleReqBettingDep(player *game.Player, msg *api.ReqBetting) (proto.Message, error) {
// player.Lock.Lock()
// defer player.Lock.Unlock()
// player.GameTable.Lock.Lock()
// defer player.GameTable.Lock.Unlock()
//
// if player.Status != 6 {
// return &api.ResFail{Msg: "当前未轮到您下注"}, nil
// }
// if !collect.In(msg.BetType, player.BetOpts...) {
// return &api.ResFail{Msg: fmt.Sprintf("当前不可执行该操作#%d", msg.BetType)}, nil
// }
// if !collect.In(player.GameTable.Stage, 2, 3, 4, 5) {
// return &api.ResFail{Msg: fmt.Sprintf("当前阶段不可下注#%d", msg.BetType)}, nil
// }
//
// // 投注结束广播牌桌状态
// defer player.GameTable.NoticeGameFullStatus()
//
// table := player.GameTable
//
// var raise bool // 是否加注
// playerBettingChip := msg.BetChip // 当前玩家加注/跟注金额
// betNotice := &api.ResNoticePlayerLine{PlayerId: player.Id}
//
// switch msg.BetType {
// case 1: // 跟注
// if msg.BetChip > player.BetMin {
// return &api.ResFail{Msg: fmt.Sprintf("投注大于应跟注金额#%d", player.BetMin)}, nil
// }
// fallthrough
// case 2: // 加注
// //if table.LastPosBetChip < 0 {
// // return &api.ResFail{Msg: "您当前只能All-In或弃牌"}, nil
// //}
// if msg.BetChip < player.BetMin {
// return &api.ResFail{Msg: fmt.Sprintf("小于当前最低投注额#%d", player.BetMin)}, nil
// }
// if player.BetMax > 0 && msg.BetChip > player.BetMax {
// return &api.ResFail{Msg: fmt.Sprintf("大于当前最大投注额#%d", player.BetMax)}, nil
// }
// // 限注式加注3次判断
// if table.TexasType == 1 && msg.BetChip > player.BetMin && table.RoundRaiseTimes >= 3 {
// return &api.ResFail{Msg: "该轮不可再加注"}, nil
// }
// if table.Stage == 2 && player.RoundBetTimes == 0 {
// // 如果是第一圈第一次大小盲注玩家下注,下注额算加上盲注
// switch player.PosIndex {
// case table.SmallBlindPos:
// playerBettingChip += table.SmallBlindChip
// case table.BigBlindPos:
// playerBettingChip += table.BigBlindChip
// }
// }
// // 加注判断
// raise = msg.BetChip > player.BetMin
// if raise {
// table.RoundRaiseTimes++
// table.LastPosBetOp = 2
// } else {
// table.LastPosBetOp = 1
// }
// player.RoundBetTimes++
// table.LastPosBetChip = playerBettingChip
// table.Chip += msg.BetChip
// player.Chip -= msg.BetChip
// player.TotalBetChip += msg.BetChip
//
// betNotice.Line1 = fmt.Sprintf("+%d", msg.BetChip)
// if playerBettingChip == player.BetMin {
// betNotice.Line2 = fmt.Sprintf("跟注%d", player.BetMin)
// } else {
// betNotice.Line2 = fmt.Sprintf("加注:%d", msg.BetChip-player.BetMin)
// if player.BetMin > 0 {
// betNotice.Line3 = fmt.Sprintf("跟注:%d", player.BetMin)
// }
// }
// table.PlayerBet(player, table.LastPosBetOp, msg.BetChip)
//
// case 3: // All-In TODO 边池
// table.PlayerBet(player, 3, player.Chip)
// table.LastPosBetOp = 3
//
// player.RoundBetTimes++
// table.LastPosBetChip = player.Chip
// table.Chip += player.Chip
// player.Chip = 0
//
// table.LastPosBetChip = playerBettingChip
//
// case 4: // 弃牌
// table.PlayerBet(player, 4, 0)
// player.SetStatus(7)
// betNotice.Line2 = "弃牌"
// // 剩余玩家数为1则结束
// leftPlayers := collect.Filter(player.GameTable.Players, func(i int, p *game.Player) bool {
// return p != nil && p.Status != 7
// })
// if len(leftPlayers) == 1 {
// err := table.RoundOver()
// if err != nil {
// return &api.ResFail{Code: 500, Msg: err.Error()}, nil
// }
// player.GameTable.NoticeAllPlayer(betNotice)
// return &api.ResSuccess{}, nil
// }
// case 5: // 过牌
// table.PlayerBet(player, 5, 0)
// table.LastPosBetOp = 5
// player.RoundBetTimes++
// betNotice.Line2 = "过牌"
//
// default:
// return &api.ResFail{Msg: fmt.Sprintf("未知操作#%d", msg.BetType)}, nil
// }
//
// // 第一轮只有未加注时大盲注可过牌,大盲注了不算只过牌
// player.RoundCheckRaiseOnly = msg.BetType == 5 && table.Stage != 2
//
// // 通知下注结果
// if betNotice.Line1 != "" || betNotice.Line2 != "" {
// player.GameTable.NoticeAllPlayer(betNotice)
// }
//
// // 下注完成:若还有玩家未下注、加注,下一玩家继续下注;若该轮下注完成,发牌开启下一轮下注
// nextPlayerPos := table.NextHoldingCardPlayerPos(player.PosIndex)
// nextPlayer := table.Players[nextPlayerPos]
// //if nextPlayer.Id == player.Id {
// // // 开启下一轮
// // err := table.RoundOver()
// // if err != nil {
// // return &api.ResFail{Code: 500, Msg: err.Error()}, nil
// // }
// // return &api.ResSuccess{}, nil
// //}
//
// if !raise { // 判断该轮开始过牌玩家
// // 当前玩家未加注, 判断是否结束本轮下注
// if nextPlayer.RoundBetTimes == player.RoundBetTimes {
// // 5 true -> next; !5 false -> next; !5 true -> continue
// if !nextPlayer.RoundCheckRaiseOnly || table.LastPosBetOp == 5 {
// // 开启下一轮
// err := table.RoundOver()
// if err != nil {
// return &api.ResFail{Code: 500, Msg: err.Error()}, nil
// }
// return &api.ResSuccess{}, nil
// } else {
// // 下个玩家为过牌玩家,当前玩家已下注
// nextPlayer.RoundBetTimes--
// }
// }
// }
//
// // 查找下一位下注玩家
// table.SetNextPlayer(player.PosIndex)
//
// // 依赌场规则而异,
// // 若所余筹码All-in后仍低于最低加注金额,则此注仅视为跟注(call)而不能被当成加注(raise),亦及该圈若未有其他人再加注,则原加注者(此例中的第一位牌手)仅可跟注补齐或盖牌,于此圈不可再行加注
// // 亦有少部分赌场规定All-in后大于最低加注额的一半以上,原加注者即可重新加注。
//
// return &api.ResSuccess{}, nil
//}
if !raise { // 判断该轮开始过牌玩家
// 当前玩家未加注, 判断是否结束本轮下注
if nextPlayer.RoundBetTimes == player.RoundBetTimes {
// 5 true -> next; !5 false -> next; !5 true -> continue
if !nextPlayer.RoundCheckRaiseOnly || table.LastPosBetType == 5 {
// 开启下一轮
err := table.RoundOver()
if err != nil {
return &api.ResFail{Code: 500, Msg: err.Error()}, nil
}
return &api.ResSuccess{}, nil
} else {
// 下个玩家为过牌玩家,当前玩家已下注
nextPlayer.RoundBetTimes--
}
}
}
// 查找下一位下注玩家
table.SetNextPlayer(player.PosIndex)
// 依赌场规则而异,
// 若所余筹码All-in后仍低于最低加注金额,则此注仅视为跟注(call)而不能被当成加注(raise),亦及该圈若未有其他人再加注,则原加注者(此例中的第一位牌手)仅可跟注补齐或盖牌,于此圈不可再行加注
// 亦有少部分赌场规定All-in后大于最低加注额的一半以上,原加注者即可重新加注。
return &api.ResSuccess{}, nil
}

47
tool/collect/collect.go

@ -1,6 +1,9 @@
package collect
func In[T comparable](value T, values ...T) bool {
if values == nil || len(values) == 0 {
return false
}
for _, v := range values {
if value == v {
return true
@ -11,6 +14,9 @@ func In[T comparable](value T, values ...T) bool {
func Count[T any](slice []T, isCount func(int, T) bool) int {
count := 0
if slice == nil {
return count
}
for i, item := range slice {
if isCount(i, item) {
count++
@ -28,3 +34,44 @@ func Filter[T any](slice []T, predicate func(int, T) bool) []T {
}
return res
}
// Tail 获取切片尾部元素
// dv: 空切片默认值
func Tail[T any](slice []T, dv T) T {
if slice == nil || len(slice) == 0 {
return dv
}
return slice[len(slice)-1]
}
func MapValues[K comparable, V any](kvs map[K]V) []V {
var values []V
if kvs == nil || len(kvs) == 0 {
return values
}
for _, v := range kvs {
values = append(values, v)
}
return values
}
func Sum[T int32 | int64 | int](nums []T) T {
var sum T = 0
for _, num := range nums {
sum += num
}
return sum
}
func Max[T int32 | int64 | int](nums []T, dv T) T {
if nums == nil || len(nums) == 0 {
return dv
}
var max T = nums[0]
for i := 1; i < len(nums); i++ {
if nums[i] > max {
max = nums[i]
}
}
return max
}

4
tool/collect/validator.go

@ -8,3 +8,7 @@ func IsEmptySlice[T any](slice []T) bool {
func IsNotEmptySlice[T any](slice []T) bool {
return !IsEmptySlice(slice)
}
func IsEmptyMap[K comparable, V any](kvs map[K]V) bool {
return kvs == nil || len(kvs) == 0
}

Loading…
Cancel
Save