From e9ea0df8a161e5d663057efb9b3d4e747b8e964e Mon Sep 17 00:00:00 2001 From: tangmingyou <234767776@qq.com> Date: Wed, 22 Feb 2023 18:00:51 +0800 Subject: [PATCH] limited texas optimize code --- api/poker.proto | 2 +- cmd/main.go | 31 ++- internal/game/actions.go | 274 ++++++++++++++++++++++++++ internal/game/hand.go | 10 +- internal/game/player.go | 2 +- internal/game/table.go | 68 ++++++- internal/service/table.go | 393 +++++++++++++++++++++++--------------- tool/collect/collect.go | 47 +++++ tool/collect/validator.go | 4 + 9 files changed, 658 insertions(+), 173 deletions(-) create mode 100644 internal/game/actions.go diff --git a/api/poker.proto b/api/poker.proto index 3bc259a..61f75eb 100644 --- a/api/poker.proto +++ b/api/poker.proto @@ -32,7 +32,7 @@ message ResSuccess { // 失败响应 message ResFail { - int32 code = 1; + int32 code = 1; // 401跳登录,402未加入牌桌,403挤号 string msg = 3; // 失败消息 } diff --git a/cmd/main.go b/cmd/main.go index 17f480c..d52b919 100644 --- a/cmd/main.go +++ b/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) } diff --git a/internal/game/actions.go b/internal/game/actions.go new file mode 100644 index 0000000..e3ab09a --- /dev/null +++ b/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) + } +} diff --git a/internal/game/hand.go b/internal/game/hand.go index f046136..19cc6cc 100644 --- a/internal/game/hand.go +++ b/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 } diff --git a/internal/game/player.go b/internal/game/player.go index 3099ae1..a4d165c 100644 --- a/internal/game/player.go +++ b/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 // 当前回合下注次数 diff --git a/internal/game/table.go b/internal/game/table.go index 042c75d..52cb803 100644 --- a/internal/game/table.go +++ b/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 } diff --git a/internal/service/table.go b/internal/service/table.go index bce2208..761068c 100644 --- a/internal/service/table.go +++ b/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) - } - } - - case 3: // All-In TODO 边池 - table.LastPosBetType = 3 - player.RoundBetTimes++ - table.LastPosBetChip = player.Chip - table.Chip += player.Chip - player.Chip = 0 - - table.LastPosBetChip = playerBettingChip - - 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 { - 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 + handler, err := table.PlayerBetting.GetBetHandler(msg.BetType) + if err != nil { + return &api.ResFail{Msg: err.Error()}, nil } - - // 第一轮只有未加注时大盲注可过牌,大盲注了不算只过牌 - player.RoundCheckRaiseOnly = msg.BetType == 5 && table.Stage != 2 - - // 通知下注结果 - if betNotice.Line1 != "" || betNotice.Line2 != "" { - player.GameTable.NoticeAllPlayer(betNotice) + res := handler(player, msg.BetChip) + if res == nil { + res = &api.ResSuccess{} } + // 记录操作 + table.PlayerBet(player, table.LastPosBetOp, table.LastPosBetChip) - // 下注完成:若还有玩家未下注、加注,下一玩家继续下注;若该轮下注完成,发牌开启下一轮下注 - 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 - //} + // 投注结束广播牌桌状态 + defer player.GameTable.NoticeGameFullStatus() - 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-- - } + // 判断回合是否结束 + if table.PlayerBetting.IsRoundOver(player, table.LastPosBetOp) { + // 开启下一轮 + err := table.RoundOver() + if err != nil { + return &api.ResFail{Code: 500, Msg: err.Error()}, nil } + return res, nil } // 查找下一位下注玩家 - table.SetNextPlayer(player.PosIndex) - - // 依赌场规则而异, - // 若所余筹码All-in后仍低于最低加注金额,则此注仅视为跟注(call)而不能被当成加注(raise),亦及该圈若未有其他人再加注,则原加注者(此例中的第一位牌手)仅可跟注补齐或盖牌,于此圈不可再行加注 - // 亦有少部分赌场规定All-in后大于最低加注额的一半以上,原加注者即可重新加注。 + 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) - return &api.ResSuccess{}, nil + return res, nil } + +// 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 +//} diff --git a/tool/collect/collect.go b/tool/collect/collect.go index 3684ef5..b1942a5 100644 --- a/tool/collect/collect.go +++ b/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 +} diff --git a/tool/collect/validator.go b/tool/collect/validator.go index 53b39bf..98a5d0d 100644 --- a/tool/collect/validator.go +++ b/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 +}