betrank.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. /*
  2. 1.玩家的投注总额都记录起来
  3. 2.每天结算开奖,开奖后把当前数据到为一个历史奖池的数据,历史奖池只保存一份,然后发奖,清空投注总额,奖池清空,并且重新开始.
  4. 3.每小局游戏的投注总额的百分之2作为奖池,不断累加
  5. 4.第一名得到百分之25的奖池
  6. 5.第二名得到百分之18的奖池
  7. 6.第三名得到百分之13的奖池
  8. 7.第四名得到百分之9的奖池
  9. 8.第五名得到百分之5的奖池
  10. 9.第六名到第十得到百分之3的奖池
  11. 10.第十一名到第十五得到百分之2的奖池
  12. 11.第十六名到第二十得到百分之1的奖池
  13. */
  14. package gamelogic
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "sort"
  19. "sync"
  20. "time"
  21. "bet24.com/log"
  22. "bet24.com/redis"
  23. timetool "bet24.com/servers/common"
  24. "bet24.com/servers/games/masharie_table/common"
  25. "bet24.com/servers/games/masharie_table/config"
  26. item "bet24.com/servers/micros/item_inventory/proto"
  27. userservices "bet24.com/servers/micros/userservices/proto"
  28. "bet24.com/servers/transaction"
  29. )
  30. const BetPoolKey = "betPool"
  31. const RankCount = 20
  32. // 奖池排名奖励
  33. type Prize struct {
  34. Day string // 奖池日期
  35. Rank int // 名次 (1-15)
  36. UserId int // 用户ID
  37. TotalBet int // 总投注额
  38. Award []item.ItemPack // 奖励
  39. }
  40. type Rank struct {
  41. UserId int
  42. BetAmount int
  43. }
  44. // 奖池
  45. type betPool struct {
  46. CycleTime int // 奖池周期
  47. Total int // 总投注额
  48. Jackpot int // 奖池 (总下注额的2%) 从config.Room.JackpotRate中取
  49. BetList map[int]int // 所有玩家的下注总额
  50. cycle string `json:"-"` // 奖池周期
  51. lock *sync.RWMutex
  52. roomInfo *config.RoomInfo
  53. }
  54. // 创建一个周的奖池
  55. func newWeekBetPool(roomInfo *config.RoomInfo) *betPool {
  56. ret := new(betPool)
  57. ret.lock = &sync.RWMutex{}
  58. ret.BetList = make(map[int]int)
  59. ret.roomInfo = roomInfo
  60. var cycle = "Week"
  61. ret.cycle = cycle
  62. // 从redis中获取奖池数据
  63. key := fmt.Sprintf("%v:%v", BetPoolKey, cycle)
  64. data, _ := redis.String_Get(common.GetRedisKey(key))
  65. ts := timetool.GetTimeStamp()
  66. weekIndex := timetool.GetWeekIndex(ts)
  67. if data == "" {
  68. // redis中没有奖池数据
  69. ret.CycleTime = weekIndex
  70. ret.betPoolToRedis()
  71. } else {
  72. // redis中有奖池数据
  73. bp := &betPool{
  74. CycleTime: 0,
  75. BetList: make(map[int]int),
  76. Total: 0,
  77. Jackpot: 0,
  78. }
  79. err := json.Unmarshal([]byte(data), bp)
  80. if err != nil {
  81. log.Release("newWeekBetPool json.Unmarshal err:%v", err)
  82. }
  83. if bp.CycleTime != weekIndex {
  84. // 奖池数据不是今天的,重新初始化
  85. ret.CycleTime = weekIndex
  86. ret.Total = 0
  87. ret.Jackpot = 0
  88. log.Debug("====newWeekBetPool.Refresh CycleTime[%d] Total[%d] Jackpot[%d]====", bp.CycleTime, bp.Total, bp.Jackpot)
  89. ret.betPoolToRedis()
  90. } else {
  91. // 奖池数据是今天的,直接赋值
  92. ret.CycleTime = bp.CycleTime
  93. ret.BetList = bp.BetList
  94. ret.Total = bp.Total
  95. ret.Jackpot = bp.Jackpot
  96. }
  97. }
  98. //起一个定时器,每周发奖并且清空奖池
  99. ret.startTimer()
  100. return ret
  101. }
  102. // 创建一个天的奖池
  103. func newDayBetPool(roomInfo *config.RoomInfo) *betPool {
  104. ret := new(betPool)
  105. ret.lock = &sync.RWMutex{}
  106. ret.BetList = make(map[int]int)
  107. ret.roomInfo = roomInfo
  108. var cycle = "Day"
  109. ret.cycle = cycle
  110. // 从redis中获取奖池数据
  111. key := fmt.Sprintf("%v:%v", BetPoolKey, cycle)
  112. data, _ := redis.String_Get(common.GetRedisKey(key))
  113. if data == "" {
  114. // redis中没有奖池数据
  115. ret.CycleTime = time.Now().Day()
  116. ret.betPoolToRedis()
  117. } else {
  118. // redis中有奖池数据
  119. bp := &betPool{
  120. CycleTime: 0,
  121. BetList: make(map[int]int),
  122. Total: 0,
  123. Jackpot: 0,
  124. }
  125. err := json.Unmarshal([]byte(data), bp)
  126. if err != nil {
  127. log.Release("newDayBetPool json.Unmarshal err:%v", err)
  128. }
  129. if bp.CycleTime != time.Now().Day() {
  130. // 奖池数据不是今天的,重新初始化
  131. ret.CycleTime = time.Now().Day()
  132. ret.Total = 0
  133. ret.Jackpot = 0
  134. log.Debug("====newDayBetPool.Refresh CycleTime[%d] Total[%d] Jackpot[%d]====", bp.CycleTime, bp.Total, bp.Jackpot)
  135. ret.betPoolToRedis()
  136. } else {
  137. // 奖池数据是今天的,直接赋值
  138. ret.CycleTime = bp.CycleTime
  139. ret.BetList = bp.BetList
  140. ret.Total = bp.Total
  141. ret.Jackpot = bp.Jackpot
  142. }
  143. }
  144. //起一个定时器,每天0点发奖并且清空奖池
  145. ret.startTimer()
  146. return ret
  147. }
  148. func (bp *betPool) startTimer() {
  149. bp.sendPrize()
  150. go func() {
  151. time.AfterFunc(1*time.Minute, func() {
  152. bp.startTimer()
  153. })
  154. }()
  155. }
  156. // 添加用户
  157. func (bp *betPool) addUser(userId int) {
  158. bp.lock.RLock()
  159. _, ok := bp.BetList[userId]
  160. bp.lock.RUnlock()
  161. if !ok {
  162. bp.lock.Lock()
  163. bp.BetList[userId] = 0
  164. bp.lock.Unlock()
  165. }
  166. }
  167. // 移除用户
  168. func (bp *betPool) removeUser(userId int) {
  169. bp.lock.Lock()
  170. delete(bp.BetList, userId)
  171. bp.lock.Unlock()
  172. // log.Release("betPool.removeUser %d not found", userId)
  173. }
  174. // 是否上榜
  175. func (bp *betPool) isRank(userId int) bool {
  176. _, _, _, myRank := bp.getRankList(userId)
  177. return myRank <= RankCount
  178. }
  179. // 每次结算时 将投注数据保存到历史奖池中
  180. func (bp *betPool) addBetPool(userId int, betAmount int) {
  181. bp.lock.RLock()
  182. data, ok := bp.BetList[userId]
  183. bp.lock.RUnlock()
  184. // 检查用户是否已经投注过
  185. if ok {
  186. bp.lock.Lock()
  187. data += betAmount
  188. bp.BetList[userId] = data
  189. bp.lock.Unlock()
  190. } else {
  191. bp.lock.Lock()
  192. bp.BetList[userId] = betAmount
  193. // log.Release("addBetPool failed bp.BetList is nil userId:%v,betAmount:%d", userId, betAmount)
  194. bp.lock.Unlock()
  195. }
  196. // 更新奖池总金额和奖池金额
  197. bp.lock.Lock()
  198. bp.Total += betAmount
  199. bp.Jackpot += betAmount * bp.roomInfo.JackpotRate / 100
  200. bp.lock.Unlock()
  201. }
  202. func (bp *betPool) betPoolToRedis() {
  203. bp.lock.RLock()
  204. var cycle = bp.cycle
  205. data, err := json.Marshal(bp)
  206. bp.lock.RUnlock()
  207. key := fmt.Sprintf("%v:%v", BetPoolKey, cycle)
  208. if err != nil {
  209. log.Debug("%vbetPool json.Marshal err:%v", cycle, err)
  210. return
  211. }
  212. redis.String_Set(common.GetRedisKey(key), string(data))
  213. }
  214. // 获取排名列表
  215. func (bp *betPool) getRankList(userId int) ([]Rank, int, int, int) {
  216. // 统计每个用户的投注金额
  217. var myBetAmount = 0
  218. var myRank = RankCount
  219. var rankList []Rank
  220. bp.lock.RLock()
  221. for k, v := range bp.BetList {
  222. rankList = append(rankList, Rank{k, v})
  223. }
  224. bp.lock.RUnlock()
  225. if len(rankList) == 0 {
  226. return rankList, bp.Jackpot, myBetAmount, myRank
  227. }
  228. // 将投注金额从大到小排序
  229. sort.Slice(rankList, func(i, j int) bool {
  230. return rankList[i].BetAmount > rankList[j].BetAmount
  231. })
  232. // 查询自己的下注额度和排名
  233. if userId > 0 {
  234. for i, bet := range rankList {
  235. if bet.UserId == userId {
  236. myBetAmount += bet.BetAmount
  237. myRank = i + 1
  238. break
  239. }
  240. }
  241. }
  242. // 截取排名在前20名的数据
  243. if len(rankList) > RankCount {
  244. rankList = rankList[:RankCount]
  245. }
  246. return rankList, bp.Jackpot, myBetAmount, myRank
  247. }
  248. // 发奖
  249. func (bp *betPool) sendPrize() {
  250. ts := timetool.GetTimeStamp()
  251. weekIndex := timetool.GetWeekIndex(ts)
  252. bp.lock.RLock()
  253. var cycle = bp.cycle
  254. var cycleTime = bp.CycleTime
  255. var total = bp.Total
  256. var jackpot = bp.Jackpot
  257. bp.lock.RUnlock()
  258. if cycle == "Week" {
  259. if cycleTime == 0 || cycleTime == weekIndex {
  260. return
  261. }
  262. } else if cycle == "Day" {
  263. if cycleTime == 0 || cycleTime == time.Now().Day() {
  264. return
  265. }
  266. }
  267. // 从当前奖池中抽取20名获奖者
  268. winners := bp.drawWinners()
  269. if len(winners) == 0 {
  270. return
  271. }
  272. log.Debug("====%vBetPool.sendPrize CycleTime[%d] Total[%d] Jackpot[%d]====", cycle, cycleTime, total, jackpot)
  273. // 发奖
  274. for _, winner := range winners {
  275. log.Debug("userId=%d, rank=%d, award=%v", winner.UserId, winner.Rank, winner.Award)
  276. //金额为0跳过
  277. if len(winner.Award) == 0 {
  278. //打印错误日志
  279. log.Release("sendPrize failed Award %v", winner.Award)
  280. continue
  281. }
  282. var gameName = "تحدي المشاريع"
  283. var rankName = fmt.Sprintf("%v %v", gameName, "يوميًا")
  284. if cycle == "Week" {
  285. rankName = fmt.Sprintf("%v %v", gameName, "أسبوعي")
  286. }
  287. var mailTitle = `ممتازة في قائمة ترتيب تحدي المشاريع`
  288. var mailContent = fmt.Sprintf("عزيزي المستخدم،لقد حصلت على المرتبة %v في تصنيف %d، وقد حصلت على الجوائز التالية، يرجى التحقق من المرفقات.",
  289. rankName, winner.Rank)
  290. // 发送中奖通知
  291. go userservices.SendSysMail(winner.UserId, &userservices.SysMail{
  292. Id: 0,
  293. Title: mailTitle,
  294. Content: mailContent,
  295. Status: 0,
  296. SourceName: gameName,
  297. Crdate: timetool.GetTimeStamp(),
  298. Tools: winner.Award,
  299. })
  300. // 写用户纪录
  301. d, _ := json.Marshal(winner)
  302. go transaction.WriteGameRecord(winner.UserId, common.GAMEID, bp.roomInfo.RoomName, string(d))
  303. }
  304. log.Debug("--------------------------------------")
  305. // 清空奖池
  306. bp.lock.Lock()
  307. if cycle == "Week" {
  308. bp.CycleTime = weekIndex
  309. } else if cycle == "Day" {
  310. bp.CycleTime = time.Now().Day()
  311. }
  312. bp.BetList = make(map[int]int)
  313. bp.Total = 0
  314. bp.Jackpot = 0
  315. bp.lock.Unlock()
  316. //清空奖池打印日志
  317. log.Debug("====Refresh BetPool cycle[%v] Total[%d] Jackpot[%d]====", cycle, 0, 0)
  318. bp.betPoolToRedis()
  319. }
  320. // 从当前池中抽取 20 名获奖者并计算他们的奖金
  321. func (bp *betPool) drawWinners() []Prize {
  322. bp.lock.RLock()
  323. var cycle = bp.cycle
  324. var jackpot = bp.Jackpot
  325. if jackpot == 0 {
  326. log.Error("drawWinners failed jackpot is 0")
  327. return nil
  328. }
  329. betList, _, _, _ := bp.getRankList(0)
  330. bp.lock.RUnlock()
  331. if len(betList) == 0 {
  332. return nil
  333. }
  334. weekRankAward := bp.roomInfo.WeekRankAward
  335. winners := make([]Prize, 0, RankCount)
  336. // 循环投注并分配排名和奖金
  337. for i, bet := range betList {
  338. // 只取前20名
  339. if i >= RankCount {
  340. break
  341. }
  342. prize := Prize{
  343. Day: time.Now().Format("2006-01-02"),
  344. Rank: i + 1,
  345. UserId: bet.UserId,
  346. Award: nil,
  347. }
  348. // 根据排名和累积奖金计算奖金金额
  349. var award []item.ItemPack
  350. items := make(map[int]int)
  351. switch cycle {
  352. case "Week":
  353. //周榜从配置中读取 weekRankAward
  354. award = weekRankAward[i]
  355. case "Day":
  356. switch prize.Rank {
  357. case 1:
  358. items[item.Item_Gold] = jackpot * 25 / 100 // 25% of jackpot
  359. case 2:
  360. items[item.Item_Gold] = jackpot * 18 / 100 // 18% of jackpot
  361. case 3:
  362. items[item.Item_Gold] = jackpot * 13 / 100 // 13% of jackpot
  363. case 4:
  364. items[item.Item_Gold] = jackpot * 9 / 100 // 9% of jackpot
  365. case 5:
  366. items[item.Item_Gold] = jackpot * 5 / 100 // 5% of jackpot
  367. case 6, 7, 8, 9, 10:
  368. items[item.Item_Gold] = jackpot * 3 / 100 // 3% of jackpot
  369. case 11, 12, 13, 14, 15:
  370. items[item.Item_Gold] = jackpot * 2 / 100 // 2% of jackpot
  371. case 16, 17, 18, 19, 20:
  372. items[item.Item_Gold] = jackpot * 1 / 100 // 1% of jackpot
  373. default:
  374. // 如果排名不在前 20 名内,则不发放奖励
  375. continue
  376. }
  377. // 将 items 转换为奖励道具列表
  378. for itemId, count := range items {
  379. award = append(award, item.ItemPack{
  380. ItemId: itemId,
  381. Count: count,
  382. })
  383. }
  384. }
  385. prize.Award = award
  386. winners = append(winners, prize)
  387. }
  388. return winners
  389. }