package room import ( "game/common/baseroom" "game/common/config/game" "game/common/proto/pb" "game/server/colorgame/config" "game/server/colorgame/model" "github.com/fox/fox/ksync" "github.com/fox/fox/log" "github.com/fox/fox/xrand" "sort" "sync" "time" ) const ( ReasonBet = "bet" // 扣钱原因:投注 ReasonSettle = "settle" // 扣钱或加钱原因:结算 ) func (rm *ColorRoom) setStatus(status pb.ColorGameStatus) { rm.status = status rm.statusTime = time.Now().UnixMilli() } // 状态结束时间点,毫秒 func (rm *ColorRoom) endTimeStatus() int64 { duration := int64(0) switch rm.status { case pb.ColorGameStatus_CGS_Start: duration = rm.timingCfg.Start case pb.ColorGameStatus_CGS_Betting: duration = rm.timingCfg.Betting case pb.ColorGameStatus_CGS_BetEnd: duration = rm.timingCfg.EndBetting case pb.ColorGameStatus_CGS_OpenThreeDice: duration = rm.timingCfg.OpenThreeDice case pb.ColorGameStatus_CGS_Settle: duration = rm.timingCfg.Settle } return rm.statusTime + duration } // 游戏开始前更新配置 func (rm *ColorRoom) updateConfig() { colorConfig := config.GetColorConfig() rm.timingCfg = colorConfig.GameTiming for _, cfg := range colorConfig.Rooms { if rm.RoomType() == cfg.RoomType { rm.roomCfg = cfg break } } } // 游戏开始时展示每个区域的默认赔率 func (rm *ColorRoom) initEndBetAreaMul() { rm.endBetAreaMul = rm.endBetAreaMul[0:0] for pos := 0; pos < len(pb.ColorBetArea_name); pos++ { prizeArea := pb.ColorPrizeArea_CPA_Single_0 var mul = rm.roomCfg.WinSingleColorMul[0] switch pos / 6 { case 1: prizeArea = pb.ColorPrizeArea_CPA_Double mul = rm.roomCfg.WinDoubleColorMul case 2: prizeArea = pb.ColorPrizeArea_CPA_Three mul = rm.roomCfg.WinThreeColorMul } rm.endBetAreaMul = append(rm.endBetAreaMul, &pb.ColorBetAreaMul{ Area: pb.ColorBetArea(pos), PrizeArea: prizeArea, PrizeType: pb.ColorPrizeType_CPT_Normal, Mul: mul[0].Mul / config.Hundred, }) } } // 随机出某个奖励档位下的赔率数组,档位,赔率数组概率之和 func (rm *ColorRoom) randArrMul(area pb.ColorBetArea) (arrMul []*game.ColorMulRate, prizeArea pb.ColorPrizeArea, sumRate int) { switch area / 6 { case 0: maxWeight := 0 for _, w := range rm.roomCfg.WinSingleColorWeight { maxWeight += w } weight := xrand.RandRange[int](0, maxWeight) for pos, w := range rm.roomCfg.WinSingleColorWeight { if weight < w { prizeArea = pb.ColorPrizeArea(pos) arrMul = rm.roomCfg.WinSingleColorMul[pos] break } weight -= w } case 1: prizeArea = pb.ColorPrizeArea_CPA_Double arrMul = rm.roomCfg.WinDoubleColorMul case 2: prizeArea = pb.ColorPrizeArea_CPA_Three arrMul = rm.roomCfg.WinThreeColorMul default: log.Error("area:%v is not exist") return } for _, mr := range arrMul { sumRate += mr.Rate } return } // 下注结束后,更新每个区域的实际赔率 func (rm *ColorRoom) updateEndBetAreaMul() { jackpotExist := false for pos := range pb.ColorBetArea_name { bam := rm.endBetAreaMul[pos] arrMul, prizeArea, sumRate := rm.randArrMul(pb.ColorBetArea(pos)) bam.PrizeArea = prizeArea bam.PrizeType = pb.ColorPrizeType_CPT_Normal if prizeArea == pb.ColorPrizeArea_CPA_Single_2 { // 在单色区域 命中三同色爆奖之后 再随jackpot概率 // 只会有一个区域有jackpot标签 if !jackpotExist && xrand.RandRange(0, config.RateBase) < rm.roomCfg.JackpotRate { bam.Mul = 0 bam.PrizeType = pb.ColorPrizeType_CPT_Jackpot jackpotExist = true continue } } mulPos := 0 rd := xrand.RandRange[int](0, sumRate) for i, mr := range arrMul { if rd < mr.Rate { mulPos = i } rd -= mr.Rate } // 赔率数组里第一个是基础赔率,后面的都是爆奖赔率 if mulPos > 0 { bam.PrizeType = pb.ColorPrizeType_CPT_Big } bam.Mul = arrMul[mulPos].Mul / config.Hundred log.Debug(rm.Log("区域:%v 显示赔率:%v 奖励类型:%v 奖励档位:%v", bam.Area, bam.Mul, bam.PrizeType, bam.PrizeArea)) } } // 获取三个骰子指定颜色中了几个 func (rm *ColorRoom) getDiceColorCount(color []pb.ColorType, c pb.ColorType) (count int) { for _, colorType := range color { if colorType == c { count++ } } return } // 获取投注区域的颜色 func (rm *ColorRoom) getAreaColor(area pb.ColorBetArea) (color pb.ColorType) { switch area { case pb.ColorBetArea_CBA_Yellow, pb.ColorBetArea_CBA_Yellow2, pb.ColorBetArea_CBA_Yellow3: return pb.ColorType_CT_Yellow case pb.ColorBetArea_CBA_White, pb.ColorBetArea_CBA_White2, pb.ColorBetArea_CBA_White3: return pb.ColorType_CT_White case pb.ColorBetArea_CBA_Pink, pb.ColorBetArea_CBA_Pink2, pb.ColorBetArea_CBA_Pink3: return pb.ColorType_CT_Pink case pb.ColorBetArea_CBA_Blue, pb.ColorBetArea_CBA_Blue2, pb.ColorBetArea_CBA_Blue3: return pb.ColorType_CT_Blue case pb.ColorBetArea_CBA_Red, pb.ColorBetArea_CBA_Red2, pb.ColorBetArea_CBA_Red3: return pb.ColorType_CT_Red case pb.ColorBetArea_CBA_Green, pb.ColorBetArea_CBA_Green2, pb.ColorBetArea_CBA_Green3: return pb.ColorType_CT_Green } return } // 获取某个区域的实际中奖赔率 func (rm *ColorRoom) getAreaMul(area pb.ColorBetArea, prizeArea pb.ColorPrizeArea) (mul int64, prizeType pb.ColorPrizeType) { bam := rm.endBetAreaMul[area] // 奖励档位一样,比如单黄投注区域,显示双色爆奖,赔率为9。此时开出双黄色,则该投注区域赔率为9。 // 如果开出三黄色,则赔率为CPA_Single_2的赔率组里的第1个赔率(基础赔率) // 如果开出单黄色,则赔率为CPA_Single_0的赔率组里的第1个赔率(基础赔率) if bam.PrizeArea == prizeArea { mul = bam.Mul prizeType = bam.PrizeType } else { arr2Mul := make([][]*game.ColorMulRate, 0, 5) arr2Mul = append(arr2Mul, rm.roomCfg.WinSingleColorMul...) arr2Mul = append(arr2Mul, rm.roomCfg.WinDoubleColorMul) arr2Mul = append(arr2Mul, rm.roomCfg.WinThreeColorMul) mul = arr2Mul[prizeArea][0].Mul prizeType = pb.ColorPrizeType_CPT_Normal } return } // 检查投掷结果是否匹配某个下注区域 func (rm *ColorRoom) isWinInArea(color []pb.ColorType, area pb.ColorBetArea) (win bool, prizeArea pb.ColorPrizeArea) { colorCount := rm.getDiceColorCount(color, rm.getAreaColor(area)) if colorCount == 0 { return false, 0 } // 单色投注区,查看开出几个该颜色 if area < pb.ColorBetArea_CBA_Yellow2 { if colorCount == 1 { return true, pb.ColorPrizeArea_CPA_Single_0 } else if colorCount == 2 { return true, pb.ColorPrizeArea_CPA_Single_1 } else { return true, pb.ColorPrizeArea_CPA_Single_2 } } else if area < pb.ColorBetArea_CBA_Yellow3 { if colorCount > 1 { return true, pb.ColorPrizeArea_CPA_Double } } else { if colorCount > 2 { return true, pb.ColorPrizeArea_CPA_Three } } return false, 0 } // 开3个骰子,并计算出赢钱区域及实际赔率 func (rm *ColorRoom) openDices() { for i := 0; i < 3; i++ { c := xrand.RandRange(int(pb.ColorType_CT_Yellow), int(pb.ColorType_CT_Green)) rm.ntfOpenThreeDice.Color = append(rm.ntfOpenThreeDice.Color, pb.ColorType(c)) } rm.ntfOpenThreeDice.AniRouteIndex = 0 for _, area := range rm.endBetAreaMul { isWin, prizeArea := rm.isWinInArea(rm.ntfOpenThreeDice.Color, area.Area) mul, prizeType := rm.getAreaMul(area.Area, prizeArea) if !isWin { continue } rm.winBetAreaMul = append(rm.winBetAreaMul, &pb.ColorBetAreaMul{ Area: area.Area, PrizeArea: prizeArea, PrizeType: prizeType, Mul: mul, }) rm.ntfOpenThreeDice.WinArea = append(rm.ntfOpenThreeDice.WinArea, area.Area) } } // 计算 下注区域中奖得分,返回是否有jp奖,jp奖位置,中奖人数 func (rm *ColorRoom) calculateJackpotScore() (pb.ColorBetArea, map[int64]int64) { jackpotArea := pb.ColorBetArea(-1) for _, winArea := range rm.winBetAreaMul { if winArea.PrizeType != pb.ColorPrizeType_CPT_Jackpot { continue } rm.RangePlayer(func(u baseroom.IPlayer) bool { user := GetPlayer(u) if user.totalBets[winArea.Area] > 0 { rm.jackpotMgr.AddJpUser(user.ID, user.totalBets[winArea.Area]) } return true }) } rm.jackpotUser, rm.jackpotValue = rm.jackpotMgr.WinJackpot() log.Debug(rm.Log("本局是否中jackpot奖:%v, 玩家一起分走jackpot:%v, 当前jackpot值:%v", rm.jackpotValue > 0, rm.jackpotValue, rm.jackpotMgr.GetJackpot())) return jackpotArea, rm.jackpotUser } // 计算 所有玩家的下注区域中奖得分 func (rm *ColorRoom) calculateAllUserScore() { // 赢钱会清空jackpot池 jpArea, userJackPot := rm.calculateJackpotScore() var jackpotUserName []string rm.RangePlayer(func(u baseroom.IPlayer) bool { user := GetPlayer(u) if user.totalBet > 0 { // 算分 jpScore := userJackPot[user.ID] rm.calculateUserScore(user, jpArea, jpScore) if jpScore > 0 { jackpotUserName = append(jackpotUserName, user.Nickname) //rm.BroadHitJackpot(user, jpScore) } } return true }) } // 计算 下注区域中奖得分 func (rm *ColorRoom) calculateUserScore(user *ColorPlayer, jpArea pb.ColorBetArea, jpScore int64) { msg := user.settleMsg for _, winArea := range rm.winBetAreaMul { win2 := int64(0) // 税前 gold := int64(0) // 税后 if user.totalBets[winArea.Area] <= 0 { continue } if jpArea != winArea.Area { odds := winArea.Mul // 奖金计算公式 Payouts =( Odds * Bet ) * ( 1 + Bonus) + Bet odds 是倍率 Bonus是猜中幸运骰子 的加成 // 本区域赢未扣税 倍率是百分值所以计算时要除以100 win2 = (odds * user.totalBets[winArea.Area]) / 100 // 本区域赢扣税 gold = win2 * (100 - rm.roomCfg.Rate) / 100 // 算税() msg.Tax += win2 - gold //// 加回投注本金 //gold += user.totalBets[winArea.Area] msg.TotalWin += gold // 统计赢区的下注总额 msg.TotalWinBaseBet += user.totalBets[winArea.Area] log.Debug(rm.UserLog(user.ID, "算分 odds:%v 投注区:%v 税前:%v 税后+本金:%v", odds, winArea.Area, win2, gold)) } else { win2 = jpScore // 本区域赢扣税 gold = jpScore * (100 - rm.roomCfg.Rate) / 100 // 算税() msg.Tax += win2 - gold //// 加回投注本金 //gold += user.totalBets[winArea.Area] msg.TotalWin += gold // 统计赢区的下注总额 msg.TotalWinBaseBet += user.totalBets[winArea.Area] log.Debug(rm.UserLog(user.ID, "算分 jackpotValue 投注区:%v win2:%v Gold:%v", winArea.Area, win2, gold)) } msg.UserAreaWin = append(msg.UserAreaWin, &pb.NtfColorSettle_UserBetAreaMul{ AreaMul: &pb.ColorBetAreaMul{ Area: winArea.Area, PrizeType: winArea.PrizeType, PrizeArea: winArea.PrizeArea, Mul: winArea.Mul, }, Bet: user.totalBets[winArea.Area], Win: win2, RealWin: gold, }) } } // 和平台做结算 func (rm *ColorRoom) settle() { rm.calculateAllUserScore() wg := new(sync.WaitGroup) rm.RangePlayer(func(u baseroom.IPlayer) bool { user := GetPlayer(u) // 没有赢分 if user.settleMsg.TotalWin < 1 { return true } ksync.GroupGo(wg, func() { afterGold, ok := rm.rpcPayoutGold(user, user.settleMsg.TotalWin, ReasonSettle) if !ok { log.Error(rm.UserLog(user.ID, "add gold:%v fail", user.settleMsg.TotalWin)) } else { rm.GetService().RunOnce(func() { rm.setGold(user, afterGold) }) } }) return true }) wg.Wait() } // 更新大客户 func (rm *ColorRoom) updateBigUsers() { if rm.status > pb.ColorGameStatus_CGS_Betting && rm.status < pb.ColorGameStatus_CGS_Settle { rm.RangePlayer(func(u baseroom.IPlayer) bool { user := GetPlayer(u) rm.bigUsers = append(rm.bigUsers, &pb.ColorBigUser{ NickName: user.Nickname, Avatar: user.AvatarUrl, WinChips: user.totalBet, AreaId: user.totalBets, }) return true }) } else if rm.status >= pb.ColorGameStatus_CGS_Settle { rm.RangePlayer(func(u baseroom.IPlayer) bool { user := GetPlayer(u) bigUser := &pb.ColorBigUser{ NickName: user.Nickname, Avatar: user.AvatarUrl, WinChips: user.settleMsg.TotalWin, } for _, area := range user.settleMsg.UserAreaWin { bigUser.AreaId = append(bigUser.AreaId, area.Win) } return true }) } sort.Slice(rm.bigUsers, func(i, j int) bool { return rm.bigUsers[i].WinChips > rm.bigUsers[j].WinChips }) if len(rm.bigUsers) > 6 { rm.bigUsers = rm.bigUsers[0:6] } } func (rm *ColorRoom) initGameTrend() [][]pb.ColorType { return model.GetTrend() } // 更新路途 最多保存100局 func (rm *ColorRoom) updateGameTrend() { rm.trend = append(rm.trend, []pb.ColorType{rm.ntfOpenThreeDice.Color[0], rm.ntfOpenThreeDice.Color[1], rm.ntfOpenThreeDice.Color[2]}) if len(rm.trend) > 100 { rm.trend = rm.trend[len(rm.trend)-100 : len(rm.trend)] } model.SetTrend(rm.trend) } // 更新路途 最多保存100局 func (rm *ColorRoom) getNotifyTrend() *pb.NtfColorTrend { ntf := &pb.NtfColorTrend{} // 只计算30局的概率 maxCount := 30 partTrend := rm.trend if len(rm.trend) > maxCount { partTrend = rm.trend[len(rm.trend)-maxCount : len(rm.trend)] } for c := pb.ColorType_CT_Yellow; c <= pb.ColorType_CT_Green; c++ { cnt := int32(0) for _, colors := range partTrend { for _, color := range colors { if color == c { cnt++ } } } ntf.ColorRate = append(ntf.ColorRate, &pb.NtfColorTrend_ColorRate{ Color: c, Rate: cnt * 10000 / 100, }) } return ntf } // 五个档位(single有三个加上double,three两个)的赔率范围 func (rm *ColorRoom) initPrizeAreaRange() { rm.prizeAreaRange = rm.prizeAreaRange[0:0] arr2Mul := make([][]*game.ColorMulRate, 0, 5) arr2Mul = append(arr2Mul, rm.roomCfg.WinSingleColorMul...) arr2Mul = append(arr2Mul, rm.roomCfg.WinDoubleColorMul) arr2Mul = append(arr2Mul, rm.roomCfg.WinThreeColorMul) for prizeType, mul := range arr2Mul { rm.prizeAreaRange = append(rm.prizeAreaRange, &pb.ColorPrizeAreaRange{ Pos: pb.ColorPrizeArea(prizeType), MinMul: mul[0].Mul, MaxMul: mul[len(mul)-1].Mul, }) } } // 玩家房间配置信息,每个玩家下注档位不一样 func (rm *ColorRoom) getUserRoomConfig(user *ColorPlayer) *pb.ColorRoomConfig { cnf := &pb.ColorRoomConfig{ BetList: nil, MulRangeConfig: rm.prizeAreaRange, } lv := 0 for pos, lvBet := range rm.roomCfg.BetLevel { if rm.GetGold(user) < lvBet { break } lv = pos } cnf.BetList = rm.roomCfg.BetList[lv] return cnf } // 玩家转换成proto格式 func (rm *ColorRoom) getProtoUser(user *ColorPlayer) *pb.ColorUser { u := &pb.ColorUser{ UserID: user.ID, Nick: user.Nickname, Head: user.AvatarUrl, Score: rm.GetGold(user), } return u } // 结束下注,扣除投注玩家的投注金额 func (rm *ColorRoom) endBetPayoutUser() { var failer []*ColorPlayer var mt sync.Mutex wg := &sync.WaitGroup{} rm.RangePlayer(func(u baseroom.IPlayer) bool { user := GetPlayer(u) // 没有下注 if user.totalBet < 1 { return true } ksync.GroupGo(wg, func() { afterGold, ok := rm.rpcPayoutGold(user, -user.totalBet, ReasonBet) if !ok { mt.Lock() failer = append(failer, user) mt.Unlock() } else { rm.GetService().RunOnce(func() { rm.setGold(user, afterGold) }) } }) return true }) wg.Wait() // 移除扣钱失败玩家的投注信息 for _, u := range failer { u.totalBet = 0 for pos := range u.totalBets { u.totalBets[pos] = 0 } rm.notifyPayoutFail(u, pb.ErrCode_GoldNotEnough) } }