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.
782 lines
18 KiB
782 lines
18 KiB
package logic |
|
|
|
import ( |
|
"context" |
|
"errors" |
|
"google.golang.org/grpc" |
|
"google.golang.org/grpc/reflection" |
|
"google.golang.org/protobuf/types/known/emptypb" |
|
"net" |
|
"sonet/api/gen/auth" |
|
"sonet/api/gen/mahjong" |
|
"sonet/internal/mahjong/game" |
|
"sonet/internal/mahjong/helper" |
|
"sonet/internal/mahjong/store" |
|
"sonet/pkg/config" |
|
"sonet/pkg/grpc/discovery" |
|
"sonet/pkg/grpc/interceptor" |
|
"sonet/pkg/protocol/deliver" |
|
"sonet/pkg/protocol/session" |
|
"sonet/pkg/utils/collect" |
|
"sonet/pkg/utils/logger" |
|
"sonet/pkg/utils/state" |
|
) |
|
|
|
type MahjongServer struct { |
|
mahjong.UnimplementedMahjongServer |
|
store *store.Store |
|
deliver *deliver.Deliver |
|
authClient auth.AuthClient |
|
} |
|
|
|
func NewMahjongServer(store *store.Store, deliver *deliver.Deliver, authClient auth.AuthClient) *MahjongServer { |
|
return &MahjongServer{ |
|
store: store, |
|
deliver: deliver, |
|
authClient: authClient, |
|
} |
|
} |
|
|
|
func (mj *MahjongServer) Run(conf config.GrpcConfig, register *discovery.Register) (err error) { |
|
server := grpc.NewServer( |
|
config.GetGrpcOptions( |
|
conf, |
|
grpc.UnaryInterceptor(interceptor.RecoverInterceptor), |
|
)..., |
|
) |
|
if !conf.NoReflection { |
|
// 注册反射服务 |
|
reflection.Register(server) |
|
} |
|
mahjong.RegisterMahjongServer(server, mj) |
|
listen, err := net.Listen("tcp", conf.Address) |
|
if err != nil { |
|
return |
|
} |
|
|
|
// registry discovery |
|
reg := conf.Register |
|
if reg.Name == "" { |
|
reg.Name = mahjong.Mahjong_ServiceDesc.ServiceName |
|
} |
|
if reg.Addr == "" { |
|
reg.Addr, err = discovery.RegisterAddress(conf.Address) |
|
if err != nil { |
|
return |
|
} |
|
} |
|
if err = register.Register(reg); err != nil { |
|
return |
|
} |
|
|
|
// run serve |
|
logger.Infof("%s grpc server running %s\n", reg.Name, listen.Addr().String()) |
|
err = server.Serve(listen) |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) PlayerOnline(ctx context.Context, empty *emptypb.Empty) (res *mahjong.ResPlayerOnline, err error) { |
|
|
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
res = &mahjong.ResPlayerOnline{} |
|
player, err := mj.store.LoadPlayer(subject.Uid) |
|
if err == nil { // 重连用户 |
|
res.Player = helper.MjPlayer2TfPlayer(player) |
|
return |
|
} |
|
|
|
// load player info |
|
account, err := mj.authClient.FindByUid(ctx, &auth.ReqFindByUid{Uid: subject.Uid}) |
|
if err != nil { |
|
return |
|
} |
|
player = game.NewMjPlayer(subject.Uid) |
|
player.Init() |
|
player.Username = account.Username |
|
player.Avatar = account.Extra["avatar"] |
|
// store online player |
|
mj.store.StorePlayer(player) |
|
|
|
res.Player = helper.MjPlayer2TfPlayer(player) |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) PlayerOffline(ctx context.Context, empty *emptypb.Empty) (_ *emptypb.Empty, err error) { |
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
// delete offline player |
|
mj.store.RemovePlayer(subject.Uid) |
|
// TODO persistent player |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) LobbyView(ctx context.Context, empty *emptypb.Empty) (r *mahjong.Lobby, err error) { |
|
r = &mahjong.Lobby{} |
|
mj.store.RangeTable(func(key, value any) bool { |
|
table := value.(*game.MjTable) |
|
r.Tables = append(r.Tables, &mahjong.LobbyTable{ |
|
TableId: table.TableId, |
|
PlayerCount: int32(table.PlayerCount), |
|
Stage: int32(table.Stage), |
|
}) |
|
return true |
|
}) |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) CreateTable(ctx context.Context, empty *emptypb.Empty) (mjTable *mahjong.MjTable, err error) { |
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
tableId := mj.store.NextTableId() |
|
table := game.NewMjTable(tableId, subject.Uid) |
|
table.Init() |
|
|
|
master, err := mj.store.LoadPlayer(subject.Uid) // master player |
|
if err != nil { |
|
return |
|
} |
|
if master.MjTable != nil { |
|
err = errors.New("请先退出当前牌桌") |
|
return |
|
} |
|
_, err = table.PlayerJoin(master) |
|
if err != nil { |
|
return |
|
} |
|
table.StateIdleClean() |
|
mj.store.StoreTable(table) |
|
mjTable = helper.MjTable2ProtoTable(table, "") |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) JoinTable(ctx context.Context, req *mahjong.ReqJoinTable) (t *mahjong.MjTable, err error) { |
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
|
|
// store player |
|
player, err := mj.store.LoadPlayer(subject.Uid) |
|
if err != nil { |
|
return |
|
} |
|
if player.MjTable != nil { |
|
t = helper.MjTable2ProtoTable(player.MjTable, player.PlayerId) |
|
return |
|
} |
|
|
|
table, err := mj.store.LoadTable(req.TableId) |
|
if err != nil { |
|
return |
|
} |
|
for _, p := range table.GetPlayers() { // 挤下线 |
|
if p.PlayerId == player.PlayerId { |
|
player.MjTable = table |
|
t = helper.MjTable2ProtoTable(player.MjTable, player.PlayerId) |
|
return |
|
} |
|
} |
|
|
|
if table.PlayerCount >= 4 { |
|
err = errors.New("牌桌已满") |
|
return |
|
} |
|
|
|
table.Lock.Lock() |
|
defer table.Lock.Unlock() |
|
|
|
_, err = table.PlayerJoin(player) |
|
if err != nil { |
|
return |
|
} |
|
table.State2Broadcast(player.PlayerId) // notice other player join me |
|
|
|
// return table status |
|
t = helper.MjTable2ProtoTable(table, player.PlayerId) |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) LeaveTable(ctx context.Context, _ *emptypb.Empty) (res *emptypb.Empty, err error) { |
|
|
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
player, err := mj.store.LoadPlayer(subject.Uid) |
|
if err != nil { |
|
return |
|
} |
|
if player.MjTable == nil { |
|
return |
|
} |
|
|
|
table, err := mj.store.LoadTable(player.MjTable.TableId) |
|
if err != nil { |
|
return |
|
} |
|
if collect.In(player.Status, 2, 3) { |
|
err = errors.New("进行中暂不能离开") |
|
return |
|
} |
|
if collect.NotIn(table.Stage, 1, 4) { |
|
err = errors.New("牌局进行中,不能离开") |
|
return |
|
} |
|
isMaster := table.MasterId == player.PlayerId |
|
rest := table.GetPlayers(player.PlayerId) |
|
if len(rest) == 0 { |
|
// 玩家全退出解散牌桌 |
|
_, err = mj.DismissTable(ctx, &emptypb.Empty{}) |
|
return |
|
} |
|
|
|
table.Lock.Lock() |
|
defer table.Lock.Unlock() |
|
|
|
err = table.PlayerLeave(player.PlayerId) |
|
if err != nil { |
|
return |
|
} |
|
if isMaster { |
|
for _, p := range table.Players { |
|
if p != nil { |
|
table.SetState(state.S{"MasterId": p.PlayerId}) |
|
break |
|
} |
|
} |
|
} |
|
table.State2Broadcast() |
|
//res = &emptypb.Empty{} |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) KickoutTable(ctx context.Context, req *mahjong.ReqKickoutTable) (res *emptypb.Empty, err error) { |
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
player, err := mj.store.LoadPlayer(subject.Uid) |
|
if err != nil { |
|
return |
|
} |
|
table := player.MjTable |
|
if table == nil { |
|
err = errors.New("牌桌不存在") |
|
return |
|
} |
|
if table.MasterId != player.PlayerId { |
|
err = errors.New("非房主不可踢人") |
|
return |
|
} |
|
table.Lock.Lock() |
|
defer table.Lock.Unlock() |
|
|
|
err = table.PlayerLeave(req.TargetUid) |
|
if err != nil { |
|
return |
|
} |
|
table.State2Broadcast() |
|
res = &emptypb.Empty{} |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) ReadyStart(ctx context.Context, _ *emptypb.Empty) (_ *emptypb.Empty, err error) { |
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
player, err := mj.store.LoadPlayer(subject.Uid) |
|
if err != nil { |
|
return |
|
} |
|
if player.MjTable == nil { |
|
err = errors.New("牌桌不存在") |
|
return |
|
} |
|
if player.Status == 2 { |
|
err = errors.New("已准备") |
|
return |
|
} |
|
|
|
player.MjTable.Lock.Lock() |
|
defer player.MjTable.Lock.Unlock() |
|
|
|
player.SetState(state.S{"Status": int8(2)}) |
|
player.State2Broadcast() |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) CancelReady(ctx context.Context, _ *emptypb.Empty) (_ *emptypb.Empty, err error) { |
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
player, err := mj.store.LoadPlayer(subject.Uid) |
|
if err != nil { |
|
return |
|
} |
|
if player.MjTable == nil { |
|
err = errors.New("牌桌不存在") |
|
return |
|
} |
|
if player.Status == 1 { |
|
err = errors.New("未准备") |
|
return |
|
} |
|
|
|
player.MjTable.Lock.Lock() |
|
defer player.MjTable.Lock.Unlock() |
|
|
|
player.SetState(state.S{"Status": int8(1)}) |
|
player.State2Broadcast() |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) DismissTable(ctx context.Context, _ *emptypb.Empty) (res *emptypb.Empty, err error) { |
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
player, err := mj.store.LoadPlayer(subject.Uid) |
|
if err != nil { |
|
return |
|
} |
|
table := player.MjTable |
|
if table == nil { |
|
err = errors.New("牌桌不存在") |
|
return |
|
} |
|
table.Lock.Lock() |
|
defer table.Lock.Unlock() |
|
|
|
if table.Stage == 2 { |
|
err = errors.New("牌局进行中") |
|
return |
|
} |
|
for _, p := range table.GetPlayers() { |
|
p.SetState(state.S{"MjTable": nil}) |
|
p.State2Assign(p.PlayerId) |
|
} |
|
receivers := table.GetPlayerIds() |
|
mj.store.RemoveTable(table.TableId) |
|
|
|
_, err = mj.deliver.DeliverBatch(ctx, &mahjong.NoticeDismissTable{}, receivers) |
|
res = &emptypb.Empty{} |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) GameStart(ctx context.Context, _ *emptypb.Empty) (res *emptypb.Empty, err error) { |
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
player, err := mj.store.LoadPlayer(subject.Uid) |
|
if err != nil { |
|
return |
|
} |
|
table := player.MjTable |
|
if table.MasterId != player.PlayerId { |
|
err = errors.New("只有房主可以开始") |
|
return |
|
} |
|
if table == nil { |
|
err = errors.New("牌桌不存在") |
|
return |
|
} |
|
if table.PlayerCount < 2 { |
|
err = errors.New("玩家人数不够") |
|
return |
|
} |
|
if collect.NotIn(table.Stage, 1, 4) { |
|
err = errors.New("牌桌正在进行中") |
|
return |
|
} |
|
// 检查玩家准备状态 |
|
for _, player := range table.GetPlayers() { |
|
if player.Status != 2 && table.MasterId != player.PlayerId { |
|
err = errors.New("有玩家未准备") |
|
return |
|
} |
|
} |
|
|
|
// 初始化状态,发牌 |
|
table.Lock.Lock() |
|
defer table.Lock.Unlock() |
|
// 初始化牌桌状态 |
|
err = table.GameStart() |
|
if err != nil { |
|
return |
|
} |
|
|
|
// action 定缺 |
|
for i, p := range table.Players { |
|
if p == nil { |
|
continue |
|
} |
|
err = p.GameStart() |
|
if err != nil { |
|
return |
|
} |
|
p.State2Broadcast() |
|
|
|
// 初始化玩家手牌 摸13张 |
|
mjHand := table.MjWall.PopN(13) |
|
game.SortTiles(mjHand) |
|
|
|
if i == table.EastPlayerSit() { |
|
// 东摸一张牌 |
|
p.SetState(state.S{"MjDraw": table.MjWall.Pop()}) |
|
table.SetState(state.S{"MjWall": table.MjWall}) |
|
} |
|
p.SetState(state.S{ |
|
"MjHand": mjHand, |
|
"Opts": []int8{game.ActionLack}, |
|
}) |
|
p.State2Assign(p.PlayerId) |
|
} |
|
table.State2Broadcast() |
|
res = &emptypb.Empty{} |
|
return |
|
} |
|
|
|
func (mj *MahjongServer) PlayerAction(ctx context.Context, req *mahjong.ReqPlayerAction) (res *emptypb.Empty, err error) { |
|
res = &emptypb.Empty{} |
|
subject, err := session.GetSubject(ctx) |
|
if err != nil { |
|
return |
|
} |
|
player, err := mj.store.LoadPlayer(subject.Uid) |
|
if err != nil { |
|
return |
|
} |
|
table := player.MjTable |
|
if table == nil { |
|
err = errors.New("牌桌不存在") |
|
return |
|
} |
|
|
|
actionOpt := int8(req.Opt) |
|
actionHit := int8(req.Hit) |
|
if collect.NotIn(actionOpt, player.Opts...) { |
|
err = errors.New("不允许当前操作") |
|
return |
|
} |
|
|
|
table.Lock.Lock() |
|
defer table.Lock.Unlock() |
|
|
|
defer func() { |
|
if collect.In(actionOpt, game.ActionGang, game.ActionPeng) { |
|
if table.HitPlayerId != player.PlayerId { |
|
table.SetState(state.S{"HitPlayerId": player.PlayerId}) |
|
table.State2Broadcast() |
|
} |
|
} |
|
player.PrevAction = actionOpt |
|
player.PrevActions = append(player.PrevActions, actionOpt) |
|
}() |
|
|
|
switch actionOpt { |
|
case game.ActionCancel: |
|
if table.HitPlayerId == player.PlayerId { |
|
err = errors.New("不可取消") |
|
return |
|
} |
|
// 取消碰杠胡 |
|
if !collect.In(game.ActionPeng, player.Opts...) && |
|
!collect.In(game.ActionGang, player.Opts...) && |
|
!collect.In(game.ActionHu, player.Opts...) { |
|
err = errors.New("不可取消") |
|
return |
|
} |
|
err = player.RoundOver() |
|
if err != nil { |
|
return |
|
} |
|
player.State2Assign(player.PlayerId) |
|
// 还有玩家杠碰胡 |
|
waitingPlayers := collect.Filter(table.GetPlayers(), func(_ int, p *game.MjPlayer) bool { |
|
return len(p.Opts) > 0 |
|
}) |
|
if len(waitingPlayers) > 0 { |
|
return |
|
} |
|
|
|
// 下个玩家 |
|
nextPlayer, e := table.NextDrawPlayer(table.HitPlayerId) |
|
if e != nil { |
|
err = e |
|
return |
|
} |
|
if nextPlayer != nil { |
|
err = nextPlayer.RoundStart() |
|
if err != nil { |
|
return |
|
} |
|
nextPlayer.State2Assign(nextPlayer.PlayerId) |
|
} |
|
table.State2Broadcast() |
|
|
|
case game.ActionLack: // 定缺 |
|
err = player.ActionLack(int8(req.TileLack)) |
|
if err != nil { |
|
return |
|
} |
|
err = player.RoundOver() |
|
if err != nil { |
|
return |
|
} |
|
player.State2Broadcast() |
|
// 手牌排序 |
|
game.SortHandTiles(player.TileLack, player.MjHand) |
|
player.SetState(state.S{"MjHand": player.MjHand}) |
|
player.State2Assign(player.PlayerId) |
|
|
|
// 所有玩家都已定缺, 东出牌 |
|
nonLacks := collect.Filter(table.GetPlayers(), func(_ int, p *game.MjPlayer) bool { |
|
return p.TileLack == -1 |
|
}) |
|
if len(nonLacks) == 0 { |
|
// 东出牌 |
|
eastPlayer := table.Players[table.EastPlayerSit()] |
|
err = eastPlayer.RoundStart() |
|
if err != nil { |
|
return |
|
} |
|
eastPlayer.State2Assign(eastPlayer.PlayerId) |
|
table.SetState(state.S{"HitPlayerId": eastPlayer.PlayerId}) |
|
table.State2Broadcast() |
|
} |
|
|
|
case game.ActionHit: // 出牌 |
|
err = player.ActionHit(actionHit) |
|
if err != nil { |
|
return |
|
} |
|
err = player.RoundOver() |
|
if err != nil { |
|
return |
|
} |
|
player.State2Assign(player.PlayerId) |
|
|
|
table.SetState(state.S{"Hits": state.SetSliceAppend(actionHit)}) |
|
|
|
// 计算其他玩家碰、杠、胡可能 |
|
otherPlayers := table.GetPlayers(player.PlayerId) |
|
waitOpts := false |
|
for _, p := range otherPlayers { |
|
if p.Status == 3 { |
|
continue |
|
} |
|
if actionHit/10 != p.TileLack { |
|
// 碰,手牌中有2张相同 |
|
if _, ok := game.FilterTiles(p.MjHand, actionHit, actionHit); ok { |
|
p.Opts = append(p.Opts, game.ActionPeng) |
|
} |
|
// 杠,手牌中有3张相同 |
|
if _, ok := game.FilterTiles(p.MjHand, actionHit, actionHit, actionHit); ok { |
|
p.Opts = append(p.Opts, game.ActionGang) |
|
p.SetState(state.S{"GangTiles": []int8{actionHit}}) |
|
} |
|
} |
|
if !game.IsHuaZhu(p.TileLack, actionHit, p.MjHand) { // 花猪 |
|
wins := game.GetHandWins(actionHit, p.MjHand) |
|
if len(wins) > 0 { |
|
p.Opts = append(p.Opts, game.ActionHu) |
|
} |
|
} |
|
if len(p.Opts) > 0 { |
|
p.Opts = append(p.Opts, game.ActionCancel) |
|
p.SetState(state.S{"Opts": p.Opts}) |
|
p.State2Assign(p.PlayerId) |
|
waitOpts = true |
|
} |
|
} |
|
if !waitOpts { |
|
// 下一家摸牌 |
|
nextPlayer, e := table.NextDrawPlayer(player.PlayerId) |
|
if e != nil { |
|
err = e |
|
return |
|
} |
|
if nextPlayer != nil { |
|
err = nextPlayer.RoundStart() |
|
if err != nil { |
|
return |
|
} |
|
nextPlayer.State2Assign(nextPlayer.PlayerId) |
|
} |
|
} |
|
table.State2Broadcast() |
|
|
|
case game.ActionPeng: |
|
err = player.ActionPeng() |
|
if err != nil { |
|
return |
|
} |
|
err = player.RoundOver() |
|
if err != nil { |
|
return |
|
} |
|
|
|
// 碰完打一张 |
|
player.SetState(state.S{"Opts": []int8{game.ActionHit}}) |
|
player.State2Assign(player.PlayerId) |
|
player.SetState(state.S{"MjPeng": player.MjPeng}) |
|
player.State2Broadcast(player.PlayerId) |
|
table.State2Broadcast() |
|
|
|
case game.ActionGang: |
|
gangType, e := player.ActionGang(actionHit) |
|
if e != nil { |
|
err = e |
|
return |
|
} |
|
err = player.RoundOver() |
|
if err != nil { |
|
return |
|
} |
|
|
|
var gangProfit int64 |
|
switch gangType { |
|
case 1: // 点杠 |
|
gangProfit = 2 |
|
hitPlayer := table.GetPlayer(table.HitPlayerId) |
|
hitPlayer.SetState(state.S{"Coin": hitPlayer.Coin - gangProfit}) |
|
hitPlayer.State2Broadcast() |
|
case 2: // 巴杠 |
|
fallthrough |
|
case 3: // 暗杠 |
|
score := gangType - 1 |
|
for _, p := range table.GetPlayers(player.PlayerId) { |
|
if p.Status != 3 { |
|
gangProfit += score |
|
p.SetState(state.S{"Coin": p.Coin - score}) |
|
p.State2Broadcast() |
|
} |
|
} |
|
} |
|
|
|
// 摸牌, opts |
|
player.SetState(state.S{"MjDraw": table.MjWall.Pop()}) |
|
table.SetState(state.S{"MjWall": table.MjWall}) |
|
table.State2Broadcast() |
|
|
|
opts := []int8{game.ActionHit} |
|
gangTiles := player.GetSelfGangTiles() |
|
if len(gangTiles) > 0 { |
|
opts = append(opts, game.ActionGang) |
|
player.SetState(state.S{"GangTiles": gangTiles}) |
|
} |
|
if !game.IsHuaZhu(player.TileLack, player.MjDraw, player.MjHand) { |
|
wins := game.GetHandWins(player.MjDraw, player.MjHand) |
|
if len(wins) > 0 { |
|
opts = append(opts, game.ActionHu) |
|
} |
|
} |
|
player.SetState(state.S{"Opts": opts}) |
|
player.State2Assign(player.PlayerId) |
|
|
|
// 广播杠 |
|
player.SetState(state.S{ |
|
"Coin": player.Coin + gangProfit, |
|
"MjGang": player.MjGang, |
|
}) |
|
player.State2Broadcast() |
|
|
|
case game.ActionHu: |
|
// 自摸 |
|
selfDraw := table.HitPlayerId == player.PlayerId |
|
winTile := player.MjDraw |
|
if !selfDraw { |
|
winTile = table.PrevHit() |
|
} |
|
if game.IsHuaZhu(player.TileLack, winTile, player.MjHand) { // 花猪 |
|
err = errors.New("不可胡") |
|
return |
|
} |
|
wins := game.GetHandWins(winTile, player.MjHand) |
|
if len(wins) == 0 { |
|
err = errors.New("不可胡") |
|
return |
|
} |
|
|
|
// calc win coins |
|
player.WinSettle = table.PlayerWin(player, selfDraw, wins) |
|
var winProfit int64 |
|
weight := int64(player.WinSettle.Weight) |
|
if !selfDraw { |
|
hitPlayer := table.GetPlayer(table.HitPlayerId) |
|
winProfit = weight |
|
hitPlayer.SetState(state.S{"Coin": hitPlayer.Coin - weight}) |
|
} else { |
|
// 自摸 |
|
for _, p := range table.GetPlayers(player.PlayerId) { |
|
if p.Status != 3 { |
|
winProfit += weight |
|
p.SetState(state.S{"Coin": p.Coin - weight}) |
|
p.State2Broadcast() |
|
} |
|
} |
|
} |
|
|
|
// 设置状态赢 |
|
player.SetState(state.S{ |
|
"Coin": player.Coin + winProfit, |
|
"Status": int8(3), |
|
"WinSettle": player.WinSettle, |
|
}) |
|
player.State2Broadcast() |
|
|
|
// 整理手牌 |
|
cards := game.InsertHand(player.TileLack, winTile, player.MjHand) |
|
if selfDraw { |
|
player.SetState(state.S{"MjDraw": int8(0)}) |
|
} else { |
|
table.SetState(state.S{"Hits": state.SetSlicePopN(1)}) |
|
hitPlayer := table.GetPlayer(table.HitPlayerId) |
|
hitPlayer.SetState(state.S{"MjHits": state.SetSlicePopN(1)}) |
|
hitPlayer.State2Broadcast() |
|
} |
|
player.SetState(state.S{"MjHand": cards}) |
|
err = player.RoundOver() |
|
if err != nil { |
|
return |
|
} |
|
player.State2Assign(player.PlayerId) |
|
|
|
// 1家未胡牌游戏结束 |
|
nonWins := collect.Filter(table.GetPlayers(), func(_ int, p *game.MjPlayer) bool { |
|
return p.Status != 3 |
|
}) |
|
if len(nonWins) == 1 { |
|
err = table.GameOver() |
|
return |
|
} |
|
|
|
// nextPlayer |
|
nextPlayer, e := table.NextDrawPlayer(player.PlayerId) |
|
if e != nil { |
|
err = e |
|
return |
|
} |
|
if nextPlayer != nil { |
|
err = nextPlayer.RoundStart() |
|
if err != nil { |
|
return |
|
} |
|
nextPlayer.State2Assign(nextPlayer.PlayerId) |
|
} |
|
table.State2Broadcast() |
|
return |
|
|
|
} |
|
return |
|
}
|
|
|