diff --git a/game/ranking/list.go b/game/leaderboard/binary_search.go similarity index 69% rename from game/ranking/list.go rename to game/leaderboard/binary_search.go index 9693687..dd7ddaa 100644 --- a/game/ranking/list.go +++ b/game/leaderboard/binary_search.go @@ -1,4 +1,4 @@ -package ranking +package leaderboard import ( "encoding/json" @@ -6,28 +6,28 @@ import ( "github.com/kercylan98/minotaur/utils/generic" ) -// NewList 创建一个排名从 0 开始的排行榜 -func NewList[CompetitorID comparable, Score generic.Ordered](options ...ListOption[CompetitorID, Score]) *List[CompetitorID, Score] { - rankingList := &List[CompetitorID, Score]{ - event: new(event[CompetitorID, Score]), - rankCount: 100, - competitors: concurrent.NewBalanceMap[CompetitorID, Score](), +// NewBinarySearch 创建一个基于内存的二分查找排行榜 +func NewBinarySearch[CompetitorID comparable, Score generic.Ordered](options ...BinarySearchOption[CompetitorID, Score]) *BinarySearch[CompetitorID, Score] { + leaderboard := &BinarySearch[CompetitorID, Score]{ + binarySearchEvent: new(binarySearchEvent[CompetitorID, Score]), + rankCount: 100, + competitors: concurrent.NewBalanceMap[CompetitorID, Score](), } for _, option := range options { - option(rankingList) + option(leaderboard) } - return rankingList + return leaderboard } -type List[CompetitorID comparable, Score generic.Ordered] struct { - *event[CompetitorID, Score] +type BinarySearch[CompetitorID comparable, Score generic.Ordered] struct { + *binarySearchEvent[CompetitorID, Score] asc bool rankCount int competitors *concurrent.BalanceMap[CompetitorID, Score] scores []*scoreItem[CompetitorID, Score] // CompetitorID, Score - rankChangeEventHandles []RankChangeEventHandle[CompetitorID, Score] - rankClearBeforeEventHandles []RankClearBeforeEventHandle[CompetitorID, Score] + rankChangeEventHandles []BinarySearchRankChangeEventHandle[CompetitorID, Score] + rankClearBeforeEventHandles []BinarySearchRankClearBeforeEventHandle[CompetitorID, Score] } type scoreItem[CompetitorID comparable, Score generic.Ordered] struct { @@ -37,7 +37,7 @@ type scoreItem[CompetitorID comparable, Score generic.Ordered] struct { // Competitor 声明排行榜竞争者 // - 如果竞争者存在的情况下,会更新已有成绩,否则新增竞争者 -func (slf *List[CompetitorID, Score]) Competitor(competitorId CompetitorID, score Score) { +func (slf *BinarySearch[CompetitorID, Score]) Competitor(competitorId CompetitorID, score Score) { v, exist := slf.competitors.GetExist(competitorId) if exist { if slf.Cmp(v, score) == 0 { @@ -66,7 +66,7 @@ func (slf *List[CompetitorID, Score]) Competitor(competitorId CompetitorID, scor } // RemoveCompetitor 删除特定竞争者 -func (slf *List[CompetitorID, Score]) RemoveCompetitor(competitorId CompetitorID) { +func (slf *BinarySearch[CompetitorID, Score]) RemoveCompetitor(competitorId CompetitorID) { if !slf.competitors.Exist(competitorId) { return } @@ -83,13 +83,13 @@ func (slf *List[CompetitorID, Score]) RemoveCompetitor(competitorId CompetitorID } // Size 获取竞争者数量 -func (slf *List[CompetitorID, Score]) Size() int { +func (slf *BinarySearch[CompetitorID, Score]) Size() int { return slf.competitors.Size() } // GetRankDefault 获取竞争者排名,如果竞争者不存在则返回默认值 // - 排名从 0 开始 -func (slf *List[CompetitorID, Score]) GetRankDefault(competitorId CompetitorID, defaultValue int) int { +func (slf *BinarySearch[CompetitorID, Score]) GetRankDefault(competitorId CompetitorID, defaultValue int) int { rank, err := slf.GetRank(competitorId) if err != nil { return defaultValue @@ -99,10 +99,10 @@ func (slf *List[CompetitorID, Score]) GetRankDefault(competitorId CompetitorID, // GetRank 获取竞争者排名 // - 排名从 0 开始 -func (slf *List[CompetitorID, Score]) GetRank(competitorId CompetitorID) (int, error) { +func (slf *BinarySearch[CompetitorID, Score]) GetRank(competitorId CompetitorID) (int, error) { competitorScore, exist := slf.competitors.GetExist(competitorId) if !exist { - return 0, ErrListNotExistCompetitor + return 0, ErrNotExistCompetitor } low, high := 0, len(slf.scores)-1 @@ -131,25 +131,25 @@ func (slf *List[CompetitorID, Score]) GetRank(competitorId CompetitorID) (int, e low = mid + 1 } } - return 0, ErrListIndexErr + return 0, ErrIndexErr } // GetCompetitor 获取特定排名的竞争者 -func (slf *List[CompetitorID, Score]) GetCompetitor(rank int) (competitorId CompetitorID, err error) { +func (slf *BinarySearch[CompetitorID, Score]) GetCompetitor(rank int) (competitorId CompetitorID, err error) { if rank < 0 || rank >= len(slf.scores) { - return competitorId, ErrListNonexistentRanking + return competitorId, ErrNonexistentRanking } return slf.scores[rank].CompetitorId, nil } // GetCompetitorWithRange 获取第start名到第end名竞争者 -func (slf *List[CompetitorID, Score]) GetCompetitorWithRange(start, end int) ([]CompetitorID, error) { +func (slf *BinarySearch[CompetitorID, Score]) GetCompetitorWithRange(start, end int) ([]CompetitorID, error) { if start < 1 || end < start { - return nil, ErrListNonexistentRanking + return nil, ErrNonexistentRanking } total := len(slf.scores) if start > total { - return nil, ErrListNonexistentRanking + return nil, ErrNonexistentRanking } if end > total { end = total @@ -162,16 +162,16 @@ func (slf *List[CompetitorID, Score]) GetCompetitorWithRange(start, end int) ([] } // GetScore 获取竞争者成绩 -func (slf *List[CompetitorID, Score]) GetScore(competitorId CompetitorID) (score Score, err error) { +func (slf *BinarySearch[CompetitorID, Score]) GetScore(competitorId CompetitorID) (score Score, err error) { data, ok := slf.competitors.GetExist(competitorId) if !ok { - return score, ErrListNotExistCompetitor + return score, ErrNotExistCompetitor } return data, nil } // GetScoreDefault 获取竞争者成绩,不存在时返回默认值 -func (slf *List[CompetitorID, Score]) GetScoreDefault(competitorId CompetitorID, defaultValue Score) Score { +func (slf *BinarySearch[CompetitorID, Score]) GetScoreDefault(competitorId CompetitorID, defaultValue Score) Score { score, err := slf.GetScore(competitorId) if err != nil { return defaultValue @@ -181,7 +181,7 @@ func (slf *List[CompetitorID, Score]) GetScoreDefault(competitorId CompetitorID, // GetAllCompetitor 获取所有竞争者ID // - 结果为名次有序的 -func (slf *List[CompetitorID, Score]) GetAllCompetitor() []CompetitorID { +func (slf *BinarySearch[CompetitorID, Score]) GetAllCompetitor() []CompetitorID { var result []CompetitorID for _, data := range slf.scores { result = append(result, data.CompetitorId) @@ -190,13 +190,13 @@ func (slf *List[CompetitorID, Score]) GetAllCompetitor() []CompetitorID { } // Clear 清空排行榜 -func (slf *List[CompetitorID, Score]) Clear() { +func (slf *BinarySearch[CompetitorID, Score]) Clear() { slf.OnRankClearBeforeEvent() slf.competitors.Clear() slf.scores = make([]*scoreItem[CompetitorID, Score], 0) } -func (slf *List[CompetitorID, Score]) Cmp(s1, s2 Score) int { +func (slf *BinarySearch[CompetitorID, Score]) Cmp(s1, s2 Score) int { var result int if s1 > s2 { result = 1 @@ -212,7 +212,7 @@ func (slf *List[CompetitorID, Score]) Cmp(s1, s2 Score) int { } } -func (slf *List[CompetitorID, Score]) competitor(competitorId CompetitorID, oldScore Score, oldRank int, score Score, low, high int) { +func (slf *BinarySearch[CompetitorID, Score]) competitor(competitorId CompetitorID, oldScore Score, oldRank int, score Score, low, high int) { for low <= high { mid := (low + high) / 2 data := slf.scores[mid] @@ -262,7 +262,7 @@ func (slf *List[CompetitorID, Score]) competitor(competitorId CompetitorID, oldS slf.scores = slf.scores[0:count] } -func (slf *List[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error { +func (slf *BinarySearch[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error { var t struct { Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"` Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"` @@ -278,7 +278,7 @@ func (slf *List[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error { return nil } -func (slf *List[CompetitorID, Score]) MarshalJSON() ([]byte, error) { +func (slf *BinarySearch[CompetitorID, Score]) MarshalJSON() ([]byte, error) { var t struct { Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"` Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"` @@ -291,21 +291,21 @@ func (slf *List[CompetitorID, Score]) MarshalJSON() ([]byte, error) { return json.Marshal(&t) } -func (slf *List[CompetitorID, Score]) RegRankChangeEvent(handle RankChangeEventHandle[CompetitorID, Score]) { +func (slf *BinarySearch[CompetitorID, Score]) RegRankChangeEvent(handle BinarySearchRankChangeEventHandle[CompetitorID, Score]) { slf.rankChangeEventHandles = append(slf.rankChangeEventHandles, handle) } -func (slf *List[CompetitorID, Score]) OnRankChangeEvent(competitorId CompetitorID, oldRank, newRank int, oldScore, newScore Score) { +func (slf *BinarySearch[CompetitorID, Score]) OnRankChangeEvent(competitorId CompetitorID, oldRank, newRank int, oldScore, newScore Score) { for _, handle := range slf.rankChangeEventHandles { handle(slf, competitorId, oldRank, newRank, oldScore, newScore) } } -func (slf *List[CompetitorID, Score]) RegRankClearBeforeEvent(handle RankClearBeforeEventHandle[CompetitorID, Score]) { +func (slf *BinarySearch[CompetitorID, Score]) RegRankClearBeforeEvent(handle BinarySearchRankClearBeforeEventHandle[CompetitorID, Score]) { slf.rankClearBeforeEventHandles = append(slf.rankClearBeforeEventHandles, handle) } -func (slf *List[CompetitorID, Score]) OnRankClearBeforeEvent() { +func (slf *BinarySearch[CompetitorID, Score]) OnRankClearBeforeEvent() { for _, handle := range slf.rankClearBeforeEventHandles { handle(slf) } diff --git a/game/leaderboard/binary_search_events.go b/game/leaderboard/binary_search_events.go new file mode 100644 index 0000000..7be94b5 --- /dev/null +++ b/game/leaderboard/binary_search_events.go @@ -0,0 +1,37 @@ +package leaderboard + +import "github.com/kercylan98/minotaur/utils/generic" + +type ( + BinarySearchRankChangeEventHandle[CompetitorID comparable, Score generic.Ordered] func(leaderboard *BinarySearch[CompetitorID, Score], competitorId CompetitorID, oldRank, newRank int, oldScore, newScore Score) + BinarySearchRankClearBeforeEventHandle[CompetitorID comparable, Score generic.Ordered] func(leaderboard *BinarySearch[CompetitorID, Score]) +) + +type binarySearchEvent[CompetitorID comparable, Score generic.Ordered] struct { + rankChangeEventHandles []BinarySearchRankChangeEventHandle[CompetitorID, Score] + rankClearBeforeEventHandles []BinarySearchRankClearBeforeEventHandle[CompetitorID, Score] +} + +// RegRankChangeEventHandle 注册排行榜变更事件 +func (slf *binarySearchEvent[CompetitorID, Score]) RegRankChangeEventHandle(handle BinarySearchRankChangeEventHandle[CompetitorID, Score]) { + slf.rankChangeEventHandles = append(slf.rankChangeEventHandles, handle) +} + +// OnRankChangeEvent 触发排行榜变更事件 +func (slf *binarySearchEvent[CompetitorID, Score]) OnRankChangeEvent(list *BinarySearch[CompetitorID, Score], competitorId CompetitorID, oldRank, newRank int, oldScore, newScore Score) { + for _, handle := range slf.rankChangeEventHandles { + handle(list, competitorId, oldRank, newRank, oldScore, newScore) + } +} + +// RegRankClearBeforeEventHandle 注册排行榜清空前事件 +func (slf *binarySearchEvent[CompetitorID, Score]) RegRankClearBeforeEventHandle(handle BinarySearchRankClearBeforeEventHandle[CompetitorID, Score]) { + slf.rankClearBeforeEventHandles = append(slf.rankClearBeforeEventHandles, handle) +} + +// OnRankClearBeforeEvent 触发排行榜清空前事件 +func (slf *binarySearchEvent[CompetitorID, Score]) OnRankClearBeforeEvent(list *BinarySearch[CompetitorID, Score]) { + for _, handle := range slf.rankClearBeforeEventHandles { + handle(list) + } +} diff --git a/game/leaderboard/binary_search_example_test.go b/game/leaderboard/binary_search_example_test.go new file mode 100644 index 0000000..df8c0be --- /dev/null +++ b/game/leaderboard/binary_search_example_test.go @@ -0,0 +1,77 @@ +package leaderboard_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/game/leaderboard" +) + +func ExampleNewBinarySearch() { + bs := leaderboard.NewBinarySearch[string, int](leaderboard.WithBinarySearchCount[string, int](10)) + + fmt.Println(bs != nil) + // Output: + // true +} + +func ExampleBinarySearch_Competitor() { + bs := leaderboard.NewBinarySearch[string, int](leaderboard.WithBinarySearchCount[string, int](10)) + + scores := []int{6131, 132, 5133, 134, 135, 136, 137, 138, 139, 140, 222, 333, 444, 555, 666} + for i := 1; i <= 15; i++ { + bs.Competitor(fmt.Sprintf("competitor_%2d", i), scores[i-1]) + } + + for rank, competitor := range bs.GetAllCompetitor() { + fmt.Println(rank, competitor) + } + + // Output: + // 0 competitor_ 1 + // 1 competitor_ 3 + // 2 competitor_15 + // 3 competitor_14 + // 4 competitor_13 + // 5 competitor_12 + // 6 competitor_11 + // 7 competitor_10 + // 8 competitor_ 9 + // 9 competitor_ 8 +} + +func ExampleBinarySearch_RemoveCompetitor() { + bs := leaderboard.NewBinarySearch[string, int](leaderboard.WithBinarySearchCount[string, int](10)) + + scores := []int{6131, 132, 5133, 134, 135, 136, 137, 138, 139, 140, 222, 333, 444, 555, 666} + for i := 1; i <= 15; i++ { + bs.Competitor(fmt.Sprintf("competitor_%2d", i), scores[i-1]) + } + bs.RemoveCompetitor("competitor_ 1") + for rank, competitor := range bs.GetAllCompetitor() { + fmt.Println(rank, competitor) + } + + // Output: + // 0 competitor_ 3 + // 1 competitor_15 + // 2 competitor_14 + // 3 competitor_13 + // 4 competitor_12 + // 5 competitor_11 + // 6 competitor_10 + // 7 competitor_ 9 + // 8 competitor_ 8 +} + +func ExampleBinarySearch_GetRank() { + bs := leaderboard.NewBinarySearch[string, int](leaderboard.WithBinarySearchCount[string, int](10)) + + scores := []int{6131, 132, 5133, 134, 135, 136, 137, 138, 139, 140, 222, 333, 444, 555, 666} + for i := 1; i <= 15; i++ { + bs.Competitor(fmt.Sprintf("competitor_%2d", i), scores[i-1]) + } + + fmt.Println(bs.GetRank("competitor_ 1")) + + // Output: + // 0 +} diff --git a/game/leaderboard/binary_search_options.go b/game/leaderboard/binary_search_options.go new file mode 100644 index 0000000..4ab5528 --- /dev/null +++ b/game/leaderboard/binary_search_options.go @@ -0,0 +1,24 @@ +package leaderboard + +import "github.com/kercylan98/minotaur/utils/generic" + +type BinarySearchOption[CompetitorID comparable, Score generic.Ordered] func(list *BinarySearch[CompetitorID, Score]) + +// WithBinarySearchCount 通过限制排行榜竞争者数量来创建排行榜 +// - 默认情况下允许100位竞争者 +func WithBinarySearchCount[CompetitorID comparable, Score generic.Ordered](rankCount int) BinarySearchOption[CompetitorID, Score] { + return func(bs *BinarySearch[CompetitorID, Score]) { + if rankCount <= 0 { + rankCount = 1 + } + bs.rankCount = rankCount + } +} + +// WithBinarySearchASC 通过升序的方式创建排行榜 +// - 默认情况下为降序 +func WithBinarySearchASC[CompetitorID comparable, Score generic.Ordered]() BinarySearchOption[CompetitorID, Score] { + return func(bs *BinarySearch[CompetitorID, Score]) { + bs.asc = true + } +} diff --git a/game/leaderboard/errors.go b/game/leaderboard/errors.go new file mode 100644 index 0000000..78da003 --- /dev/null +++ b/game/leaderboard/errors.go @@ -0,0 +1,9 @@ +package leaderboard + +import "errors" + +var ( + ErrNotExistCompetitor = errors.New("leaderboard not exist competitor") + ErrIndexErr = errors.New("leaderboard index error") + ErrNonexistentRanking = errors.New("nonexistent ranking") +) diff --git a/game/ranking/list_errors.go b/game/ranking/list_errors.go deleted file mode 100644 index b4db00c..0000000 --- a/game/ranking/list_errors.go +++ /dev/null @@ -1,9 +0,0 @@ -package ranking - -import "errors" - -var ( - ErrListNotExistCompetitor = errors.New("ranking list not exist competitor") - ErrListIndexErr = errors.New("ranking list index error") - ErrListNonexistentRanking = errors.New("nonexistent ranking") -) diff --git a/game/ranking/list_events.go b/game/ranking/list_events.go deleted file mode 100644 index 6e6b925..0000000 --- a/game/ranking/list_events.go +++ /dev/null @@ -1,37 +0,0 @@ -package ranking - -import "github.com/kercylan98/minotaur/utils/generic" - -type ( - RankChangeEventHandle[CompetitorID comparable, Score generic.Ordered] func(list *List[CompetitorID, Score], competitorId CompetitorID, oldRank, newRank int, oldScore, newScore Score) - RankClearBeforeEventHandle[CompetitorID comparable, Score generic.Ordered] func(list *List[CompetitorID, Score]) -) - -type event[CompetitorID comparable, Score generic.Ordered] struct { - rankChangeEventHandles []RankChangeEventHandle[CompetitorID, Score] - rankClearBeforeEventHandles []RankClearBeforeEventHandle[CompetitorID, Score] -} - -// RegRankChangeEventHandle 注册排行榜变更事件 -func (slf *event[CompetitorID, Score]) RegRankChangeEventHandle(handle RankChangeEventHandle[CompetitorID, Score]) { - slf.rankChangeEventHandles = append(slf.rankChangeEventHandles, handle) -} - -// OnRankChangeEvent 触发排行榜变更事件 -func (slf *event[CompetitorID, Score]) OnRankChangeEvent(list *List[CompetitorID, Score], competitorId CompetitorID, oldRank, newRank int, oldScore, newScore Score) { - for _, handle := range slf.rankChangeEventHandles { - handle(list, competitorId, oldRank, newRank, oldScore, newScore) - } -} - -// RegRankClearBeforeEventHandle 注册排行榜清空前事件 -func (slf *event[CompetitorID, Score]) RegRankClearBeforeEventHandle(handle RankClearBeforeEventHandle[CompetitorID, Score]) { - slf.rankClearBeforeEventHandles = append(slf.rankClearBeforeEventHandles, handle) -} - -// OnRankClearBeforeEvent 触发排行榜清空前事件 -func (slf *event[CompetitorID, Score]) OnRankClearBeforeEvent(list *List[CompetitorID, Score]) { - for _, handle := range slf.rankClearBeforeEventHandles { - handle(list) - } -} diff --git a/game/ranking/list_example_test.go b/game/ranking/list_example_test.go deleted file mode 100644 index 6d28b2e..0000000 --- a/game/ranking/list_example_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package ranking_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/game/ranking" -) - -func ExampleNewList() { - ranklingList := ranking.NewList[string, int](ranking.WithListCount[string, int](10)) - - fmt.Println(ranklingList != nil) - // Output: - // true -} - -func ExampleList_Competitor() { - ranklingList := ranking.NewList[string, int](ranking.WithListCount[string, int](10)) - - scores := []int{6131, 132, 5133, 134, 135, 136, 137, 138, 139, 140, 222, 333, 444, 555, 666} - for i := 1; i <= 15; i++ { - ranklingList.Competitor(fmt.Sprintf("competitor_%2d", i), scores[i-1]) - } - - for rank, competitor := range ranklingList.GetAllCompetitor() { - fmt.Println(rank, competitor) - } - - // Output: - // 0 competitor_ 1 - // 1 competitor_ 3 - // 2 competitor_15 - // 3 competitor_14 - // 4 competitor_13 - // 5 competitor_12 - // 6 competitor_11 - // 7 competitor_10 - // 8 competitor_ 9 - // 9 competitor_ 8 -} - -func ExampleList_RemoveCompetitor() { - ranklingList := ranking.NewList[string, int](ranking.WithListCount[string, int](10)) - - scores := []int{6131, 132, 5133, 134, 135, 136, 137, 138, 139, 140, 222, 333, 444, 555, 666} - for i := 1; i <= 15; i++ { - ranklingList.Competitor(fmt.Sprintf("competitor_%2d", i), scores[i-1]) - } - ranklingList.RemoveCompetitor("competitor_ 1") - for rank, competitor := range ranklingList.GetAllCompetitor() { - fmt.Println(rank, competitor) - } - - // Output: - // 0 competitor_ 3 - // 1 competitor_15 - // 2 competitor_14 - // 3 competitor_13 - // 4 competitor_12 - // 5 competitor_11 - // 6 competitor_10 - // 7 competitor_ 9 - // 8 competitor_ 8 -} - -func ExampleList_GetRank() { - ranklingList := ranking.NewList[string, int](ranking.WithListCount[string, int](10)) - - scores := []int{6131, 132, 5133, 134, 135, 136, 137, 138, 139, 140, 222, 333, 444, 555, 666} - for i := 1; i <= 15; i++ { - ranklingList.Competitor(fmt.Sprintf("competitor_%2d", i), scores[i-1]) - } - - fmt.Println(ranklingList.GetRank("competitor_ 1")) - - // Output: - // 0 -} diff --git a/game/ranking/list_options.go b/game/ranking/list_options.go deleted file mode 100644 index 979ef10..0000000 --- a/game/ranking/list_options.go +++ /dev/null @@ -1,24 +0,0 @@ -package ranking - -import "github.com/kercylan98/minotaur/utils/generic" - -type ListOption[CompetitorID comparable, Score generic.Ordered] func(list *List[CompetitorID, Score]) - -// WithListCount 通过限制排行榜竞争者数量来创建排行榜 -// - 默认情况下允许100位竞争者 -func WithListCount[CompetitorID comparable, Score generic.Ordered](rankCount int) ListOption[CompetitorID, Score] { - return func(list *List[CompetitorID, Score]) { - if rankCount <= 0 { - rankCount = 1 - } - list.rankCount = rankCount - } -} - -// WithListASC 通过升序的方式创建排行榜 -// - 默认情况下为降序 -func WithListASC[CompetitorID comparable, Score generic.Ordered]() ListOption[CompetitorID, Score] { - return func(list *List[CompetitorID, Score]) { - list.asc = true - } -}