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.
236 lines
5.2 KiB
236 lines
5.2 KiB
package game |
|
|
|
import ( |
|
"errors" |
|
"sonet/pkg/utils/state" |
|
"sync" |
|
) |
|
|
|
type MjTable struct { |
|
state.State |
|
TableId string |
|
MasterId string // 房主id |
|
Stage int8 // 游戏阶段:1.等待开始;2.游戏开始;3.undefined; 4.结算阶段 |
|
GameTimes int32 // 游戏次数 |
|
|
|
PlayerCount int16 |
|
Players [4]*MjPlayer // 玩家列表 |
|
PlayerDirects [4]int8 // 玩家方向: 0.东 1.南 2.西 3.北 |
|
MjWall *MjWall // 麻将牌堆 |
|
HitPlayerId string // 回合出牌玩家 |
|
Hits []int8 |
|
Lock *sync.Mutex |
|
} |
|
|
|
func NewMjTable(tableId string, masterId string) *MjTable { |
|
return &MjTable{ |
|
TableId: tableId, |
|
MasterId: masterId, |
|
Stage: 1, |
|
Players: [4]*MjPlayer{}, |
|
PlayerDirects: [4]int8{0, 1, 2, 3}, |
|
MjWall: &MjWall{}, |
|
Lock: &sync.Mutex{}, |
|
} |
|
} |
|
|
|
func (t *MjTable) Init() { |
|
t.InitState(t, state.WithState2Mapping(func(states state.S, setters *[]state.Setter) { |
|
if mjWall, ok := states["EastPlayerIndex"]; ok { |
|
delete(states, "EastPlayerIndex") |
|
states["eastPlayerId"] = mjWall.(*MjWall).RestNum() |
|
} |
|
|
|
if mjWall, ok := states["MjWall"]; ok { |
|
delete(states, "MjWall") |
|
states["RestTiles"] = mjWall.(*MjWall).RestNum() |
|
} |
|
})) |
|
} |
|
|
|
// GameStart 下一句游戏 |
|
func (t *MjTable) GameStart() (err error) { |
|
// 换东南西北 |
|
if t.GameTimes > 0 { |
|
for i := 0; i < 4; i++ { |
|
t.PlayerDirects[i] = (t.PlayerDirects[i] + 1) % 4 |
|
} |
|
} |
|
// 初始化麻将牌堆 |
|
t.MjWall.Init() |
|
return t.SetStateEx(state.S{ |
|
"Stage": int8(2), |
|
"PlayerDirects": t.PlayerDirects, |
|
"GameTimes": t.GameTimes + 1, |
|
"MjWall": t.MjWall, |
|
"Hits": []int8{}, |
|
}) |
|
} |
|
|
|
// GetEastPlayerIndex 东玩家索引 |
|
func (t *MjTable) GetEastPlayerIndex() int { |
|
for i, direct := range t.PlayerDirects { |
|
if direct == 0 { |
|
return i |
|
} |
|
} |
|
return 0 |
|
} |
|
|
|
// EastPlayerSit 东玩家(为nil则顺时针最近玩家)索引位置 |
|
func (t *MjTable) EastPlayerSit() int { |
|
for i, direct := range t.PlayerDirects { |
|
if direct == 0 { // 东 |
|
if t.Players[i] != nil { |
|
return i |
|
} else { |
|
for j := 1; j < 4; j++ { |
|
sit := (i + j) % 4 |
|
if t.Players[sit] != nil { |
|
return sit |
|
} |
|
} |
|
} |
|
break |
|
} |
|
} |
|
return 0 |
|
} |
|
|
|
// PrevHit 最后出的一张牌 |
|
func (t *MjTable) PrevHit() int8 { |
|
length := len(t.Hits) |
|
if length == 0 { |
|
return 0 |
|
} |
|
return t.Hits[length-1] |
|
} |
|
func (t *MjTable) GetPlayer(playerId string) *MjPlayer { |
|
for _, p := range t.Players { |
|
if p != nil && p.PlayerId == playerId { |
|
return p |
|
} |
|
} |
|
return nil |
|
} |
|
func (t *MjTable) GetPlayers(excludePlayerIds ...string) (players []*MjPlayer) { |
|
excludes := make(map[string]bool, len(excludePlayerIds)) |
|
for _, exclude := range excludePlayerIds { |
|
excludes[exclude] = true |
|
} |
|
|
|
players = make([]*MjPlayer, 0, t.PlayerCount) |
|
for _, player := range t.Players { |
|
if player != nil { |
|
if excludes[player.PlayerId] { // 跳过要忽略的 |
|
continue |
|
} |
|
players = append(players, player) |
|
} |
|
} |
|
return |
|
} |
|
|
|
func (t *MjTable) GetPlayerIds(excludePlayerIds ...string) (playerIds []string) { |
|
players := t.GetPlayers(excludePlayerIds...) |
|
playerIds = make([]string, 0, len(players)) |
|
for _, p := range players { |
|
playerIds = append(playerIds, p.PlayerId) |
|
} |
|
return |
|
} |
|
|
|
func (t *MjTable) PlayerJoin(player *MjPlayer) (int, error) { |
|
for i := 0; i < 4; i++ { |
|
if t.Players[i] == nil { |
|
err := t.SetStateEx(state.S{ |
|
"Players": state.SetArray(i, player), |
|
}) |
|
if err != nil { |
|
return i, err |
|
} |
|
player.MjTable = t |
|
player.Status = 1 |
|
t.PlayerCount++ |
|
return i, err |
|
} |
|
} |
|
return 0, errors.New("牌桌已满") |
|
} |
|
|
|
func (t *MjTable) PlayerLeave(playerId string) (err error) { |
|
for i := 0; i < 4; i++ { |
|
if t.Players[i] != nil && t.Players[i].PlayerId == playerId { |
|
player := t.Players[i] |
|
err = t.SetStateEx(state.S{ |
|
"Players": state.SetArray(i, nil), |
|
}) |
|
if err != nil { |
|
return |
|
} |
|
player.MjTable = nil |
|
t.PlayerCount-- |
|
break |
|
} |
|
} |
|
return |
|
} |
|
|
|
// NextDrawPlayer 下一摸牌玩家 |
|
func (t *MjTable) NextDrawPlayer(curPlayerId string) (nextPlayer *MjPlayer, err error) { |
|
if t.MjWall.RestNum() == 0 { // 没牌了结算 |
|
err = t.GameOver() |
|
return |
|
} |
|
for i, p := range t.Players { |
|
if p != nil && p.PlayerId == curPlayerId { |
|
for j := 1; j <= 4; j++ { |
|
next := t.Players[(i+j)%4] |
|
if next != nil && next.Status != 3 { |
|
nextPlayer = next |
|
return |
|
} |
|
} |
|
break |
|
} |
|
} |
|
// 没下个玩家了结算 |
|
err = t.GameOver() |
|
return |
|
} |
|
|
|
// GameOver 游戏结束, 结算 |
|
func (t *MjTable) GameOver() (err error) { |
|
// 游戏结束 |
|
for _, player := range t.GetPlayers() { |
|
player.SetState(state.S{ |
|
"Status": int8(4), |
|
"MjHand": player.MjHand, |
|
"MjDraw": player.MjDraw, |
|
}) |
|
player.State2Broadcast() |
|
} |
|
t.SetState(state.S{"Stage": int8(4)}) |
|
t.State2Broadcast() |
|
return |
|
} |
|
|
|
// PlayerWin 计算胡牌倍数 |
|
func (t *MjTable) PlayerWin(player *MjPlayer, selfDraw bool, wins []HandWin) (settle *WinSettle) { |
|
settle = &WinSettle{ |
|
Weights: player.WinWeights(selfDraw), |
|
} |
|
for _, win := range wins { |
|
winType := win.GetWinType(player.MjPeng, player.MjGang) |
|
calcWeight := winType.Weight() |
|
for _, weight := range settle.Weights { |
|
calcWeight *= weight.Weight() |
|
} |
|
if calcWeight > settle.Weight { |
|
settle.Weight = calcWeight |
|
settle.WinType = winType |
|
} |
|
} |
|
|
|
return |
|
}
|
|
|