samba/server/truco/room/trucoRoom.go
2025-06-04 09:51:39 +08:00

1688 lines
50 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<p2:CppSmall
cmpRet := r.pokers.Cmp(maxSeats[color].OutPoker, r.Seats[seatNo].OutPoker)
if cmpRet == poker.CppSmall {
maxSeats[color] = r.Seats[seatNo]
}
}
}
// 比较红绿双方谁大谁小
cmpRet := r.pokers.Cmp(maxSeats[TcRed].OutPoker, maxSeats[TcGreen].OutPoker)
// Cmp p1<p2:CppSmall
switch cmpRet {
case poker.CppSmall:
maxPokerSeatNo = r.Seats[TcGreen].No()
cmpResult = GlGreen
maxColor = r.teamColor(maxPokerSeatNo)
case poker.CppBig:
maxPokerSeatNo = r.Seats[TcRed].No()
cmpResult = GlRed
maxColor = r.teamColor(maxPokerSeatNo)
case poker.CppEqual:
/*
普通平/普通平/普通平,最终结果为平
普通平/普通平/(特殊平),最终要决胜负
普通平/特殊平/普通平最终要决胜负需要拉取之前喊truco和接受的数据
普通平/特殊平/(特殊平),最终要决胜负
特殊平/普通平/普通平最终要决胜负需要拉取之前喊truco和接受的数据
特殊平/普通平/(特殊平),最终要决胜负
特殊平/(特殊平),最终要决胜负
*/
switch r.round {
case 0:
/*
第一轮:
无限制会出现普通平或者特殊平即第一轮中无论是否喊了truco如果双方出了一样的牌就算平局
*/
switch r.lastTrucoColor {
case TcUnknown:
cmpResult = GlNormalYellow
default:
cmpResult = util.Tie(r.lastTrucoColor == TcGreen, GlGreenYellow, GlRedYellow)
}
case 1:
/*
第二轮:
假如双方出了一样的牌那么需要首先检测【有没有发生过特殊平】其次要检测【这轮有没有喊过truco】
1.如果之前没有发生过特殊平且这轮也没有喊过,那这轮就是普通平
2.如果之前没有发生过特殊平但是这轮有喊过,那这轮就是特殊平
3.如果之前发生过特殊平但是本轮没有喊过,那这轮就是普通平
4.如果之前发生过特殊平并且本轮也喊过,那就一定要决胜负,就不会有第三轮了
*/
if r.light[0] == GlNormalYellow { // 第一轮普通平,本轮普通平或特殊平
switch r.lastTrucoColor {
case TcUnknown:
cmpResult = GlNormalYellow
default:
cmpResult = util.Tie(r.lastTrucoColor == TcGreen, GlGreenYellow, GlRedYellow)
}
} else if r.light[0] == GlGreenYellow || r.light[0] == GlRedYellow { // 第一轮特殊平,本轮要么普通平,要么决胜负
switch r.lastTrucoColor {
case TcUnknown:
cmpResult = GlNormalYellow
default:
// 特殊平时喊truco方判定输
cmpResult = util.Tie(r.lastTrucoColor == TcGreen, GlRed, GlGreen)
}
} else {
switch r.lastTrucoColor {
case TcUnknown:
cmpResult = GlNormalYellow
default:
cmpResult = util.Tie(r.lastTrucoColor == TcGreen, GlGreenYellow, GlRedYellow)
}
}
default:
/*
第三轮:
假如双方出了一样的牌那么需要首先检测【有没有发生过特殊平】其次要检测【这轮有没有喊过truco】
1.如果之前没有发生过特殊平且这轮也没有喊过,那这轮就是普通平
2.如果之前没有发生过特殊平但是这轮有喊过,【那必须要决胜负】
3.如果之前发生过特殊平但是本轮没有喊过,【那必须要决胜负】
4.如果之前发生过特殊平并且本轮也喊过,那就一定要决胜负
*/
if r.light[0] == GlNormalYellow && r.light[1] == GlNormalYellow {
if r.lastTrucoColor == TcUnknown {
cmpResult = GlNormalYellow
} else {
cmpResult = util.Tie(r.lastTrucoColor == TcGreen, GlRed, GlGreen)
}
} else {
if r.lastTrucoColor == TcUnknown {
// 前两轮一定有一个特殊平,先找出特殊平
cmpResult = util.Tie(r.light[0] == GlNormalYellow, r.light[1], r.light[0])
// 特殊平时喊truco方判定输
cmpResult = util.Tie(cmpResult == GlGreenYellow, GlRed, GlGreen)
} else {
cmpResult = util.Tie(r.lastTrucoColor == TcGreen, GlRed, GlGreen)
}
}
}
if cmpResult.Color() == GlNormalYellow {
maxColor = r.teamColor(begin)
maxPokerSeatNo = r.Seats[maxColor].No()
} else {
maxColor = util.Tie(cmpResult == GlRed, TcRed, TcGreen)
maxPokerSeatNo = r.Seats[maxColor].No()
}
}
for _, seat := range r.Seats {
cmpRet = r.pokers.Cmp(r.Seats[maxPokerSeatNo].OutPoker, seat.OutPoker)
if cmpRet == poker.CppEqual {
maxPokerSeats = append(maxPokerSeats, seat.No())
}
}
return
}
// 比牌时打印玩家牌
func (r *TrucoRoom) debugAllOutPoker() string {
begin := r.current
outPokers := "" // 各玩家出的牌
for i := begin; i < begin+len(r.Seats); i++ {
no := i % len(r.Seats)
if r.Seats[no].Empty() {
continue
}
outPokers += fmt.Sprintf("seat:%v poker:%v ", no, r.Seats[no].OutPoker.ToString())
}
return outPokers
}
// 回合结算广播
func (r *TrucoRoom) notifyRoundSettle(color TeamColor, cmpResult GameLight, maxSeatNo []int) {
//log.Debug(r.Log("比牌"))
r.gameStep = GsCmpPoker
r.light = append(r.light, cmpResult)
ntf := &proto.NtfRoundSettle{MaxSeat: maxSeatNo}
// (0:红灯 1:绿灯 2:黄灯)
switch cmpResult {
case GlGreen:
ntf.Settle = 1
case GlRed:
ntf.Settle = 0
default:
ntf.Settle = 2
}
log.Debug(r.Log("%v队胜率 广播比牌:%v", color, ntf))
r.Broadcast(proto.NtfRoundSettleId, ntf, false)
r.round++
r.lastTrucoColor = TcUnknown
// 牌局回顾
r.record.AddAction(proto.NtfRoundSettleId, ntf)
}
// 回合结算返回true则小结算后进入大结算
func (r *TrucoRoom) roundSettle() bool {
seatNo, color, cmpResult, maxSeatNos := r.cmpPoker()
// 比牌打印
log.Debug(r.Log("比牌,所有出牌:%v, 比牌规则:%v", r.debugAllOutPoker(), r.pokers.DebugSortPoker()))
r.current = seatNo
r.roundStartPos = r.current
r.notifyRoundSettle(color, cmpResult, maxSeatNos)
if len(r.light) == 2 {
// 2红或2绿则直接本局结算
if r.light[0] == r.light[1] && r.light[0].Color() != GlNormalYellow {
return true
}
// 1胜1平则直接本局结算
if r.light[0].Color() != GlNormalYellow && r.light[1].Color() == GlNormalYellow {
return true
}
// 1平1胜则直接本局结算
if r.light[0].Color() == GlNormalYellow && r.light[1].Color() != GlNormalYellow {
return true
}
}
if len(r.Seats[seatNo].Pokers) == 0 {
return true
}
log.Debug(r.Log("清理所有出牌"))
// 清理所有玩家出的牌
for _, seat := range r.Seats {
seat.OutPoker = nil
}
// 清理本回合所有叫分操作
r.clearAllAct()
r.NewTimer(TtCmpPoker, time.Duration(stub.GGlobal.TrucoCmpPokerTime)*time.Second, r.Seats[seatNo].Player())
return false
}
// 保存战绩
func (r *TrucoRoom) SaveRecord(gameRecordPlayerInfos []*proto.GameRecordPlayerInfo) {
// 为nil表示解散房间保存的战绩
if len(gameRecordPlayerInfos) == 0 {
for _, seat := range r.Seats {
if seat.Empty() {
continue
}
gameRecordPlayerInfos = append(gameRecordPlayerInfos, &proto.GameRecordPlayerInfo{UId: seat.Player().UID, Win: 0, Disband: int(seat.disband)})
}
}
for _, seat := range r.Seats {
if seat.Empty() {
continue
}
var playerInfo *proto.GameRecordPlayerInfo
for _, info := range gameRecordPlayerInfos {
if info.UId == seat.Player().UID {
playerInfo = info
break
}
}
if playerInfo != nil {
// 牌局回顾
r.record.AddPlayerRecord(r, seat.Player().UID, r.GetTakeCoin(seat.Player()), playerInfo.Win, gameRecordPlayerInfos)
if r.ClubId() == 0 {
r.gameLog.TrucoPlayers[seat.No()].EndCoin = r.GetTakeCoin(seat.Player())
} else {
sumScore, _, _, _ := r.GetUserClubScore(seat.Player().UID)
r.gameLog.TrucoPlayers[seat.No()].EndCoin = sumScore
}
r.gameLog.TrucoPlayers[seat.No()].WinCoin = playerInfo.Win
}
}
r.gameLog.EndTime = r.endTime.Unix()
r.record.Flush()
r.gameLog.Flush()
}
// 破产判断
func (r *TrucoRoom) isBankrupt(seat *TrucoSeat, myWin int64) (*proto.NtfBankrupt, bool) {
if seat.Player().IsRobot() {
return nil, false
}
if r.ClubId() > 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()))
}
}