package game import ( "errors" "sonet/pkg/utils/collect" "sonet/pkg/utils/state" "sort" ) // BlankHandTiles 空白手牌 var BlankHandTiles = make([]int, 13) var BlankHandTilesI8 = make([]int8, 13) type MjPlayer struct { state.State PlayerId string `json:"playerId"` Username string `json:"username"` Avatar string `json:"avatar"` Direct int `json:"direct"` Status int8 `json:"status"` // 1.未准备 2.已准备 3.赢 4.结算阶段 TileLack int8 `json:"tileLack"` // 定缺: 0万 1条 2筒 Coin int64 `json:"coin"` MjTable *MjTable `json:"-"` // 牌桌 MjHand []int8 `json:"mjHand"` // 手牌 MjDraw int8 `json:"mjDraw"` // 摸牌 MjPeng [][3]int8 `json:"mjPeng"` // 碰 MjGang [][4]int8 `json:"mjGang"` // 杠 MjHits []int8 `json:"mjHits"` // 出牌列表 Opts []int8 `json:"opts"` // 可操作列表 GangTiles []int8 `json:"optTiles"` // 可杠牌列表 PrevAction int8 `json:"prevAction"` PrevActions []int8 `json:"prevActions"` // 历史操作列表 WinSettle *WinSettle `json:"winSettle"` } func NewMjPlayer(playerId string) *MjPlayer { return &MjPlayer{ PlayerId: playerId, TileLack: -1, } } func (p *MjPlayer) Init() { p.InitState(p, state.WithState2Mapping(func(states state.S, setters *[]state.Setter) { if table, ok := states["MjTable"]; ok { // table -> tableId delete(states, "MjTable") states["TableId"] = "" if table != nil { states["TableId"] = table.(*MjTable).TableId } } if v, ok := states["WinSettle"]; ok { // win -> win names if v != nil { delete(states, "WinSettle") settle := v.(*WinSettle) states["WinSettle"] = state.S{ "weight": settle.Weight, "winTypeName": settle.WinType.String(), "weightNames": collect.Mapping(settle.Weights, func(w MjWeight) string { return w.String() }), } } } })) } // GameStart 游戏开始初始化状态 func (p *MjPlayer) GameStart() error { return p.SetStateEx(state.S{ "TileLack": int8(-1), "MjHand": []int8{}, "MjDraw": int8(0), "MjPeng": [][3]int8{}, "MjGang": [][4]int8{}, "MjHits": []int8{}, "Opts": []int8{}, "GangTiles": []int8{}, "PrevAction": int8(0), "PrevActions": []int8{}, "WinSettle": nil, }) } // RoundStart 回合开始 摸牌... func (p *MjPlayer) RoundStart() (err error) { table := p.MjTable err = p.SetStateEx(state.S{"MjDraw": table.MjWall.Pop()}) if err != nil { return } // 判断可杠 gangTiles := p.GetSelfGangTiles() if len(gangTiles) > 0 { p.Opts = append(p.Opts, ActionGang) err = p.SetStateEx(state.S{"GangTiles": gangTiles}) if err != nil { return } } // 判断可胡 if !IsHuaZhu(p.TileLack, p.MjDraw, p.MjHand) { wins := GetHandWins(p.MjDraw, p.MjHand) if len(wins) > 0 { p.Opts = append(p.Opts, ActionHu) } } p.Opts = append(p.Opts, ActionHit) err = p.SetStateEx(state.S{"Opts": p.Opts}) if err != nil { return } // 更新牌桌 err = table.SetStateEx(state.S{ "HitPlayerId": p.PlayerId, "MjWall": table.MjWall, }) return } func (p *MjPlayer) RoundOver() error { cleanStates := state.S{"Opts": nil} if len(p.GangTiles) > 0 { cleanStates["GangTiles"] = nil } return p.SetStateEx(cleanStates) } func (p *MjPlayer) GetSelfGangTiles() (tiles []int8) { cards := p.MjHand if p.MjDraw > 0 { cards = InsertHand(p.TileLack, p.MjDraw, cards) } // 手牌暗杠 gangs := FindQuaternion(cards) if len(gangs) > 0 { for _, gang := range gangs { if gang[0]/10 != p.TileLack { tiles = append(tiles, gang[0]) } } } // 巴杠 for _, peng := range p.MjPeng { for _, tile := range cards { if peng[0] == tile { tiles = append(tiles, peng[0]) break } } } return } // ActionLack 定缺 func (p *MjPlayer) ActionLack(lack int8) (err error) { if p.TileLack > -1 { return errors.New("已定缺") } return p.SetStateEx(state.S{"TileLack": lack}) } // ActionHit 出牌 func (p *MjPlayer) ActionHit(hit int8) (err error) { defer func() { // log hits if err == nil { err = p.SetStateEx(state.S{"MjHits": state.SetSliceAppend(hit)}) } }() if p.MjDraw == hit { err = p.SetStateEx(state.S{"MjDraw": int8(0)}) return } // 从手牌移除 handLen := len(p.MjHand) for i, tile := range p.MjHand { if tile == hit { if i < handLen-1 { // 从这里前移一位 copy(p.MjHand[i:handLen-1], p.MjHand[i+1:]) } p.MjHand = p.MjHand[0 : handLen-1] break } } if len(p.MjHand) != handLen-1 { err = errors.New("手中无这张牌") return } if p.MjDraw > 0 { // 摸牌插入手牌 p.MjHand = InsertHand(p.TileLack, p.MjDraw, p.MjHand) } err = p.SetStateEx(state.S{ "MjDraw": int8(0), "MjHand": p.MjHand, }) return } // ActionPeng 碰牌 func (p *MjPlayer) ActionPeng() (err error) { table := p.MjTable tile := table.PrevHit() if tile == 0 { return errors.New("不可碰") } cards, ok := FilterTiles(p.MjHand, tile, tile) if !ok { return errors.New("不可碰") } // 从牌池回收碰的牌 err = table.SetStateEx(state.S{"Hits": state.SetSlicePopN(1)}) if err != nil { return } hitPlayer := table.GetPlayer(table.HitPlayerId) err = hitPlayer.SetStateEx(state.S{"MjHits": state.SetSlicePopN(1)}) if err != nil { return } hitPlayer.State2Broadcast() return p.SetStateEx(state.S{ "MjHand": cards, "MjPeng": append(p.MjPeng, [3]int8{tile, tile, tile}), }) } // ActionGang 杠 // gangType: 1.点杠,2.巴杠,3.暗杠 func (p *MjPlayer) ActionGang(tile int8) (gangType int64, err error) { if collect.NotIn(tile, p.GangTiles...) { err = errors.New("不可杠") return } table := p.MjTable if table.HitPlayerId != p.PlayerId { gangType = 1 // 点杠/下雨 if table.PrevHit() != tile { err = errors.New("不可杠") return } cards, ok := FilterTiles(p.MjHand, tile, tile, tile) // 3张手牌 if !ok { err = errors.New("不可杠") return } err = p.SetStateEx(state.S{ "MjHand": cards, "MjGang": state.SetSliceAppend([4]int8{tile, tile, tile, tile}), }) if err != nil { return } err = table.SetStateEx(state.S{"Hits": state.SetSlicePopN(1)}) hitPlayer := table.GetPlayer(table.HitPlayerId) err = hitPlayer.SetStateEx(state.S{"MjHits": state.SetSlicePopN(1)}) if err != nil { return } hitPlayer.State2Broadcast() if err != nil { return } return } // 自己的回合 ok := false // 刮风/巴杠 for i, peng := range p.MjPeng { if ok = peng[0] == tile; ok { gangType = 2 if p.MjDraw == tile { p.SetState(state.S{"MjDraw": int8(0)}) } else { // 从手牌移除 cards, ok := FilterTiles(p.MjHand, tile) if !ok { err = errors.New("不可杠") return } if p.MjDraw > 0 { cards = InsertHand(p.TileLack, p.MjDraw, cards) } p.SetState(state.S{"MjHand": cards}) } p.SetState(state.S{ "MjPeng": collect.Filter(p.MjPeng, func(index int, _ [3]int8) bool { return index != i }), "MjGang": state.SetSliceAppend([4]int8{tile, tile, tile, tile}), }) break } } if ok { return } // 暗杠 handTiles := []int8{tile, tile, tile} if p.MjDraw != tile { handTiles = append(handTiles, tile) } cards, ok := FilterTiles(p.MjHand, handTiles...) if !ok { err = errors.New("不可杠") return } gangType = 3 if p.MjDraw == tile { p.SetState(state.S{"MjDraw": int8(0)}) } else if p.MjDraw > 0 { // 摸牌插入手牌 cards = InsertHand(p.TileLack, p.MjDraw, cards) } err = p.SetStateEx(state.S{ "MjHand": cards, "MjGang": state.SetSliceAppend([4]int8{tile, tile, tile, tile}), }) return } func (p *MjPlayer) GetPrevAction(step int) (action int8, ok bool) { length := len(p.PrevActions) ok = step > 0 && step <= length if !ok { return } action = p.PrevActions[length-step] return } // WinWeights 计算玩家牌型额外分数 func (p *MjPlayer) WinWeights(selfDraw bool) (weights []MjWeight) { // 杠 for i := 0; i < len(p.MjGang); i++ { weights = append(weights, WeightGang) } table := p.MjTable if len(p.MjHand) == 1 { weights = append(weights, WeightJinGouDiao) } // 自己摸牌胡 if selfDraw { // 自摸 weights = append(weights, WeightZiMo) // 杠上开花 if p.PrevAction == ActionGang { weights = append(weights, WeightGangShangKaiHua) } // 海底捞 if table.MjWall.RestNum() == 0 { weights = append(weights, WeightHaiDiLaoYue) } } else { hitPlayer := table.GetPlayer(table.HitPlayerId) // 杠上炮 if action, ok := hitPlayer.GetPrevAction(2); ok && action == ActionGang { weights = append(weights, WeightGangShangPao) } } if len(p.MjHits) == 0 { // 天胡, 地胡 if table.Players[table.EastPlayerSit()].PlayerId == p.PlayerId { weights = append(weights, WeightTianHu) } else { weights = append(weights, WeightDiHu) } } if len(weights) > 1 { sort.Slice(weights, func(i, j int) bool { return weights[i].Weight() > weights[j].Weight() }) } return }