You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

440 lines
11 KiB
Go

package valueobjects
import (
"fmt"
"time"
"github.com/knowfoolery/backend/shared/errors"
)
type LeaderboardStatistics struct {
totalEntries int32
activeEntries int32
totalScoreSum int64
averageScore float32
highestScore int32
lowestScore int32
medianScore float32
totalGamesPlayed int64
uniquePlayers int32
lastUpdated time.Time
createdAt time.Time
resetCount int32
lastResetAt time.Time
topScoreStreaks map[string]int32
competitiveness float32
activityLevel float32
growthRate float32
retentionRate float32
metadata map[string]interface{}
}
func NewLeaderboardStatistics() *LeaderboardStatistics {
now := time.Now()
return &LeaderboardStatistics{
totalEntries: 0,
activeEntries: 0,
totalScoreSum: 0,
averageScore: 0.0,
highestScore: 0,
lowestScore: 0,
medianScore: 0.0,
totalGamesPlayed: 0,
uniquePlayers: 0,
lastUpdated: now,
createdAt: now,
resetCount: 0,
lastResetAt: time.Time{},
topScoreStreaks: make(map[string]int32),
competitiveness: 0.0,
activityLevel: 0.0,
growthRate: 0.0,
retentionRate: 0.0,
metadata: make(map[string]interface{}),
}
}
func (ls *LeaderboardStatistics) TotalEntries() int32 {
return ls.totalEntries
}
func (ls *LeaderboardStatistics) ActiveEntries() int32 {
return ls.activeEntries
}
func (ls *LeaderboardStatistics) TotalScoreSum() int64 {
return ls.totalScoreSum
}
func (ls *LeaderboardStatistics) AverageScore() float32 {
return ls.averageScore
}
func (ls *LeaderboardStatistics) HighestScore() int32 {
return ls.highestScore
}
func (ls *LeaderboardStatistics) LowestScore() int32 {
return ls.lowestScore
}
func (ls *LeaderboardStatistics) MedianScore() float32 {
return ls.medianScore
}
func (ls *LeaderboardStatistics) TotalGamesPlayed() int64 {
return ls.totalGamesPlayed
}
func (ls *LeaderboardStatistics) UniquePlayers() int32 {
return ls.uniquePlayers
}
func (ls *LeaderboardStatistics) LastUpdated() time.Time {
return ls.lastUpdated
}
func (ls *LeaderboardStatistics) CreatedAt() time.Time {
return ls.createdAt
}
func (ls *LeaderboardStatistics) ResetCount() int32 {
return ls.resetCount
}
func (ls *LeaderboardStatistics) LastResetAt() time.Time {
return ls.lastResetAt
}
func (ls *LeaderboardStatistics) TopScoreStreaks() map[string]int32 {
result := make(map[string]int32)
for k, v := range ls.topScoreStreaks {
result[k] = v
}
return result
}
func (ls *LeaderboardStatistics) Competitiveness() float32 {
return ls.competitiveness
}
func (ls *LeaderboardStatistics) ActivityLevel() float32 {
return ls.activityLevel
}
func (ls *LeaderboardStatistics) GrowthRate() float32 {
return ls.growthRate
}
func (ls *LeaderboardStatistics) RetentionRate() float32 {
return ls.retentionRate
}
func (ls *LeaderboardStatistics) Metadata() map[string]interface{} {
result := make(map[string]interface{})
for k, v := range ls.metadata {
result[k] = v
}
return result
}
func (ls *LeaderboardStatistics) UpdateScoreStatistics(scores []int32) error {
if len(scores) == 0 {
return nil
}
ls.totalEntries = int32(len(scores))
ls.activeEntries = ls.totalEntries
var sum int64
highest := scores[0]
lowest := scores[0]
for _, score := range scores {
sum += int64(score)
if score > highest {
highest = score
}
if score < lowest {
lowest = score
}
}
ls.totalScoreSum = sum
ls.averageScore = float32(sum) / float32(len(scores))
ls.highestScore = highest
ls.lowestScore = lowest
ls.medianScore = ls.calculateMedian(scores)
ls.lastUpdated = time.Now()
return nil
}
func (ls *LeaderboardStatistics) calculateMedian(scores []int32) float32 {
if len(scores) == 0 {
return 0
}
sorted := make([]int32, len(scores))
copy(sorted, scores)
for i := 0; i < len(sorted)-1; i++ {
for j := i + 1; j < len(sorted); j++ {
if sorted[i] > sorted[j] {
sorted[i], sorted[j] = sorted[j], sorted[i]
}
}
}
n := len(sorted)
if n%2 == 0 {
return float32(sorted[n/2-1]+sorted[n/2]) / 2.0
}
return float32(sorted[n/2])
}
func (ls *LeaderboardStatistics) IncrementGamesPlayed(count int64) error {
if count < 0 {
return errors.ErrInvalidGameCount
}
ls.totalGamesPlayed += count
ls.lastUpdated = time.Now()
return nil
}
func (ls *LeaderboardStatistics) SetUniquePlayers(count int32) error {
if count < 0 {
return errors.ErrInvalidPlayerCount
}
ls.uniquePlayers = count
ls.lastUpdated = time.Now()
return nil
}
func (ls *LeaderboardStatistics) IncrementResetCount() {
ls.resetCount++
ls.lastResetAt = time.Now()
ls.lastUpdated = time.Now()
}
func (ls *LeaderboardStatistics) UpdateTopScoreStreak(playerID string, streak int32) error {
if playerID == "" {
return errors.ErrInvalidPlayerID
}
if streak < 0 {
return errors.ErrInvalidStreak
}
if streak == 0 {
delete(ls.topScoreStreaks, playerID)
} else {
ls.topScoreStreaks[playerID] = streak
}
ls.lastUpdated = time.Now()
return nil
}
func (ls *LeaderboardStatistics) CalculateCompetitiveness() {
if ls.totalEntries <= 1 {
ls.competitiveness = 0.0
return
}
if ls.highestScore == ls.lowestScore {
ls.competitiveness = 0.0
return
}
scoreRange := float32(ls.highestScore - ls.lowestScore)
normalizedRange := scoreRange / float32(ls.highestScore)
participationFactor := float32(ls.activeEntries) / float32(ls.totalEntries)
ls.competitiveness = normalizedRange * participationFactor
if ls.competitiveness > 1.0 {
ls.competitiveness = 1.0
}
ls.lastUpdated = time.Now()
}
func (ls *LeaderboardStatistics) CalculateActivityLevel(recentGames int64, timeWindow time.Duration) {
if timeWindow <= 0 {
ls.activityLevel = 0.0
return
}
hoursInWindow := float64(timeWindow) / float64(time.Hour)
if hoursInWindow == 0 {
ls.activityLevel = 0.0
return
}
ls.activityLevel = float32(float64(recentGames) / hoursInWindow)
ls.lastUpdated = time.Now()
}
func (ls *LeaderboardStatistics) CalculateGrowthRate(previousPeriodPlayers int32, currentPeriodPlayers int32) {
if previousPeriodPlayers <= 0 {
if currentPeriodPlayers > 0 {
ls.growthRate = 1.0
} else {
ls.growthRate = 0.0
}
return
}
ls.growthRate = float32(currentPeriodPlayers-previousPeriodPlayers) / float32(previousPeriodPlayers)
ls.lastUpdated = time.Now()
}
func (ls *LeaderboardStatistics) CalculateRetentionRate(returningPlayers int32, totalPlayers int32) {
if totalPlayers <= 0 {
ls.retentionRate = 0.0
return
}
ls.retentionRate = float32(returningPlayers) / float32(totalPlayers)
if ls.retentionRate > 1.0 {
ls.retentionRate = 1.0
}
ls.lastUpdated = time.Now()
}
func (ls *LeaderboardStatistics) SetMetadata(key string, value interface{}) error {
if key == "" {
return errors.ErrInvalidMetadata
}
ls.metadata[key] = value
ls.lastUpdated = time.Now()
return nil
}
func (ls *LeaderboardStatistics) RemoveMetadata(key string) {
delete(ls.metadata, key)
ls.lastUpdated = time.Now()
}
func (ls *LeaderboardStatistics) Reset() {
ls.totalEntries = 0
ls.activeEntries = 0
ls.totalScoreSum = 0
ls.averageScore = 0.0
ls.highestScore = 0
ls.lowestScore = 0
ls.medianScore = 0.0
ls.totalGamesPlayed = 0
ls.uniquePlayers = 0
ls.topScoreStreaks = make(map[string]int32)
ls.competitiveness = 0.0
ls.activityLevel = 0.0
ls.growthRate = 0.0
ls.retentionRate = 0.0
ls.IncrementResetCount()
}
func (ls *LeaderboardStatistics) GetAge() time.Duration {
return time.Since(ls.createdAt)
}
func (ls *LeaderboardStatistics) GetTimeSinceLastUpdate() time.Duration {
return time.Since(ls.lastUpdated)
}
func (ls *LeaderboardStatistics) GetTimeSinceLastReset() time.Duration {
if ls.lastResetAt.IsZero() {
return ls.GetAge()
}
return time.Since(ls.lastResetAt)
}
func (ls *LeaderboardStatistics) IsHealthy() bool {
timeSinceUpdate := ls.GetTimeSinceLastUpdate()
return timeSinceUpdate < time.Hour*24
}
func (ls *LeaderboardStatistics) String() string {
return fmt.Sprintf("LeaderboardStatistics{TotalEntries: %d, AvgScore: %.2f, Highest: %d, Lowest: %d, Players: %d}",
ls.totalEntries, ls.averageScore, ls.highestScore, ls.lowestScore, ls.uniquePlayers)
}
type LeaderboardStatisticsSnapshot struct {
TotalEntries int32 `json:"total_entries"`
ActiveEntries int32 `json:"active_entries"`
TotalScoreSum int64 `json:"total_score_sum"`
AverageScore float32 `json:"average_score"`
HighestScore int32 `json:"highest_score"`
LowestScore int32 `json:"lowest_score"`
MedianScore float32 `json:"median_score"`
TotalGamesPlayed int64 `json:"total_games_played"`
UniquePlayers int32 `json:"unique_players"`
LastUpdated time.Time `json:"last_updated"`
CreatedAt time.Time `json:"created_at"`
ResetCount int32 `json:"reset_count"`
LastResetAt time.Time `json:"last_reset_at"`
TopScoreStreaks map[string]int32 `json:"top_score_streaks"`
Competitiveness float32 `json:"competitiveness"`
ActivityLevel float32 `json:"activity_level"`
GrowthRate float32 `json:"growth_rate"`
RetentionRate float32 `json:"retention_rate"`
Metadata map[string]interface{} `json:"metadata"`
}
func (ls *LeaderboardStatistics) ToSnapshot() LeaderboardStatisticsSnapshot {
return LeaderboardStatisticsSnapshot{
TotalEntries: ls.totalEntries,
ActiveEntries: ls.activeEntries,
TotalScoreSum: ls.totalScoreSum,
AverageScore: ls.averageScore,
HighestScore: ls.highestScore,
LowestScore: ls.lowestScore,
MedianScore: ls.medianScore,
TotalGamesPlayed: ls.totalGamesPlayed,
UniquePlayers: ls.uniquePlayers,
LastUpdated: ls.lastUpdated,
CreatedAt: ls.createdAt,
ResetCount: ls.resetCount,
LastResetAt: ls.lastResetAt,
TopScoreStreaks: ls.TopScoreStreaks(),
Competitiveness: ls.competitiveness,
ActivityLevel: ls.activityLevel,
GrowthRate: ls.growthRate,
RetentionRate: ls.retentionRate,
Metadata: ls.Metadata(),
}
}
func (ls *LeaderboardStatistics) FromSnapshot(snapshot LeaderboardStatisticsSnapshot) error {
ls.totalEntries = snapshot.TotalEntries
ls.activeEntries = snapshot.ActiveEntries
ls.totalScoreSum = snapshot.TotalScoreSum
ls.averageScore = snapshot.AverageScore
ls.highestScore = snapshot.HighestScore
ls.lowestScore = snapshot.LowestScore
ls.medianScore = snapshot.MedianScore
ls.totalGamesPlayed = snapshot.TotalGamesPlayed
ls.uniquePlayers = snapshot.UniquePlayers
ls.lastUpdated = snapshot.LastUpdated
ls.createdAt = snapshot.CreatedAt
ls.resetCount = snapshot.ResetCount
ls.lastResetAt = snapshot.LastResetAt
ls.competitiveness = snapshot.Competitiveness
ls.activityLevel = snapshot.ActivityLevel
ls.growthRate = snapshot.GrowthRate
ls.retentionRate = snapshot.RetentionRate
ls.topScoreStreaks = make(map[string]int32)
for k, v := range snapshot.TopScoreStreaks {
ls.topScoreStreaks[k] = v
}
ls.metadata = make(map[string]interface{})
for k, v := range snapshot.Metadata {
ls.metadata[k] = v
}
return nil
}