You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
8.9 KiB
381 lines
8.9 KiB
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 |
|
}
|
|
|