package room import ( "errors" "fmt" "math/rand" "samba/pkg/log" "samba/pkg/servername" "samba/proto" . "samba/server/game/baseroom" . "samba/server/game/player" "samba/server/truco/poker" . "samba/server/truco/service" "samba/stub" "samba/util/event" "samba/util/model" . "samba/util/playtype" "samba/util/routingKey" "samba/util/state" "samba/util/util" "slices" "time" ) type TrucoSeat struct { *BaseSeat Pokers []*poker.Poker // 手牌 //ActType ActType // 最近叫分行为 OutPoker *poker.Poker // 最近出的牌 disband state.DisbandType // 解散状态 } // TeamAct 队伍操作 type TeamAct struct { color TeamColor // 队伍颜色 maxAct ActType // 最大操作 actType map[int]ActType // 操作行为 //seatNo int // 操作的座位号 //round int // 叫分轮次 } func newTeamAct(color TeamColor) *TeamAct { act := &TeamAct{color: color, maxAct: AtUnknown, actType: make(map[int]ActType)} return act } type TrucoRoom struct { *BaseRoom[*TrucoSeat] *event.Events pokers poker.IPokerGenerator pointType *poker.PlayTypeConst // 玩法不同,初始点数及加点也不同 dealer int // 庄家座位 current int // 当前行动人座位 roundStartPos int // 本轮中最开始的位置,用于比牌 round int // 当前轮 0-2 lastTrucoColor TeamColor // 最近叫分的队伍颜色 actTypes []*TeamAct // 双方最近行动类型 agreeStatus [2]ActType // 本局双方同意状态 points [2]int // 双方累积分 point int // 本局分数 light []GameLight // 回合结算胜负结果 gameStep GameStep // 游戏状态 decidingTeam TeamColor // 决胜局队伍 star int // 金币场,倍数,用星星表示,默认1倍 startTime time.Time endTime time.Time record *TrucoGameRecord // 战绩玩家查询 gameLog *TrucoGameLog // 游戏操作汇总给后台 smallGameCount int // 记录第几小局 isActTeam bool // 当前行动人数,1人或者1队 disbandInfo map[int64]int // 申请解散 disbandTime int64 // 申请解散房间的起始时间 disband state.RoomDisbandType // 解散状态 } func NewTrucoRoom(id, roomType, clubId int) (IRoom, proto.ErrorCode) { baseRoom, code := NewBaseRoom[*TrucoSeat](id, roomType, clubId) if code != proto.Ok { return nil, code } room := &TrucoRoom{ BaseRoom: baseRoom, Events: event.NewEvents(), pokers: poker.NewPokerGenerator(PlayType(baseRoom.RoomCnf.PlayType)), pointType: poker.NewPlayTypeConst(PlayType(baseRoom.RoomCnf.PlayType)), dealer: 0, current: 0, round: 0, actTypes: make([]*TeamAct, 0), star: 1, agreeStatus: [2]ActType{AtUnknown, AtUnknown}, points: [2]int{0, 0}, point: poker.NewPlayTypeConst(PlayType(baseRoom.RoomCnf.PlayType)).GameStartPoint(), light: make([]GameLight, 0), gameStep: GsReadyGame, gameLog: NewTrucoGameLog(), smallGameCount: 0, disband: state.RdtNormal, } room.SetTruthRoom(room) if room.pokers == nil { log.Error(room.Log("create room fail.play type:%v", baseRoom.RoomCnf.PlayType)) return nil, proto.Internal } room.SetTimerHandler(room) for i := 0; i < room.RoomCnf.MinPlayers; i++ { room.Seats = append(room.Seats, &TrucoSeat{ BaseSeat: NewBaseSeat(i), Pokers: make([]*poker.Poker, 0), //ActType: AtUnknown, OutPoker: nil, }) } room.dealer = int(rand.Int31n(int32(len(room.Seats)))) room.current = (room.dealer + 1) % len(room.Seats) room.roundStartPos = room.current room.gameLog.Star = room.star if err := TrucoService.QueueBind(QueueName(), routingKey.RoomKey(room.Id()), util.Direct(servername.Truco)); err != nil { log.Error(err.Error()) } room.UpdateClubWaitingRoom() //room.SetGmPokers(0, [][]int{{302, 402, 102}, {403, 203, 303}}, 202) return room, code } func (r *TrucoRoom) addResource(player *Player, add int64, resType, reason string) { if add == 0 { return } if resType == model.ResTakeCoins { r.AddTakeCoin(player, add, reason) } else { r.AddUserResource(player.UID, add, resType, reason) } } // 大局之前初始化 func (r *TrucoRoom) ReInit() { //if err := TrucoService.QueueBind(QueueName(), routingKey.RoomKey(r.Id()), util.Direct(servername.Truco)); err != nil { // log.Error(err.Error()) //} // 进房间时,匹配房间已经移除 //GMatchQueue.RemoveRoom(r.Id(), r.Type()) RoomMgr.Add(r) //r.gameNo = uuid.NewString() r.star = 1 r.startTime = time.Now() r.record = NewTrucoGameRecord() var playingNum int64 var err error for _, seat := range r.Seats { log.Debug(r.SeatLog(seat, "颜色:%v", r.teamColor(seat.No()))) if !seat.Empty() && !seat.Player().IsRobot() { num, err1 := model.AddRoomTypePlayerNum(r.RoomCnf, seat.Player().UID) if err1 != nil { err = errors.Join(err1) } else { playingNum = num } } } if err == nil && r.RoomCnf.Mode != int(stub.RmClub) { // 金币场 广播虚假在玩人数 BroadcastMsg(proto.NtfPlayingNumId, &proto.NtfPlayingNum{RoomType: r.RoomCnf.Id, PlayingNum: model.CalculateFakeCount(playingNum)}) } r.record.AddPlayers(r) r.gameLog.BaseGameLog = r.BaseGameLog() r.gameLog.BaseGameLog.StartGame(r.startTime.Unix(), r.GameNo) r.gameLog.AddPlayers(r) } func (r *TrucoRoom) OnMessage(msgId string, msg map[string]interface{}) { switch msgId { case proto.ReqReadyGameId: r.onReady(msg) case proto.ReqEmoteId: r.onEmote(msg) case proto.ReqEnterRoomId: r.onReentryRoom(msg) case proto.ReqLeaveRoomId: r.onLeaveRoom(msg) case proto.ReqSetDarkPokerId: r.onSetDarkPoker(msg) case proto.ReqPlayerOutPokerId: r.onOutPoker(msg) case proto.ReqPlayerActId: r.onPlayerAct(msg) case proto.ReqReconnectId: r.onReconnect(msg) case proto.ReqShowPokerId: r.onShowPoker(msg) case proto.NtfMaintainId: r.onMaintain(msg) case proto.ReqDisbandRoomId: r.onDisbandRoom(msg) case proto.ReqUserDisbandRoomId: r.onUserDisbandRoom(msg) case proto.ReqHostingId: r.onHosting(msg) } } func (r *TrucoRoom) GetSeat(uid int64) *TrucoSeat { for _, seat := range r.Seats { if !seat.Empty() && seat.Player().UID == uid { return seat } } log.Error(r.Log("player:%d not in any seat", uid)) return nil } func (r *TrucoRoom) GetSeatNo(uid int64) int { for _, seat := range r.Seats { if seat.Player() != nil && seat.Player().UID == uid { return seat.No() } } log.Error(r.Log("player:%d is not in any seat", uid)) return -1 } // 发牌 func (r *TrucoRoom) dealPoker() { r.gameStep = GsDealPoker r.pokers.Shuffle() alloc := &poker.Allocator{} alloc.Init(r.pokers, r.pokers.Pokers()) // 发牌时,打乱发牌顺序 randSeats := slices.Clone(r.Seats) rand.Shuffle(len(randSeats), func(i, j int) { randSeats[i], randSeats[j] = randSeats[j], randSeats[i] }) for _, seat := range randSeats { var ok bool sum, err := seat.Player().GetPlayCount(stub.Truco) if err != nil { log.Error(r.UserLog(seat.Player().UID, "获取玩家游戏次数失败:%s", err.Error())) } switch { case r.ClubId() > 0: seat.Pokers, _ = alloc.Alloc(poker.AllocNormal, PlayerPokersNum) log.Debug(r.SeatLog(seat, "俱乐部场,普通配牌")) case seat.Player().IsRobot(): // 机器人总是普通配牌 seat.Pokers, _ = alloc.Alloc(poker.AllocNormal, PlayerPokersNum) log.Debug(r.SeatLog(seat, "机器人普通配牌")) case r.HasLuckyUser: // 房间内有幸运玩家(白名单),所有配好牌 seat.Pokers, ok = alloc.Alloc(poker.AllocGood, PlayerPokersNum) log.Debug(r.SeatLog(seat, "房间内有幸运玩家,所有配好牌,是否成功配牌:%v", ok)) case err == nil && sum <= stub.PlayerNoviceThreshold: // 新手玩家,不走幸运值逻辑,配好牌 seat.Pokers, ok = alloc.Alloc(poker.AllocGood, PlayerPokersNum) log.Debug(r.SeatLog(seat, "游玩局数为 %d 新手配好牌,是否成功配牌:%v", sum, ok)) default: // 走幸运值配牌 r.luckyAllocPoker(alloc, seat) } } } // 幸运值配牌逻辑 func (r *TrucoRoom) luckyAllocPoker(alloc *poker.Allocator, seat *TrucoSeat) { var ok bool act := poker.AllocNormal if !seat.Player().IsRobot() { act = poker.AllocActByLuckyPoint(seat.Player().LuckyPoint.GetLuckyPoint(stub.Truco)) } seat.Pokers, ok = alloc.Alloc(act, PlayerPokersNum) if ok && act == poker.AllocGood { // 触发配好牌行为 // 减少玩家幸运值 lp, err := model.NewUserLuckyPointOp().AddByGameType(seat.Player().UID, stub.Truco, -10) if err == nil { seat.Player().LuckyPoint.SetLuckyPoint(stub.Truco, lp) } else { log.Error(r.UserLog(seat.Player().UID, "配好牌时,减少幸运值失败:%s", err.Error())) } } log.Debug(r.SeatLog(seat, "幸运值配牌,配牌行为 act:%s 是否成功配牌:%v", act, ok)) } func (r *TrucoRoom) playerPokersToPorto(seatNo int) [][]int { arr := make([][]int, len(r.Seats)) for no := range arr { arr[no] = make([]int, PlayerPokersNum) } for i := 0; i < len(r.Seats[seatNo].Pokers); i++ { arr[seatNo][i] = r.Seats[seatNo].Pokers[i].ToInt() } return arr } // 判断玩家队伍颜色 func (r *TrucoRoom) teamColor(seatNo int) TeamColor { if seatNo%2 == 0 { return TcRed } return TcGreen } // 判断对手队伍颜色 func (r *TrucoRoom) rivalColor(seatNo int) TeamColor { return (r.teamColor(seatNo) + 1) % 2 } // 判断对手队伍颜色 func (r *TrucoRoom) rivalColor2(color TeamColor) TeamColor { return (color + 1) % 2 } func (r *TrucoRoom) smallGameStart() { r.smallGameCount++ log.Debug(r.Log("第%d小局,开始发牌", r.smallGameCount)) r.record.NewGame() r.SetStatus(state.RsGaming) for _, st := range r.Seats { st.Player().Continue = GcNormal if !st.Empty() { // 俱乐部房间触发在玩通知 r.TriggerClubMemberPlaying(st.Player().UID, true) } } // 重置本局数据 r.dealer = (r.dealer + 1) % len(r.Seats) r.current = (r.dealer + 1) % len(r.Seats) r.roundStartPos = r.current r.round = 0 r.lastTrucoColor = TcUnknown r.light = r.light[0:0] r.point = r.pointType.GameStartPoint() r.clearAllAct() r.agreeStatus[TcRed] = AtUnknown r.agreeStatus[TcGreen] = AtUnknown r.dealPoker() r.notifyDealPoker() r.NewTimer(TtDealPoker, time.Duration(stub.GGlobal.TrucoDealPokerTime)*time.Second, r.CurrentPlayer()) } // GameStart 开始游戏 func (r *TrucoRoom) GameStart() { r.BaseRoom.GameStart() RoomMgr.CreateClubRoom(r.ClubId(), r.Type(), NewTrucoRoom) r.smallGameStart() } // 决胜局通知决胜方操作 func (r *TrucoRoom) decidingGame() { for _, seat := range r.Seats { if r.teamColor(seat.No()) == r.decidingTeam { seat.SetCanAct(true) log.Debug(r.SeatLog(seat, "玩家可以操作")) } } r.gameStep = GsDecidingAct r.notifyDecidingGame() r.NewTimer(TtDecidingGame, time.Duration(stub.GGlobal.TrucoDecidingGameNtf+stub.GGlobal.TrucoActExTime)*time.Second, r.CurrentPlayer()) // 记录决胜局次数 r.gameLog.Teams[r.decidingTeam].Deciding++ } // 发牌动画结束,如果决胜局,则决胜局广播,非决胜局则通知玩家行动, func (r *TrucoRoom) dealPokerTimerOver() { r.decidingTeam = r.decidingGameTeam() if r.decidingTeam != TcUnknown { r.decidingGame() } else { r.PlayerAct(r.CurrentPlayer()) } } // AutoDecidingAct 决胜手自动行动 func (r *TrucoRoom) AutoDecidingAct() { teamAct := r.lastTeamAct(r.decidingTeam) if teamAct == nil { teamAct = &TeamAct{} } for _, seat := range r.Seats { if r.teamColor(seat.No()) == r.decidingTeam { if _, ok := teamAct.actType[seat.No()]; !ok { msg := util.MakeMessage(proto.ReqPlayerActId, &proto.ReqPlayerAct{Raise: AtGiveUp.ToInt(r.PlayType())}, r.Seats[seat.No()].Player().UID, r.Id()) log.Debug(r.SeatLog(r.Seats[seat.No()], "决胜局自动放弃")) r.OnMessage(proto.ReqPlayerActId, msg) } } } } // 决胜局广播 func (r *TrucoRoom) notifyDecidingGame() { r.isActTeam = true ntf := &proto.NtfDecidingGame{ TeamColor: int(r.decidingTeam), Poker: nil, Countdown: stub.GGlobal.TrucoDecidingGameNtf, CountdownEx: stub.GGlobal.TrucoActExTime, } for _, seat := range r.Seats { //ntf.Seat = seat.No() ntf.Poker = ntf.Poker[0:0] ntf.CanGiveUp = AtUnknown.ToInt(r.PlayType()) ntf.CanAgree = AtUnknown.ToInt(r.PlayType()) if r.teamColor(seat.No()) == r.decidingTeam { ntf.CanGiveUp = AtGiveUp.ToInt(r.PlayType()) ntf.CanAgree = AtAgree.ToInt(r.PlayType()) // 决胜队伍 for _, st := range r.Seats { if r.teamColor(seat.No()) == r.teamColor(st.No()) { seatPoker := &proto.SeatPoker{Seat: st.No()} for _, pk := range st.Pokers { seatPoker.Poker = append(seatPoker.Poker, pk.ToInt()) } ntf.Poker = append(ntf.Poker, seatPoker) } } } else { for _, st := range r.Seats { if r.teamColor(seat.No()) != r.teamColor(st.No()) { seatPoker := &proto.SeatPoker{Seat: st.No()} seatPoker.Poker = append(seatPoker.Poker, 0, 0, 0) ntf.Poker = append(ntf.Poker, seatPoker) } } } r.SendMsg(seat.Player(), proto.NtfDecidingGameId, ntf, false) } // 牌局回顾 ntf.Poker = ntf.Poker[0:0] ntf.CanGiveUp = AtUnknown.ToInt(r.PlayType()) ntf.CanAgree = AtUnknown.ToInt(r.PlayType()) for _, seat := range r.Seats { if r.teamColor(seat.No()) == r.decidingTeam { // 决胜队伍 seatPoker := &proto.SeatPoker{Seat: seat.No()} for _, pk := range seat.Pokers { seatPoker.Poker = append(seatPoker.Poker, pk.ToInt()) } ntf.Poker = append(ntf.Poker, seatPoker) } } // 牌局回顾 r.record.AddAction(proto.NtfDecidingGameId, ntf) } // 检查是否决胜局以及决胜方颜色 func (r *TrucoRoom) decidingGameTeam() TeamColor { if r.points[TcRed] == r.pointType.DecidingPoint() && r.points[TcGreen] != r.pointType.DecidingPoint() { return TcRed } if r.points[TcGreen] == r.pointType.DecidingPoint() && r.points[TcRed] != r.pointType.DecidingPoint() { return TcGreen } return TcUnknown } // 广播发牌 func (r *TrucoRoom) notifyDealPoker() { // 向玩家发送消息 rsp := &proto.NtfDealPokers{ Time: 0, CuttingPoker: 0, Poker: nil, Dealer: r.dealer, GameCount: r.smallGameCount, } if r.pokers.GhostPoker() != nil { rsp.CuttingPoker = r.pokers.GhostPoker().ToInt() } for _, seat := range r.Seats { if seat.Player() != nil { rsp.Seat = seat.No() rsp.Poker = r.playerPokersToPorto(seat.No()) r.SendMsg(seat.Player(), proto.NtfDealPokersId, rsp, false) } } log.Debug(r.SeatLog(r.Seats[r.dealer], "庄家:%v", r.dealer)) rsp.Seat = 0 rsp.Poker = nil for _, seat := range r.Seats { if seat.Player() != nil { var seatPokers []int for _, pk := range seat.Pokers { seatPokers = append(seatPokers, pk.ToInt()) } rsp.Poker = append(rsp.Poker, seatPokers) } } // 牌局回顾 r.record.AddAction(proto.NtfDealPokersId, rsp) } func (r *TrucoRoom) CurrentPlayer() *Player { return r.Seats[r.current].Player() } func (r *TrucoRoom) CurrentSeat() *TrucoSeat { return r.Seats[r.current] } func (r *TrucoRoom) BroadcastToColor(msgId string, msg interface{}, color TeamColor, save bool, exclude ...*Player) { for _, seat := range r.Seats { if !seat.Empty() && r.teamColor(seat.No()) == color { in := false for _, p := range exclude { if seat.Player().UID == p.UID { in = true break } } if !in { r.SendMsg(seat.Player(), msgId, msg, save) } } } } // Teammate 队友 func (r *TrucoRoom) Teammate(seatNo int) (seats []*TrucoSeat) { color := r.teamColor(seatNo) for _, seat := range r.Seats { if !seat.Empty() && seat.No() != seatNo && r.teamColor(seat.No()) == color { seats = append(seats, seat) } } if len(seats) == 0 { log.Error(r.Log("seat:%d has not teammate", seatNo)) } return } func (r *TrucoRoom) NextSeat() { r.current = (r.current + 1) % len(r.Seats) } func (r *TrucoRoom) AutoPlayerOutPoker() { if r.Seats[r.current].Player().IsRobot() { log.Error(r.SeatLog(r.Seats[r.current], "robot can not trigger timer auto out poker")) } // 有牌则出牌 if len(r.Seats[r.current].Pokers) > 0 { card := r.Seats[r.current].Pokers[0] req := proto.ReqPlayerOutPoker{Poker: card.ToInt()} log.Debug(r.SeatLog(r.Seats[r.current], "自动出牌:%v", poker.NewPoker(req.Poker).ToString())) msg := util.MakeMessage(proto.ReqPlayerOutPokerId, req, r.CurrentPlayer().UID, r.Id()) r.OnMessage(proto.ReqPlayerOutPokerId, msg) return } log.Error(r.SeatLog(r.Seats[r.current], "poker was empty")) } // AutoPlayerRspRaise 自动行动 func (r *TrucoRoom) AutoPlayerRspRaise(color TeamColor) { teamAct := r.lastTeamAct(color) for _, seat := range r.Seats { if r.teamColor(seat.No()) == color { if _, ok := teamAct.actType[seat.No()]; !ok { msg := util.MakeMessage(proto.ReqPlayerActId, &proto.ReqPlayerAct{Raise: AtGiveUp.ToInt(r.PlayType())}, r.Seats[seat.No()].Player().UID, r.Id()) log.Debug(r.SeatLog(r.Seats[seat.No()], "自动放弃")) r.OnMessage(proto.ReqPlayerActId, msg) } } } } func (r *TrucoRoom) OnTimer(timerType TimerType, args ...interface{}) { //log.Debug(r.Log("cancel timer:%v by on timer", timerType)) r.CancelTimer(timerType) switch timerType { case TtGameReadyStart: if r.SeatPlayerNum() == r.RoomCnf.MinPlayers && r.Status() != state.RsGaming { r.ReInit() r.GameStart() } case TtDealPoker: r.dealPokerTimerOver() case TtDecidingGame: // 设置队伍托管 r.setTeamHosting(r.decidingTeam, true) r.AutoDecidingAct() case TtPlayerAct: user := args[0].(*Player) if r.CurrentPlayer() == user { // 设置玩家托管 r.setPlayerHosting(user, true) r.AutoPlayerOutPoker() } case TtPlayerRspRaise: color := args[0].(int) // 设置队伍托管 r.setTeamHosting(TeamColor(color), true) r.AutoPlayerRspRaise(TeamColor(color)) case TtCmpPoker: user := args[0].(*Player) r.PlayerAct(user) case TtNextGame: r.smallGameStart() case TtDelDisbandRoomInfo: r.disbandInfo = nil r.disbandTime = 0 } } // PlayerAct 通知玩家行动 func (r *TrucoRoom) PlayerAct(user *Player) { seat := r.GetSeat(user.UID) if seat == nil { log.Error(r.Log("user:%v is not in room", user.UID)) return } if seat.No() != r.current { log.Error(r.SeatLog(r.GetSeat(user.UID), "is not current:%v", r.current)) return } seat.SetCanAct(true) log.Debug(r.SeatLog(seat, "玩家可以操作")) r.notifyPlayerAct() r.NewTimer(TtPlayerAct, time.Duration(r.RoomCnf.ActTime+stub.GGlobal.TrucoActExTime)*time.Second, user) } // 设置队伍托管 func (r *TrucoRoom) setTeamHosting(color TeamColor, enable bool) { for _, seat := range r.Seats { if r.teamColor(seat.No()) == color { r.setPlayerHosting(seat.Player(), enable) } } } // 设置玩家托管 func (r *TrucoRoom) setPlayerHosting(user *Player, enable bool) { if user.IsRobot() { log.Error(r.SeatLog(r.GetSeat(user.UID), "robot cannot set hosting")) return } // 如果状态没有变化,直接返回 if user.IsHosting() == enable { return } // 设置托管机器人 if enable { user.Robot = NewRobotHosting(r, r.GetSeat(user.UID)) } else { user.CleanRobot() } // 通知状态变更 r.Broadcast(proto.NtfHostingId, proto.NtfHosting{Seat: r.GetSeatNo(user.UID), Enable: enable}, false) log.Debug(r.SeatLog(r.GetSeat(user.UID), "玩家托管状态变更: %s", util.Tie(enable, "启用", "关闭"))) } // 通知玩家行动 func (r *TrucoRoom) notifyPlayerAct() { // 向玩家发送消息 rsp := &proto.NtfPlayerAct{ RoomId: r.Id(), Seat: r.current, Countdown: r.RoomCnf.ActTime, CountdownEx: stub.GGlobal.TrucoActExTime, CanCall: 0, CanGiveUp: 0, } color := r.teamColor(r.current) if truco, code := r.canRaise(color, true, false); code == proto.Ok { rsp.CanCall = truco.ToInt(r.PlayType()) } if code := r.canGiveUp(color, true, false); code == proto.Ok { rsp.CanGiveUp = 1 } log.Debug(r.Log("seat:%v 通知出牌, 消息:%+v", r.current, rsp)) r.Broadcast(proto.NtfPlayerActId, rsp, true) r.gameStep = GsPlayerAct r.isActTeam = false // 牌局回顾 r.record.AddAction(proto.NtfPlayerActId, rsp) } // 最近一轮的叫分状态 func (r *TrucoRoom) lastTeamAct(color TeamColor) *TeamAct { if len(r.actTypes) == 0 { return nil } if color == TcUnknown { return r.actTypes[len(r.actTypes)-1] } for end := len(r.actTypes) - 1; end >= 0; end-- { if r.actTypes[end].color == color { return r.actTypes[end] } } return nil } // 队伍的第一个操作 func (r *TrucoRoom) firstTeamAct(color TeamColor) *TeamAct { if len(r.actTypes) == 0 { return nil } if color == TcUnknown { return r.actTypes[0] } for _, act := range r.actTypes { if act.color == color { return act } } return nil } // 是否能放弃 // 一个回合里,一方喊过truco,就不能再放弃。另一方可以放弃 // 对方累积分数+当前分数>=12分则不能放弃 func (r *TrucoRoom) canGiveUp(color TeamColor, isTruco, isClient bool) proto.ErrorCode { // 决胜手队伍在决胜手操作时没有放弃,之后第一回合行动时,不能放弃 if r.points[color] == r.pointType.DecidingPoint() && r.round == 0 { if isClient { log.Error(r.Log("can not sendRaise. our team record point:%v", r.points[color])) } return proto.BadAction } // 对方决胜手,我方不能放弃,不能truco,只能出牌 if r.points[r.rivalColor2(color)] == r.pointType.DecidingPoint() { if isClient { log.Error(r.Log("can not sendRaise. our team record point:%v", r.points[color])) } return proto.BadAction } if r.points[r.rivalColor2(color)]+r.point >= stub.GGlobal.TrucoWinPoint { return proto.BadAction } // 最后操作是放弃或同意,则发起truco方不能再放弃 if act := r.lastTeamAct(TcUnknown); act != nil && (act.maxAct == AtGiveUp || act.maxAct == AtAgree) { if firstAct := r.firstTeamAct(TcUnknown); firstAct != nil && firstAct.color == color { return proto.BadAction } } if isTruco && r.lastTrucoColor == color { return proto.BadAction } return proto.Ok } // 判断是否能加分 func (r *TrucoRoom) canRaise(color TeamColor, isTruco, isClient bool) (ActType, proto.ErrorCode) { // 决胜局不能truco if r.decidingTeam != TcUnknown { if isClient { log.Error(r.Log("can not sendRaise. our team record point:%v", r.points[color])) } return AtUnknown, proto.BadAction } // 叫分不能超过12分 if r.points[color]+r.point >= stub.GGlobal.TrucoWinPoint { if isClient { log.Error(r.Log("can not sendRaise. our team record point:%v, current point:%v is error", r.points[color], r.point)) } return AtUnknown, proto.BadAction } // 本局游戏,对方有同意,则我方不能再发起truco rivalColor := r.rivalColor2(color) if isTruco && r.agreeStatus[rivalColor] == AtAgree { if isClient { log.Error(r.Log("can not sendRaise. rival team was agree")) } return AtUnknown, proto.BadAction } // 一回合中只能有一次truco if act := r.lastTeamAct(TcUnknown); act != nil && isTruco { if isClient { log.Error(r.Log("can not truco. lastTrucoRound==round:%v", r.round)) } return AtUnknown, proto.BadAction } if r.point == r.pointType.GameStartPoint() { return AtTruco3, proto.Ok } return IntToActType(r.pointType.NextPoint(r.point)), proto.Ok } // 判断是否能同意 func (r *TrucoRoom) canAgree(color TeamColor) proto.ErrorCode { // 决胜手队伍在决胜手操作时没有放弃,之后第一回合行动时,不能放弃 if r.points[color] == r.pointType.DecidingPoint() && r.round == 0 { return proto.Ok } if len(r.actTypes) <= 1 { log.Error(r.Log("can not agree. rival team status was not sendRaise")) return proto.BadAction } // 敌方颜色 rivalColor := r.rivalColor2(color) rivalAct := r.lastTeamAct(rivalColor) // 对方没有加分,则我方不能同意 if rivalAct.maxAct == AtAgree || rivalAct.maxAct == AtGiveUp { log.Error(r.Log("can not agree. rival team status was not sendRaise")) return proto.BadAction } return proto.Ok } // 判断是否能同意 func (r *TrucoRoom) canOutPoker() bool { if len(r.actTypes) > 0 { maxAct := r.lastTeamAct(TcUnknown).maxAct if maxAct != AtGiveUp && maxAct != AtAgree { return false } } return true } // 记录玩家叫分操作 func (r *TrucoRoom) gameLogAddAct(color TeamColor, act ActType, seatNo int) { switch act { case AtTruco3: if len(r.actTypes) == 1 { r.gameLog.TrucoPlayers[seatNo].Raise++ } else { r.gameLog.TrucoPlayers[seatNo].ContinueRaise++ } case AtGiveUp: r.gameLog.TrucoPlayers[seatNo].GiveUp++ case AtAgree: r.gameLog.TrucoPlayers[seatNo].Agree++ } if len(r.actTypes) > 1 { teamAct := r.lastTeamAct(color) // 行动一致 concert := true for _, otherAct := range teamAct.actType { if otherAct != act { concert = false } } if concert { r.gameLog.Teams[color].Concert++ } } } // 更新队伍操作及队伍叫分状态 func (r *TrucoRoom) addTeamAct() *TeamAct { teamAct := r.lastTeamAct(TcUnknown) color := r.rivalColor2(teamAct.color) teamAct = newTeamAct(color) r.actTypes = append(r.actTypes, teamAct) return teamAct } // 更新队伍操作及队伍叫分状态 func (r *TrucoRoom) getActTimeType() TimerType { // 首个操作,且不是决胜局,则是玩家主动truco,不需要等待队友操作 if len(r.actTypes) == 1 && r.decidingTeam == TcUnknown { return TtPlayerAct } teamAct := r.lastTeamAct(TcUnknown) // 本队所有人都做出了选择 if len(teamAct.actType) == r.RoomCnf.MinPlayers/2 { // 决胜局首个操作 if len(r.actTypes) == 1 && r.decidingTeam != TcUnknown { return TtDecidingGame } else { return TtPlayerRspRaise } } return TtUnknown } func (r *TrucoRoom) clearAllAct() { r.actTypes = r.actTypes[0:0] } // 对方喊truco,我方同意,对方出牌,轮到我时,我可以放弃 func (r *TrucoRoom) canGiveUpAfterAgree(color TeamColor, act ActType, _ int) bool { if act != AtGiveUp { return false } // 最后操作是放弃或同意,则发起truco方不能再放弃 lastTeamAct := r.lastTeamAct(TcUnknown) if lastTeamAct == nil || lastTeamAct.maxAct != AtAgree { return false } if firstAct := r.firstTeamAct(TcUnknown); firstAct != nil && firstAct.color == color { return false } // 同意后轮到,发起truco方出手,他只能出牌,换手后对方可以放弃,所以这里不需要检查同意与放弃之间是否有人出牌。逻辑上保证了中间一定有人出牌了 return true } // 更新队伍操作及队伍叫分状态 func (r *TrucoRoom) updateTeamAct(color TeamColor, act ActType, seatNo int) { if len(r.actTypes) == 0 && act == AtAgree && r.points[color] != r.pointType.DecidingPoint() { log.Error(r.SeatLog(r.Seats[seatNo], "第一次只能叫分或放弃,不能同意")) return } // 更新叫分状态,第一个叫分或放弃,队友无需操作 if len(r.actTypes) == 0 { teamAct := newTeamAct(color) teamAct.maxAct = act if r.IsMVM() && !r.isActTeam { teammates := r.Teammate(seatNo) for _, st := range teammates { teamAct.actType[st.No()] = act } } r.actTypes = append(r.actTypes, teamAct) } // 通知玩家回应加注时已经添加过teamAct,这里不会为nil teamAct := r.lastTeamAct(color) if teamAct == nil { log.Error(r.SeatLog(r.Seats[seatNo], "team:%v act is nil", color)) return } if _, ok := teamAct.actType[seatNo]; ok { if r.canGiveUpAfterAgree(color, act, seatNo) { teamAct = newTeamAct(color) r.actTypes = append(r.actTypes, teamAct) for _, seat := range r.Seats { if r.teamColor(seat.No()) == color { teamAct.actType[seat.No()] = act } } teamAct.maxAct = act } else { log.Error(r.SeatLog(r.Seats[seatNo], "team:%v repeat operation", color)) return } } teamAct.actType[seatNo] = act if teamAct.maxAct < act { teamAct.maxAct = act } if len(teamAct.actType) == r.RoomCnf.MinPlayers/2 { if len(r.actTypes) > 1 || r.decidingTeam != TcUnknown { if teamAct.maxAct == AtAgree { r.agreeStatus[teamAct.color] = AtAgree } if teamAct.maxAct != AtGiveUp { oldPoint := r.point r.point = r.pointType.NextPoint(r.point) if oldPoint != r.point { log.Debug(r.Log("point from:%v to %v", oldPoint, r.point)) } } } } r.gameLogAddAct(color, act, seatNo) } // 通知玩家回应对方的叫分 func (r *TrucoRoom) notifyPlayerRspRaise() { r.isActTeam = true teamAct := r.addTeamAct() enemyAct := r.lastTeamAct(r.rivalColor2(teamAct.color)) // 向玩家发送消息 rsp := &proto.NtfPlayerRspRaise{ TeamColor: int(teamAct.color), Countdown: r.RoomCnf.ActTime, CountdownEx: stub.GGlobal.TrucoActExTime, Enemy: AtUnknown.ToInt(r.PlayType()), Ally: nil, CanRaise: 0, } point := r.pointType.NextPoint(r.point) log.Debug(r.Log("r.points[teamAct.color]:%v + point:%v<%v", r.points[teamAct.color], point, stub.GGlobal.TrucoWinPoint)) if r.points[teamAct.color]+point < stub.GGlobal.TrucoWinPoint { rsp.CanRaise = IntToActType(r.pointType.NextPoint(point)).ToInt(r.PlayType()) } if enemyAct != nil { rsp.Enemy = enemyAct.maxAct.ToInt(r.PlayType()) } log.Debug(r.Log("通知队伍回应:%v 消息:%+v", teamAct.color, rsp)) r.Broadcast(proto.NtfPlayerRspRaiseId, rsp, true) // 牌局回顾 r.record.AddAction(proto.NtfPlayerRspRaiseId, rsp) } // 回合结束,判断是否需要进入比牌 func (r *TrucoRoom) isRoundOver() bool { for _, seat := range r.Seats { if seat.OutPoker == nil { return false } } return true } // 比牌,返回最大牌的位置及队伍颜色,以及相同大牌的座位 // cmpResult只有相等才需要处理(平局) func (r *TrucoRoom) cmpPoker() (maxPokerSeatNo int, maxColor TeamColor, cmpResult GameLight, maxPokerSeats []int) { begin := r.roundStartPos maxPokerSeatNo = begin // 找出双方各自最大牌 maxSeats := make([]*TrucoSeat, 2) for i := begin; i < begin+len(r.Seats); i++ { seatNo := i % len(r.Seats) if r.Seats[seatNo].Empty() { continue } color := r.teamColor(seatNo) if maxSeats[color] == nil { maxSeats[color] = r.Seats[seatNo] } else { // Cmp p1 0 { return nil, false } uid := seat.Player().UID if !model.UserIsBankrupt(uid, myWin) { return nil, false } ntf, err := model.NewBankruptNtfMsg(uid) if err != nil { log.Error(fmt.Sprintf("生成破产通知失败:%v", err)) return nil, false } return ntf, true } // 按座位结算 func (r *TrucoRoom) bigSettlementBySeat(seat *TrucoSeat, myWin, tip, inoutAdd, moneyAdd int64) (moneyCost, freeCost, inoutCost int64) { r.addResource(seat.Player(), myWin, model.ResTakeCoins, model.ReasonGame) if r.ClubId() > 0 { if myWin < 0 { moneyCost, freeCost, inoutCost = r.CostUserClubScore(seat.Player().UID, -myWin) moneyCost = -moneyCost freeCost = -freeCost inoutCost = -inoutCost log.Debug(r.SeatLog(seat, "净胜分:%v 其中免费积分:%v 可提现积分:%v 充值积分:%v", myWin, freeCost, inoutCost, moneyCost)) } else { // tip先扣可提现积分 inoutTip := util.Tie(inoutAdd < tip, inoutAdd, tip) inoutAdd -= inoutTip tip -= inoutTip // 最后扣俱乐部积分 moneyAdd -= tip r.AddUserResource(seat.Player().UID, moneyAdd, model.ResClubUserScore, model.ReasonGame) r.AddUserResource(seat.Player().UID, inoutAdd, model.ResClubUserInOutFreeScore, model.ReasonGame) moneyCost = moneyAdd inoutCost = inoutAdd log.Debug(r.SeatLog(seat, "净胜分:%v 其中免费积分:%v 可提现积分:%v 充值积分:%v", myWin, freeCost, inoutCost, moneyCost)) } } else { r.addResource(seat.Player(), myWin, model.ResCoins, model.ReasonGame) } return } // 按队伍颜色结算 cost:扣除金币 tip:台费 func (r *TrucoRoom) bigSettlementByColor(winColor TeamColor, cost, tip int64) []*proto.GameRecordPlayerInfo { gameRecordPlayerInfos := make([]*proto.GameRecordPlayerInfo, 0) var inoutAdd, moneyAdd, myWin int64 // 先结算输家 for _, seat := range r.Seats { if seat.Empty() || r.teamColor(seat.No()) == winColor { continue } myWin = -cost aMoneyAdd, aFreeAdd, aInoutAdd := r.bigSettlementBySeat(seat, myWin, 0, 0, 0) inoutAdd -= aFreeAdd + aInoutAdd moneyAdd -= aMoneyAdd gameRecordPlayerInfos = append(gameRecordPlayerInfos, &proto.GameRecordPlayerInfo{UId: seat.Player().UID, Win: myWin, Disband: int(seat.disband)}) r.gameLog.TrucoPlayers[seat.No()].FreeScore = aFreeAdd r.gameLog.TrucoPlayers[seat.No()].InoutScore = aInoutAdd r.gameLog.TrucoPlayers[seat.No()].MoneyScore = aMoneyAdd } dividend := int64(len(r.Seats) / 2) // 每人能分多少分 1v1分对方的全部,2v2每人分总数的1/2 // 再结算赢家 for _, seat := range r.Seats { if seat.Empty() || r.teamColor(seat.No()) != winColor { continue } myWin = cost - tip aInoutAdd, aMoneyAdd := inoutAdd/dividend, moneyAdd/dividend aMoneyAdd, _, aInoutAdd = r.bigSettlementBySeat(seat, myWin, tip, aInoutAdd, aMoneyAdd) gameRecordPlayerInfos = append(gameRecordPlayerInfos, &proto.GameRecordPlayerInfo{UId: seat.Player().UID, Win: myWin, Disband: int(seat.disband)}) r.gameLog.TrucoPlayers[seat.No()].InoutScore = aInoutAdd r.gameLog.TrucoPlayers[seat.No()].MoneyScore = aMoneyAdd } return gameRecordPlayerInfos } func (r *TrucoRoom) bigSettlement() { winColor := util.Tie[TeamColor](r.points[GlRed] > r.points[GlGreen], TcRed, TcGreen) // 扣除金币 cost := r.RoomCnf.Blind * int64(r.star) // 台费 tip := cost * int64(r.RoomCnf.Rate) / int64(100) gameRecordPlayerInfos := r.bigSettlementByColor(winColor, cost, tip) var teamMembers []*proto.TeamMember bankruptSeats := map[int]*proto.NtfBankrupt{} allTip := int64(0) for _, seat := range r.Seats { if seat.Empty() { continue } // 净胜分 myWin := util.Tie(r.teamColor(seat.No()) == winColor, cost-tip, -cost) if myWin > 0 { allTip += tip } r.gameLog.TrucoPlayers[seat.No()].Tip = tip //log.Info(r.SeatLog(seat, "赢取金币:%v", myWin)) // 机器人输赢汇入总池 if seat.Player().IsRobot() { model.AddRobotResourcePool(r.Type(), myWin, util.Tie[string](r.ClubId() <= 0, model.ResCoins, model.ResClubUserScore)) } if ntf, ok := r.isBankrupt(seat, myWin); ok { bankruptSeats[seat.No()] = ntf } //r.addResource(seat.Player(), myWin, model.ResTakeCoins, model.ReasonGame) //r.addResource(seat.Player(), myWin, model.ResCoins, model.ReasonGame) color := r.teamColor(seat.No()) teamMembers = append(teamMembers, &proto.TeamMember{ Seat: seat.No(), WinCoin: myWin, Color: int(color), Fee: util.Tie(myWin > 0, tip, 0), }) log.Info(r.SeatLog(seat, "净胜分:%v take coin:%v", myWin, r.GetTakeCoin(seat.Player()))) } for _, seat := range r.Seats { if seat.Empty() { continue } r.notifySettlement(teamMembers, seat, winColor) if ntf, ok := bankruptSeats[seat.No()]; ok { r.SendMsg(seat.Player(), proto.NtfBankruptId, ntf, false) sum, err := model.NewUserStreakOp().UpdateBankruptStreak(seat.Player().UID) if sum >= 3 { // TODO 可以提取到stub // 连续破产三次 _, err = model.NewUserLuckyPointOp().Add(seat.Player().UID, 20) log.Debug(fmt.Sprintf("连续破产%d次,增加幸运值", sum)) } if err != nil { log.Error(fmt.Sprintf("更新连续破产失败:%v", err)) } } } r.ClubSettlement(allTip) r.SaveRecord(gameRecordPlayerInfos) } func (r *TrucoRoom) notifySettlement(members []*proto.TeamMember, seat *TrucoSeat, winColor TeamColor) { ntf := &proto.NtfGameBigSettle{ Team: members, Star: r.star, Seat: seat.No(), Color: int(r.teamColor(seat.No())), Rate: r.RoomCnf.Rate, Blind: r.RoomCnf.Blind, Win: util.Tie(winColor == r.teamColor(seat.No()), 1, 0), Points: r.points, } //log.Debug(r.Log("NtfGameBigSettle:%+v", ntf)) r.SendMsg(seat.Player(), proto.NtfGameBigSettleId, ntf, false) r.record.AddAction(proto.NtfGameBigSettleId, ntf) var err error if ntf.Win == 1 { // 赢了,清除连败 err = model.NewUserStreakOp().SetLossStreak(seat.Player().UID, stub.Truco, 0) } else { // 记录连败 var loss int64 loss, err = model.NewUserStreakOp().IncLossStreak(seat.Player().UID, stub.Truco, 1) if loss >= 3 { // 连续输掉三次,增加幸运值 // TODO 可以提取到stub _, err = model.NewUserLuckyPointOp().Add(seat.Player().UID, 20) log.Debug(fmt.Sprintf("连续输掉%d次,增加幸运值", loss)) } } if err != nil { log.Error(fmt.Sprintf("更新连败失败:%v", err)) } } // 广播结算 func (r *TrucoRoom) notifyGameSettle(redWin, greenWin int, nextGame bool) { ng := 0 if nextGame { ng = 1 } // 向玩家发送消息 rsp := &proto.NtfGameSettle{Settle: make([]int, 2), NextGame: ng} rsp.Settle[GlRed] = redWin rsp.Settle[GlGreen] = greenWin r.Broadcast(proto.NtfGameSettleId, rsp, false) // 牌局回顾 r.record.AddAction(proto.NtfGameSettleId, rsp) } func (r *TrucoRoom) settlePoint() (redWin, greenWin int) { redLightCount := 0 greenLightCount := 0 for _, light := range r.light { if light == GlRed { redLightCount++ } else if light == GlGreen { greenLightCount++ } } if redLightCount > greenLightCount { redWin = r.point } else if redLightCount == greenLightCount { // 平平平,双方不得分 if redLightCount == 0 { return } else { // 胜负平,先胜为胜 if r.light[0] == GlRed { redWin = r.point } else { greenWin = r.point } } } else { greenWin = r.point } return } // 是否显示按钮 func (r *TrucoRoom) notifyDisplayShowPoker() int { _ = stub.GGlobal.TrucoNextGameTime cd := stub.GGlobal.TrucoNextGameTime for _, seat := range r.Seats { if len(seat.Pokers) > 0 { cd = stub.GGlobal.TrucoNextGameTime + stub.GGlobal.TrucoShowPoker break } } //display := 0 //for _, seat := range r.Seats { // display = util.Tie(len(seat.handPoker) > 0, 1, 0) // r.SendMsg(seat.Player(), proto.NtfCanShowPokerId, &proto.NtfCanShowPoker{Display: display, CountDown: cd}, false) //} return cd } // GameOver 本局游戏结束,会清理部分数据 func (r *TrucoRoom) GameOver(seat *TrucoSeat) { r.gameStep = GsGameSettle //log.Debug(r.Log("cancel timer:all timer")) r.CancelAllTimer() clear(r.LastMsg) redWin := 0 greenWin := 0 if seat == nil { redWin, greenWin = r.settlePoint() } else { // 投降 color := r.teamColor(seat.No()) if color == TcRed { greenWin = r.point } else { redWin = r.point } } //log.Debug(r.Log("清理所有出牌")) // 清理玩家出牌 for _, st := range r.Seats { st.OutPoker = nil } r.points[GlRed] += redWin r.points[GlGreen] += greenWin if r.points[GlRed] > stub.GGlobal.TrucoWinPoint { r.points[GlRed] = stub.GGlobal.TrucoWinPoint } if r.points[GlGreen] > stub.GGlobal.TrucoWinPoint { r.points[GlGreen] = stub.GGlobal.TrucoWinPoint } log.Debug(r.Log("对局结束,双方比分:%v", r.points)) nextGame := false if r.points[GlRed] < stub.GGlobal.TrucoWinPoint && r.points[GlGreen] < stub.GGlobal.TrucoWinPoint { nextGame = true nextGameCd := r.notifyDisplayShowPoker() r.notifyGameSettle(r.points[GlRed], r.points[GlGreen], nextGame) // 开启下一局 r.NewTimer(TtNextGame, time.Duration(nextGameCd)*time.Second) return } // 游戏结束 r.endTime = time.Now() r.notifyGameSettle(r.points[GlRed], r.points[GlGreen], nextGame) // 大结算 r.bigSettlement() r.gameStep = GsWaitGame // 清理玩家信息 for _, st := range r.Seats { if !st.Empty() { r.leaveRoom(st) } } r.TryReleaseRoom() } // 大牌 func (r *TrucoRoom) isBigPoker(pk *poker.Poker) bool { return r.pokers.IsBigPoker(pk, stub.GGlobalAI.TrucoBiggerPoker) } // 离开房间 func (r *TrucoRoom) SetFakeLeave(uid int64, fake bool) { seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } if fake { log.Debug(r.SeatLog(seat, "玩家假离开, room status:%v", r.gameStep)) seat.SetFakeLeave(true) } else { log.Debug(r.SeatLog(seat, "玩家假离开后返回, room status:%v", r.gameStep)) seat.SetFakeLeave(false) } r.notifyFakeLeave(seat) } // 推送房间信息给玩家 func (r *TrucoRoom) notifyFakeLeave(seat *TrucoSeat) { ntf := &proto.NtfFakeLeave{ RoomId: r.Id(), CurrentSeat: seat.No(), FakeLeave: util.Tie(seat.FakeLeave(), 0, 1), } r.Broadcast(proto.NtfFakeLeaveId, ntf, false) } // 推送房间信息给玩家 func (r *TrucoRoom) notifyRoomInfo(user *Player) { ntf := r.makeProtoRoomInfo() r.SendMsg(user, proto.NtfRoomInfoId, ntf, false) } // 推送玩家信息给房间里的玩家 func (r *TrucoRoom) notifyMeToOthers(user *Player) { ntf := proto.NtfPlayerInfo{Players: make([]*proto.Player, 0)} ntf.Players = append(ntf.Players, r.makePlayerToProto(r.GetSeat(user.UID), true)) for _, seat := range r.Seats { if seat.Player() != nil && seat.Player().UID != user.UID { r.SendMsg(seat.Player(), proto.NtfPlayerInfoId, ntf, false) } } } // 推送玩家信息给房间里的玩家 func (r *TrucoRoom) notifyOthersToMe(user *Player) { ntf := proto.NtfPlayerInfo{Players: make([]*proto.Player, 0)} for _, seat := range r.Seats { if seat.Player() != nil { ntf.Players = append(ntf.Players, r.makePlayerToProto(seat, true)) } } r.SendMsg(user, proto.NtfPlayerInfoId, ntf, false) } // 推送玩家信息给房间里的玩家 func (r *TrucoRoom) notifyPlayerInfo(user *Player, isReentry bool) { if !isReentry { r.notifyMeToOthers(user) } r.notifyOthersToMe(user) } func (r *TrucoRoom) SetGmPokers(uid int64, pokers [][]int, ghostPoker int) bool { no := 0 for _, seat := range r.Seats { if seat.Player() != nil && seat.Player().UID == uid { no = seat.No() break } } log.Debug(r.SeatLog(r.Seats[no], "玩家:%v seat:%v 配牌:%+v 鬼牌:%v", uid, no, pokers, ghostPoker)) r.pokers.SetGmPoker(pokers, ghostPoker, no) r.pokers.Pokers() return true } func (r *TrucoRoom) SetRobotTemper(seatNo int, cnf stub.RobotTemper) { seat := r.Seats[seatNo] user := seat.Player() if !seat.Empty() && seat.Player().IsRobot() { switch cnf { case stub.RteAplomb: user.Robot = NewRobotAplomb(cnf, user.UserInfo, seat, r) case stub.RteNormal: user.Robot = NewRobotNormal(cnf, user.UserInfo, seat, r) case stub.RteRadical: user.Robot = NewRobotRadical(cnf, user.UserInfo, seat, r) case stub.RteRotten: user.Robot = NewRobotRotten(cnf, user.UserInfo, seat, r) case stub.RteEvaluation: user.Robot = NewRobotEvaluation(cnf, user.UserInfo, seat, r) default: log.Error(r.SeatLog(seat, "初始化机器人失败:%v", cnf.String())) } log.Debug(r.SeatLog(seat, "初始化%v机器人", cnf.String())) } }