524 lines
15 KiB
Go
524 lines
15 KiB
Go
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)
|
||
}
|
||
}
|