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

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
}