package combomatch import ( "bet24.com/log" "bet24.com/servers/common" inventory "bet24.com/servers/micros/item_inventory/proto" item "bet24.com/servers/micros/item_inventory/proto" "bet24.com/servers/micros/matches/handler/matchbase" "bet24.com/servers/micros/matches/handler/pointmatch" "bet24.com/servers/micros/matches/handler/setsmatch" "bet24.com/servers/micros/matches/handler/simplematch" notification "bet24.com/servers/micros/notification/proto" task "bet24.com/servers/micros/task/proto" "encoding/json" "sync" "time" ) type matchround struct { MatchType int MatchNo int matchInstance matchbase.MatchInstance EndTime int64 StartTime int64 createTime int64 } func (mr *matchround) getStatus() int { if mr.matchInstance == nil { return matchbase.MatchStatus_Invalid } return mr.matchInstance.GetStatus() } type matchInfo struct { MatchId int Rounds []*matchround CurrentRound int // 当前轮次 mm *combomatchMgr lock *sync.RWMutex userFee map[int]item.ItemPack robotConfig *matchbase.Robot_config robotCount int config *matchconfig allUsers []matchbase.MatchUser // 本次赛事所有用户,比赛开始后才有 currentRoundWinners []matchbase.MatchUser // 本轮胜者,用于延迟进行下一轮缓存数据 enrollUsers []combomatchuser // 预报名用户 lockEnroll *sync.RWMutex userAwarded map[int]bool startFailed bool enrolledUsers []matchbase.EnrollUser } func newMatchInfo(matchId int, mm *combomatchMgr, robotConfig *matchbase.Robot_config, config *matchconfig) *matchInfo { ret := &matchInfo{ MatchId: matchId, mm: mm, config: config, CurrentRound: -1, // 预报名 } ret.userFee = make(map[int]item.ItemPack) ret.userAwarded = make(map[int]bool) ret.lock = &sync.RWMutex{} ret.lockEnroll = &sync.RWMutex{} ret.robotConfig = robotConfig if robotConfig != nil && robotConfig.Max > 0 { sec := robotConfig.GetWaitSec() time.AfterFunc(time.Second*time.Duration(sec), ret.checkRobot) } // 如果是预先报名,则启动定时器开始 if config.IsPreEnroll() { nextGame := config.GetNextStartTime() - int(time.Now().Unix()) log.Debug("combomatch.newMatchInfo MatchId[%d] [%d] seconds to start", matchId, nextGame) time.AfterFunc(time.Second*time.Duration(nextGame), ret.onMatchStart) // 最后一次 if config.EnrollRange.End > 0 { go func(startTime int, beforeTime int) { delaySec := startTime - beforeTime if delaySec > 0 { time.Sleep(time.Second * time.Duration(delaySec)) ret.onMatchLocked(beforeTime, true) } }(nextGame, config.EnrollRange.End) } // 额外的 for _, v := range config.ExtraNotifySecs { go func(startTime int, beforeTime int) { delaySec := startTime - beforeTime if delaySec > 0 { time.Sleep(time.Second * time.Duration(delaySec)) ret.onMatchLocked(beforeTime, false) } }(nextGame, v) } } return ret } func (mi *matchInfo) getMatchRound() *matchround { if mi.CurrentRound >= len(mi.Rounds) || mi.CurrentRound < 0 { return nil } return mi.Rounds[mi.CurrentRound] } func (mi *matchInfo) getCurrentInstance() matchbase.MatchInstance { mr := mi.getMatchRound() if mr == nil { return nil } return mr.matchInstance } func (mi *matchInfo) isEnded() bool { if mi.startFailed { return true } if mi.CurrentRound < mi.config.getTotalRound()-1 { return false } instance := mi.getCurrentInstance() if instance == nil { return false } return instance.GetStatus() == matchbase.MatchStatus_Ended } func (mi *matchInfo) getStatus() int { if mi.isEnded() { return matchbase.MatchStatus_Ended } // 报名 if mi.CurrentRound < 0 { return matchbase.MatchStatus_Free } instance := mi.getCurrentInstance() if instance == nil { return matchbase.MatchStatus_Free } // 第一场如果未开,取第一场状态 if mi.CurrentRound == 0 { return instance.GetStatus() } return matchbase.MatchStatus_Playing } func (mi *matchInfo) isFull() bool { if mi.config.IsPreEnroll() { //return len(mi.enrollUsers) >= mi.config.EnrollMax return false // 如果预报名,则不需要根据人满重开一个 } if mi.CurrentRound == -1 { mi.lockEnroll.RLock() enrollCount := len(mi.enrollUsers) mi.lockEnroll.RUnlock() return enrollCount >= mi.config.TotalUser } instance := mi.getCurrentInstance() if instance == nil { return true } return instance.IsFull() } func (mi *matchInfo) canEnroll() bool { mi.lockEnroll.RLock() defer mi.lockEnroll.RUnlock() if mi.config.IsPreEnroll() { return len(mi.enrollUsers) < mi.config.EnrollMax } return len(mi.enrollUsers) < mi.config.TotalUser } func (mi *matchInfo) isUserEnrolled(userId int) bool { mi.lockEnroll.RLock() for _, v := range mi.enrollUsers { if v.UserId == userId { mi.lockEnroll.RUnlock() return true } } mi.lockEnroll.RUnlock() instance := mi.getCurrentInstance() if instance == nil { return false } return instance.IsUserEnrolled(userId) } func (mi *matchInfo) isUserPlaying(userId int) bool { if mi.getStatus() == matchbase.MatchStatus_Ended { return false } if !mi.isUserEnrolled(userId) { return false } instance := mi.getCurrentInstance() // 这里存在比赛还没开始的情况 if instance == nil { // 超过一轮,看是否在上一轮晋级中 if mi.CurrentRound < 0 { return true } // 在不在晋级列表 for _, v := range mi.currentRoundWinners { if v.UserId == userId { return true } } return false } userlist := instance.GetUserList() for _, v := range userlist { if v == userId { return true } } return false } func (mi *matchInfo) isTimeout() bool { if !mi.isEnded() { return false } return time.Now().Unix()-mi.getEndTime() >= match_time_out_ended } func (mi *matchInfo) dump(isDetail bool) { log.Release(" MatchId[%d] CurrentRound[%d]", mi.MatchId, mi.CurrentRound) // 还没开始 if len(mi.Rounds) == 0 { mi.lockEnroll.RLock() log.Release(" EnrollUserCount:%d", len(mi.enrollUsers)) if isDetail { for _, v := range mi.enrollUsers { log.Release(" UserId:%d Nickname:%s", v.UserId, v.NickName) } } mi.lockEnroll.RUnlock() return } else { log.Release(" EnrollUserCount:%d", len(mi.allUsers)) if isDetail { for _, v := range mi.allUsers { log.Release(" UserId:%d Nickname:%s,Score:%d,WinCount:%d,EnrollTime:%d", v.UserId, v.NickName, v.Score, v.WinCount, v.EnrollTime) } } } for i := 0; i < len(mi.Rounds); i++ { r := mi.Rounds[i] if i < len(mi.Rounds)-1 { log.Release(" Round[%d] MatchNo[%d] StartTime[%s] EndTime[%s]", i, r.MatchNo, common.TimeStampToString(r.StartTime), common.TimeStampToString(r.EndTime)) continue } log.Release(" Round[%d] MatchNo[%d] Status[%s] StartTime[%s] EndTime[%s]", i, r.MatchNo, matchbase.GetMatchStatusDesc(r.getStatus()), common.TimeStampToString(r.StartTime), common.TimeStampToString(r.EndTime)) } } func (mi *matchInfo) checkRobot() { if mi.getStatus() > matchbase.MatchStatus_Free || mi.robotCount >= mi.robotConfig.Max { return } if mi.isFull() { return } sec := mi.robotConfig.GetWaitSec() time.AfterFunc(time.Second*time.Duration(sec), mi.checkRobot) mi.robotCount += mi.mm.addARobot(mi.MatchId) } func (mi *matchInfo) sendNotification(userId int, data string) { if userId == -1 { // send all if mi.CurrentRound < 0 { mi.lockEnroll.RLock() for _, v := range mi.enrollUsers { notification.AddNotification(v.UserId, notification.Notification_Match, data) } mi.lockEnroll.RUnlock() } else { mi.lock.RLock() for _, v := range mi.allUsers { notification.AddNotification(v.UserId, notification.Notification_Match, data) } mi.lock.RUnlock() } return } notification.AddNotification(userId, notification.Notification_Match, data) } func (mi *matchInfo) setUserFee(userId int, fee item.ItemPack) { mi.lock.Lock() mi.userFee[userId] = fee mi.lock.Unlock() } func (mi *matchInfo) getAndRemoveUserFee(userId int) item.ItemPack { mi.lock.Lock() defer mi.lock.Unlock() ret, ok := mi.userFee[userId] if !ok { return item.ItemPack{} } delete(mi.userFee, userId) return ret } func (mi *matchInfo) isUserAwarded(userId int) bool { mi.lock.RLock() defer mi.lock.RUnlock() ret, ok := mi.userAwarded[userId] if !ok { return false } return ret } func (mi *matchInfo) setUserAwarded(userId int) { mi.lock.Lock() defer mi.lock.Unlock() mi.userAwarded[userId] = true } func (mi *matchInfo) getUserList() []int { instance := mi.getCurrentInstance() if instance == nil { return []int{} } return instance.GetUserList() } func (mi *matchInfo) getEndTime() int64 { if !mi.isEnded() { return 0 } if len(mi.Rounds) <= 0 { return 0 } return mi.Rounds[len(mi.Rounds)-1].EndTime } func (mi *matchInfo) getStartTime() int64 { if len(mi.Rounds) == 0 { return 0 } return mi.Rounds[0].StartTime } func (mi *matchInfo) getMatchNo() int { if len(mi.Rounds) == 0 { return 0 } return mi.Rounds[len(mi.Rounds)-1].MatchNo } func (mi *matchInfo) getStartSeconds() int { nextStartTime := mi.config.GetNextStartTime() if nextStartTime == 0 { return 0 } return nextStartTime - int(time.Now().Unix()) } func (mi *matchInfo) setStartTime(matchNo int) { if len(mi.Rounds) == 0 { log.Release("matchInfo.setStartTime no rounds") return } r := mi.Rounds[len(mi.Rounds)-1] if r.MatchNo != matchNo { log.Release("matchInfo.setStartTime MatchNo not match %d != %d", r.MatchNo, matchNo) return } r.StartTime = time.Now().Unix() if mi.CurrentRound == 0 { instance := mi.getCurrentInstance() if instance == nil { return } mi.allUsers = instance.GetAllMatchUsers() // 赋值enrollTime for k, v := range mi.allUsers { go task.DoTaskAction(v.UserId, task.TaskAction_playComboMatch, 1, task.TaskScope{}) for _, v1 := range mi.enrollUsers { if v.UserId == v1.UserId { mi.allUsers[k].EnrollTime = v1.EnrollTime break } } } } } func (mi *matchInfo) setEndTime(matchNo int) { if len(mi.Rounds) == 0 { log.Release("matchInfo.setEndTime no rounds") return } r := mi.Rounds[len(mi.Rounds)-1] if r.MatchNo != matchNo { log.Release("matchInfo.setEndTime MatchNo not match %d != %d", r.MatchNo, matchNo) return } r.EndTime = time.Now().Unix() } func (mi *matchInfo) getAllMatchUsers() []matchbase.MatchUser { return mi.allUsers } func (mi *matchInfo) addMatchInfo(matchNo int, matchInstance matchbase.MatchInstance) { mi.CurrentRound++ mi.Rounds = append(mi.Rounds, &matchround{ MatchType: mi.config.getMatchType(mi.CurrentRound), MatchNo: matchNo, matchInstance: matchInstance, createTime: time.Now().Unix(), }) //mi.currentRoundWinners = []int{} } func (mi *matchInfo) getMatchUser(userId int) *matchbase.MatchUser { for _, v := range mi.allUsers { if v.UserId == userId { return &v } } return nil } func (mi *matchInfo) getCurrentMatchType() int { if mi.CurrentRound == -1 { return matchbase.MatchType_Invalid } return mi.config.getMatchType(mi.CurrentRound) } func (mi *matchInfo) setRoundWinners(winners []int) { ni := ComboMatch_notificationInfo{Msg: ComboMatch_noti_promoted} d, _ := json.Marshal(ni) data := string(d) for _, v := range winners { mi.sendNotification(v, data) usr := mi.getMatchUser(v) if usr != nil { usr.Score = mi.config.getScorePercentToNext(mi.CurrentRound) * usr.Score / 100 if usr.Score < 0 { usr.Score = 0 } } mi.currentRoundWinners = append(mi.currentRoundWinners, *usr) } } func (mi *matchInfo) onMatchLocked(secsToStart int, finalCall bool) { if !mi.config.IsPreEnroll() { return } log.Debug("combomatch.matchInfo.onMatchLocked matchId[%d] secsToStart[%d]", mi.MatchId, secsToStart) mi.lockEnroll.Lock() totalUser := len(mi.enrollUsers) for i := 0; i < totalUser; i++ { mi.enrollUsers[i].confirmed = mi.enrollUsers[i].isRobot } mi.lockEnroll.Unlock() /* // 最后一次如果不满人,则不通知 if totalUser < mi.config.EnrollMin && secsToStart <= mi.config.EnrollRange.End { mi.onMatchStart() return } */ //postMatchLockNotification(mi.MatchId, secsToStart) callMsg := ComboMatch_noti_matchcall if finalCall { callMsg = ComboMatch_noti_matchlocked } ni := ComboMatch_notificationInfo{Msg: callMsg, MatchId: mi.MatchId, Seconds: secsToStart} d, _ := json.Marshal(ni) mi.sendNotification(-1, string(d)) } func (mi *matchInfo) onMatchStart() { log.Debug("combomatch.matchInfo.onMatchStart matchId[%d]", mi.MatchId) // 把没有confirm的人清理 var toRemove []int mi.lockEnroll.Lock() for i := 0; i < len(mi.enrollUsers); { if mi.enrollUsers[i].confirmed { i++ } else { log.Debug(" removing unconfirmed user[%d]", mi.enrollUsers[i].UserId) toRemove = append(toRemove, mi.enrollUsers[i].UserId) mi.enrollUsers = append(mi.enrollUsers[:i], mi.enrollUsers[i+1:]...) } } mi.lockEnroll.Unlock() for _, v := range toRemove { // 退还报名费 mi.quitUser(v, false) } // 看下人数够不够 mi.lockEnroll.RLock() totalUser := len(mi.enrollUsers) mi.lockEnroll.RUnlock() // 如果是提前报名的,并且人数不够下限,则不开始比赛 if totalUser < mi.config.EnrollMin && mi.config.IsPreEnroll() { mi.startFailed = true //postMatchFailedNotification(mi.MatchId) ni := ComboMatch_notificationInfo{Msg: ComboMatch_noti_matchfailed, MatchId: mi.MatchId} d, _ := json.Marshal(ni) mi.sendNotification(-1, string(d)) mi.quitAllUsers() return } config := mi.config var matchNo int var err string var matchInstance matchbase.MatchInstance switch config.getMatchType(0) { case matchbase.MatchType_SimpleMatch: matchNo, err = simplematch.CreateMatch(-1, config.GameId, config.GameRule, totalUser, config.getTarget(0), config.TableUser, 0, 0, config.PlayTime, config.isEleminateByScore(mi.CurrentRound), config.getWinnerCount(0)) if matchNo == 0 { log.Release("combomatchMgr.getOrCreateMatch simplematch.CreateMatch failed %s", err) return } matchInstance = simplematch.GetMatchInstance(matchNo) case matchbase.MatchType_PointMatch: e, ssec, sscore, w := config.getPointMatchParams(0) matchNo, err = pointmatch.CreateMatch(-1, config.GameId, config.GameRule, totalUser, config.TableUser, 0, 0, config.PlayTime, e, ssec, sscore, w) if matchNo == 0 { log.Release("combomatchMgr.getOrCreateMatch pointmatch.CreateMatch failed %s", err) return } matchInstance = pointmatch.GetMatchInstance(matchNo) case matchbase.MatchType_SetsMatch: matchNo, err = setsmatch.CreateMatch(-1, config.GameId, config.GameRule, totalUser, config.TableUser, 0, 0, config.PlayTime, config.getWinnerCount(0)) if matchNo == 0 { log.Release("combomatchMgr.getOrCreateMatch setsmatch.CreateMatch failed %s", err) return } matchInstance = setsmatch.GetMatchInstance(matchNo) } if matchInstance == nil { log.Release("combomatchMgr.getOrCreateMatch GetMatchInstance == nil") return } matchInstance.RegisterReceiver(mi.mm) mi.CurrentRound = -1 mi.addMatchInfo(matchNo, matchInstance) // 把报名玩家都拉进去 mi.lockEnroll.RLock() for _, v := range mi.enrollUsers { switch mi.getCurrentMatchType() { case matchbase.MatchType_SimpleMatch: simplematch.EnrollMatch(v.UserId, v.NickName, v.FaceId, v.FaceUrl, matchNo) case matchbase.MatchType_PointMatch: pointmatch.EnrollMatch(v.UserId, v.NickName, v.FaceId, v.FaceUrl, matchNo) case matchbase.MatchType_SetsMatch: setsmatch.EnrollMatch(v.UserId, v.NickName, v.FaceId, v.FaceUrl, matchNo) } } mi.lockEnroll.RUnlock() } func (mi *matchInfo) quitAllUsers() { for _, v := range mi.enrollUsers { mi.quitUser(v.UserId, false) } mi.lockEnroll.Lock() mi.enrollUsers = []combomatchuser{} mi.lockEnroll.Unlock() } func (mi *matchInfo) quitUser(userId int, check bool) bool { config := mi.config if check { found := false mi.lockEnroll.Lock() for i := 0; i < len(mi.enrollUsers); { if mi.enrollUsers[i].UserId == userId { found = true mi.enrollUsers = append(mi.enrollUsers[:i], mi.enrollUsers[i+1:]...) } else { i++ } } mi.lockEnroll.Unlock() if !found { log.Release("combomatch.matchInfo.quitUser[%d] not found in matchId[%d]", userId, mi.MatchId) return false } } mi.config.addOnline(-1) // 如果比赛已开始 status := mi.getStatus() if status == matchbase.MatchStatus_Playing { // 按淘汰处理 // ... } // 退钱 if len(config.EnrollFee) > 0 && status < matchbase.MatchStatus_Playing { itm := mi.getAndRemoveUserFee(userId) if itm.Count > 0 { inventory.AddItems(userId, []item.ItemPack{itm}, "combomatch fee return", common.LOGTYPE_COMBOMATCH_ENTER_RETURN) } else if config.DailyFreeCount > 0 { getFreeCountManager().reduceFreeCount(userId, config.MatchId) } } //postUserExitNotification(userId, mi.MatchId) ni := ComboMatch_notificationInfo{Msg: ComboMatch_noti_userexit, UserId: userId, MatchId: mi.MatchId} d, _ := json.Marshal(ni) mi.sendNotification(-1, string(d)) return true } func (mi *matchInfo) addEnrollUser(userId int, nickname string, faceId int, faceUrl string, isRobot bool) { if mi.isUserEnrolled(userId) { log.Release("combomatch.addEnrollUser user[%d] already exist in matchId[%d]", userId, mi.MatchId) return } found := false for k, v := range mi.enrolledUsers { if v.UserId == userId { mi.enrolledUsers[k].EnrollTime = time.Now().Unix() found = true break } } if !found { mi.enrolledUsers = append(mi.enrolledUsers, matchbase.EnrollUser{UserId: userId, EnrollTime: time.Now().Unix()}) } var usr combomatchuser usr.UserId = userId usr.NickName = nickname usr.FaceId = faceId usr.FaceUrl = faceUrl usr.EnrollTime = time.Now().Unix() usr.isRobot = isRobot usr.confirmed = true mi.lockEnroll.Lock() mi.enrollUsers = append(mi.enrollUsers, usr) userCount := len(mi.enrollUsers) mi.lockEnroll.Unlock() mi.config.addOnline(1) // 如果报名人满,则开始比赛,预报名除外 if userCount == mi.config.TotalUser && !mi.config.IsPreEnroll() { mi.onMatchStart() } } func (mi *matchInfo) getMatchInfo() string { var ret struct { CurrentRound int Users []matchbase.MatchUser } ret.CurrentRound = mi.CurrentRound if mi.CurrentRound < 0 { for i := 0; i < len(mi.enrollUsers); i++ { ret.Users = append(ret.Users, mi.enrollUsers[i].MatchUser) } } else { ret.Users = mi.allUsers } d, _ := json.Marshal(ret) return string(d) } func (mi *matchInfo) getOnline() int { return len(mi.allUsers) } func (mi *matchInfo) updateRankAndScore(userId, rank, score int, winCount int) { for i := 0; i < len(mi.allUsers); i++ { if mi.allUsers[i].UserId == userId { mi.allUsers[i].Rank = rank mi.allUsers[i].Score = score mi.allUsers[i].WinCount += winCount return } } log.Release("combomatch.matchInfo.updateRankAndScore MatchNo[%d] UserId [%d] not found", mi.getMatchNo(), userId) } func (mi *matchInfo) getUser(userId int) *matchbase.MatchUser { ci := mi.getCurrentInstance() if ci != nil { return ci.GetUser(userId) } return nil } func (mi *matchInfo) postUserRankNotification(userId int, rank int, items []item.ItemPack) { ni := ComboMatch_notificationInfo{Msg: ComboMatch_noti_rank, UserId: userId, MatchId: mi.MatchId, Rank: rank, Prize: items} d, _ := json.Marshal(ni) mi.sendNotification(userId, string(d)) } func (mi *matchInfo) getConfirmCount() int { ret := 0 mi.lock.RLock() for _, v := range mi.enrollUsers { if v.confirmed { ret++ } } mi.lock.RUnlock() return ret } func (mi *matchInfo) confirmMatch(userId int) bool { mi.lock.Lock() defer mi.lock.Unlock() for k, v := range mi.enrollUsers { if v.UserId == userId { mi.enrollUsers[k].confirmed = true return true } } return false } func (mi *matchInfo) tryRemoveOneRobot(forceRemove bool) bool { var robotId int realUserCount := 0 totalUserCount := 0 mi.lock.RLock() totalUserCount = len(mi.enrollUsers) for _, v := range mi.enrollUsers { if !v.isRobot { realUserCount++ } else { if robotId == 0 { robotId = v.UserId } } } mi.lock.RUnlock() if !forceRemove { if totalUserCount < 5 || realUserCount > 0 { return false } } if robotId > 0 { return mi.quitUser(robotId, true) } return false }