/* 1.玩家的投注总额都记录起来 2.每天结算开奖,开奖后把当前数据到为一个历史奖池的数据,历史奖池只保存一份,然后发奖,清空投注总额,奖池清空,并且重新开始. 3.每小局游戏的投注总额的百分之2作为奖池,不断累加 4.第一名得到百分之25的奖池 5.第二名得到百分之18的奖池 6.第三名得到百分之13的奖池 7.第四名得到百分之9的奖池 8.第五名得到百分之5的奖池 9.第六名到第十得到百分之3的奖池 10.第十一名到第十五得到百分之2的奖池 11.第十六名到第二十得到百分之1的奖池 */ package gamelogic import ( "encoding/json" "fmt" "sort" "sync" "time" "bet24.com/log" "bet24.com/redis" timetool "bet24.com/servers/common" "bet24.com/servers/games/masharie_table/common" "bet24.com/servers/games/masharie_table/config" item "bet24.com/servers/micros/item_inventory/proto" userservices "bet24.com/servers/micros/userservices/proto" "bet24.com/servers/transaction" ) const BetPoolKey = "betPool" const RankCount = 20 // 奖池排名奖励 type Prize struct { Day string // 奖池日期 Rank int // 名次 (1-15) UserId int // 用户ID TotalBet int // 总投注额 Award []item.ItemPack // 奖励 } type Rank struct { UserId int BetAmount int } // 奖池 type betPool struct { CycleTime int // 奖池周期 Total int // 总投注额 Jackpot int // 奖池 (总下注额的2%) 从config.Room.JackpotRate中取 BetList map[int]int // 所有玩家的下注总额 cycle string `json:"-"` // 奖池周期 lock *sync.RWMutex roomInfo *config.RoomInfo } // 创建一个周的奖池 func newWeekBetPool(roomInfo *config.RoomInfo) *betPool { ret := new(betPool) ret.lock = &sync.RWMutex{} ret.BetList = make(map[int]int) ret.roomInfo = roomInfo var cycle = "Week" ret.cycle = cycle // 从redis中获取奖池数据 key := fmt.Sprintf("%v:%v", BetPoolKey, cycle) data, _ := redis.String_Get(common.GetRedisKey(key)) ts := timetool.GetTimeStamp() weekIndex := timetool.GetWeekIndex(ts) if data == "" { // redis中没有奖池数据 ret.CycleTime = weekIndex ret.betPoolToRedis() } else { // redis中有奖池数据 bp := &betPool{ CycleTime: 0, BetList: make(map[int]int), Total: 0, Jackpot: 0, } err := json.Unmarshal([]byte(data), bp) if err != nil { log.Release("newWeekBetPool json.Unmarshal err:%v", err) } if bp.CycleTime != weekIndex { // 奖池数据不是今天的,重新初始化 ret.CycleTime = weekIndex ret.Total = 0 ret.Jackpot = 0 log.Debug("====newWeekBetPool.Refresh CycleTime[%d] Total[%d] Jackpot[%d]====", bp.CycleTime, bp.Total, bp.Jackpot) ret.betPoolToRedis() } else { // 奖池数据是今天的,直接赋值 ret.CycleTime = bp.CycleTime ret.BetList = bp.BetList ret.Total = bp.Total ret.Jackpot = bp.Jackpot } } //起一个定时器,每周发奖并且清空奖池 ret.startTimer() return ret } // 创建一个天的奖池 func newDayBetPool(roomInfo *config.RoomInfo) *betPool { ret := new(betPool) ret.lock = &sync.RWMutex{} ret.BetList = make(map[int]int) ret.roomInfo = roomInfo var cycle = "Day" ret.cycle = cycle // 从redis中获取奖池数据 key := fmt.Sprintf("%v:%v", BetPoolKey, cycle) data, _ := redis.String_Get(common.GetRedisKey(key)) if data == "" { // redis中没有奖池数据 ret.CycleTime = time.Now().Day() ret.betPoolToRedis() } else { // redis中有奖池数据 bp := &betPool{ CycleTime: 0, BetList: make(map[int]int), Total: 0, Jackpot: 0, } err := json.Unmarshal([]byte(data), bp) if err != nil { log.Release("newDayBetPool json.Unmarshal err:%v", err) } if bp.CycleTime != time.Now().Day() { // 奖池数据不是今天的,重新初始化 ret.CycleTime = time.Now().Day() ret.Total = 0 ret.Jackpot = 0 log.Debug("====newDayBetPool.Refresh CycleTime[%d] Total[%d] Jackpot[%d]====", bp.CycleTime, bp.Total, bp.Jackpot) ret.betPoolToRedis() } else { // 奖池数据是今天的,直接赋值 ret.CycleTime = bp.CycleTime ret.BetList = bp.BetList ret.Total = bp.Total ret.Jackpot = bp.Jackpot } } //起一个定时器,每天0点发奖并且清空奖池 ret.startTimer() return ret } func (bp *betPool) startTimer() { bp.sendPrize() go func() { time.AfterFunc(1*time.Minute, func() { bp.startTimer() }) }() } // 添加用户 func (bp *betPool) addUser(userId int) { bp.lock.RLock() _, ok := bp.BetList[userId] bp.lock.RUnlock() if !ok { bp.lock.Lock() bp.BetList[userId] = 0 bp.lock.Unlock() } } // 移除用户 func (bp *betPool) removeUser(userId int) { bp.lock.Lock() delete(bp.BetList, userId) bp.lock.Unlock() // log.Release("betPool.removeUser %d not found", userId) } // 是否上榜 func (bp *betPool) isRank(userId int) bool { _, _, _, myRank := bp.getRankList(userId) return myRank <= RankCount } // 每次结算时 将投注数据保存到历史奖池中 func (bp *betPool) addBetPool(userId int, betAmount int) { bp.lock.RLock() data, ok := bp.BetList[userId] bp.lock.RUnlock() // 检查用户是否已经投注过 if ok { bp.lock.Lock() data += betAmount bp.BetList[userId] = data bp.lock.Unlock() } else { bp.lock.Lock() bp.BetList[userId] = betAmount // log.Release("addBetPool failed bp.BetList is nil userId:%v,betAmount:%d", userId, betAmount) bp.lock.Unlock() } // 更新奖池总金额和奖池金额 bp.lock.Lock() bp.Total += betAmount bp.Jackpot += betAmount * bp.roomInfo.JackpotRate / 100 bp.lock.Unlock() } func (bp *betPool) betPoolToRedis() { bp.lock.RLock() var cycle = bp.cycle data, err := json.Marshal(bp) bp.lock.RUnlock() key := fmt.Sprintf("%v:%v", BetPoolKey, cycle) if err != nil { log.Debug("%vbetPool json.Marshal err:%v", cycle, err) return } redis.String_Set(common.GetRedisKey(key), string(data)) } // 获取排名列表 func (bp *betPool) getRankList(userId int) ([]Rank, int, int, int) { // 统计每个用户的投注金额 var myBetAmount = 0 var myRank = RankCount var rankList []Rank bp.lock.RLock() for k, v := range bp.BetList { rankList = append(rankList, Rank{k, v}) } bp.lock.RUnlock() if len(rankList) == 0 { return rankList, bp.Jackpot, myBetAmount, myRank } // 将投注金额从大到小排序 sort.Slice(rankList, func(i, j int) bool { return rankList[i].BetAmount > rankList[j].BetAmount }) // 查询自己的下注额度和排名 if userId > 0 { for i, bet := range rankList { if bet.UserId == userId { myBetAmount += bet.BetAmount myRank = i + 1 break } } } // 截取排名在前20名的数据 if len(rankList) > RankCount { rankList = rankList[:RankCount] } return rankList, bp.Jackpot, myBetAmount, myRank } // 发奖 func (bp *betPool) sendPrize() { ts := timetool.GetTimeStamp() weekIndex := timetool.GetWeekIndex(ts) bp.lock.RLock() var cycle = bp.cycle var cycleTime = bp.CycleTime var total = bp.Total var jackpot = bp.Jackpot bp.lock.RUnlock() if cycle == "Week" { if cycleTime == 0 || cycleTime == weekIndex { return } } else if cycle == "Day" { if cycleTime == 0 || cycleTime == time.Now().Day() { return } } // 从当前奖池中抽取20名获奖者 winners := bp.drawWinners() if len(winners) == 0 { return } log.Debug("====%vBetPool.sendPrize CycleTime[%d] Total[%d] Jackpot[%d]====", cycle, cycleTime, total, jackpot) // 发奖 for _, winner := range winners { log.Debug("userId=%d, rank=%d, award=%v", winner.UserId, winner.Rank, winner.Award) //金额为0跳过 if len(winner.Award) == 0 { //打印错误日志 log.Release("sendPrize failed Award %v", winner.Award) continue } var gameName = "تحدي المشاريع" var rankName = fmt.Sprintf("%v %v", gameName, "يوميًا") if cycle == "Week" { rankName = fmt.Sprintf("%v %v", gameName, "أسبوعي") } var mailTitle = `ممتازة في قائمة ترتيب تحدي المشاريع` var mailContent = fmt.Sprintf("عزيزي المستخدم،لقد حصلت على المرتبة %v في تصنيف %d، وقد حصلت على الجوائز التالية، يرجى التحقق من المرفقات.", rankName, winner.Rank) // 发送中奖通知 go userservices.SendSysMail(winner.UserId, &userservices.SysMail{ Id: 0, Title: mailTitle, Content: mailContent, Status: 0, SourceName: gameName, Crdate: timetool.GetTimeStamp(), Tools: winner.Award, }) // 写用户纪录 d, _ := json.Marshal(winner) go transaction.WriteGameRecord(winner.UserId, common.GAMEID, bp.roomInfo.RoomName, string(d)) } log.Debug("--------------------------------------") // 清空奖池 bp.lock.Lock() if cycle == "Week" { bp.CycleTime = weekIndex } else if cycle == "Day" { bp.CycleTime = time.Now().Day() } bp.BetList = make(map[int]int) bp.Total = 0 bp.Jackpot = 0 bp.lock.Unlock() //清空奖池打印日志 log.Debug("====Refresh BetPool cycle[%v] Total[%d] Jackpot[%d]====", cycle, 0, 0) bp.betPoolToRedis() } // 从当前池中抽取 20 名获奖者并计算他们的奖金 func (bp *betPool) drawWinners() []Prize { bp.lock.RLock() var cycle = bp.cycle var jackpot = bp.Jackpot if jackpot == 0 { log.Error("drawWinners failed jackpot is 0") return nil } betList, _, _, _ := bp.getRankList(0) bp.lock.RUnlock() if len(betList) == 0 { return nil } weekRankAward := bp.roomInfo.WeekRankAward winners := make([]Prize, 0, RankCount) // 循环投注并分配排名和奖金 for i, bet := range betList { // 只取前20名 if i >= RankCount { break } prize := Prize{ Day: time.Now().Format("2006-01-02"), Rank: i + 1, UserId: bet.UserId, Award: nil, } // 根据排名和累积奖金计算奖金金额 var award []item.ItemPack items := make(map[int]int) switch cycle { case "Week": //周榜从配置中读取 weekRankAward award = weekRankAward[i] case "Day": switch prize.Rank { case 1: items[item.Item_Gold] = jackpot * 25 / 100 // 25% of jackpot case 2: items[item.Item_Gold] = jackpot * 18 / 100 // 18% of jackpot case 3: items[item.Item_Gold] = jackpot * 13 / 100 // 13% of jackpot case 4: items[item.Item_Gold] = jackpot * 9 / 100 // 9% of jackpot case 5: items[item.Item_Gold] = jackpot * 5 / 100 // 5% of jackpot case 6, 7, 8, 9, 10: items[item.Item_Gold] = jackpot * 3 / 100 // 3% of jackpot case 11, 12, 13, 14, 15: items[item.Item_Gold] = jackpot * 2 / 100 // 2% of jackpot case 16, 17, 18, 19, 20: items[item.Item_Gold] = jackpot * 1 / 100 // 1% of jackpot default: // 如果排名不在前 20 名内,则不发放奖励 continue } // 将 items 转换为奖励道具列表 for itemId, count := range items { award = append(award, item.ItemPack{ ItemId: itemId, Count: count, }) } } prize.Award = award winners = append(winners, prize) } return winners }