tournaments/standings.go
func NewStandings(tournaments Tournaments) PlayerStandings {

	// First, find all active players for these tournaments
	// Also, get the max number of players for a given set of tournaments
	// This gives us the basis for low point scoring
	activePlayers, maxPlayers := getActivePlayers(tournaments)

	// Then, loop through tournaments again to keep track of relevant stats
	winnings := make(map[uuid.UUID]int)
	sumPlace := make(map[uuid.UUID]int)
	points := make(map[uuid.UUID][]int)
	numHeadsUp := make(map[uuid.UUID]int)
	numWins := make(map[uuid.UUID]int)
	numPlayed := make(map[uuid.UUID]int)
	knockouts := make(map[uuid.UUID]int)
	results := make(map[uuid.UUID][]PlayerResult)
	numTotal := 0

	for _, t := range tournaments {
		if !t.Played || len(t.Result) == 0 {
			continue
		}
		numTotal += 1
		seenPlayer := make(map[uuid.UUID]bool)
		for i, player := range t.Result {
			place := i + 1
			results[player] = append(results[player], PlayerResult{
				Place:      place,
				When:       t.Info.Scheduled,
				NumPlayers: len(t.Result),
			})

			sumPlace[player] += place
			numPlayed[player] += 1
			seenPlayer[player] = true
			points[player] = append(points[player], place)

			switch place {
			case 1:
				numWins[player] += 1
				winnings[player] += (len(t.Result) - 2) * t.Info.Stake
				numHeadsUp[player] += 1
			case 2:
				numHeadsUp[player] += 1
			default:
				winnings[player] -= t.Info.Stake
			}
		}

		for _, player := range activePlayers {
			if _, seen := seenPlayer[player]; !seen {
				points[player] = append(points[player], maxPlayers+1)
			}
		}

		for player := range t.BountyHunters {
			knockouts[player] += len(t.BountyHunters[player])
		}
	}

	// Finally, loop through active players and set totals, returning standings
	var standings PlayerStandings

	for _, player := range activePlayers {

		// Remove worst point score at 10 and 20 played tournaments
		pp := points[player]
		sort.Ints(pp)

		if numPlayed[player] >= 10 {
			i := len(pp) - 1
			pp = append(pp[:i], pp[i+1:]...) // delete element at index i (last)
		}
		if numPlayed[player] >= 20 {
			i := len(pp) - 1
			pp = append(pp[:i], pp[i+1:]...) // delete element at index i (last)
		}

		// Now, sum up the points
		sumPoints := 0
		for _, p := range pp {
			sumPoints += p
		}

		// Check if player has enough tournaments
		// TODO: Is this a property of season?
		enough := false
		if numPlayed[player] >= 11 {
			enough = true
		}

		standings = append(standings, &PlayerStanding{
			Player:     player,
			Results:    results[player],
			Winnings:   winnings[player],
			AvgPlace:   float64(sumPlace[player]) / float64(numPlayed[player]),
			Points:     sumPoints,
			NumHeadsUp: numHeadsUp[player],
			NumWins:    numWins[player],
			NumPlayed:  numPlayed[player],
			Enough:     enough,
			NumTotal:   numTotal,
			Knockouts:  knockouts[player],
		})
	}

	return standings
}
func NewStandings(tournaments Tournaments) PlayerStandings {

	// First, find all active players for these tournaments
	// Also, get the max number of players for a given set of tournaments
	// This gives us the basis for low point scoring
	activePlayers, maxPlayers := getActivePlayers(tournaments)

	// Then, loop through tournaments again to keep track of relevant stats
	winnings := make(map[uuid.UUID]int)
	sumPlace := make(map[uuid.UUID]int)
	points := make(map[uuid.UUID][]int)
	numHeadsUp := make(map[uuid.UUID]int)
	numWins := make(map[uuid.UUID]int)
	numPlayed := make(map[uuid.UUID]int)
	knockouts := make(map[uuid.UUID]int)
	results := make(map[uuid.UUID][]PlayerResult)
	numTotal := 0

	for _, t := range tournaments {
		if !t.Played || len(t.Result) == 0 {
			continue
		}
		numTotal += 1
		seenPlayer := make(map[uuid.UUID]bool)
		for i, player := range t.Result {
			place := i + 1
			results[player] = append(results[player], PlayerResult{
				Place:      place,
				When:       t.Info.Scheduled,
				NumPlayers: len(t.Result),
			})

			sumPlace[player] += place
			numPlayed[player] += 1
			seenPlayer[player] = true
			points[player] = append(points[player], place)

			switch place {
			case 1:
				numWins[player] += 1
				winnings[player] += (len(t.Result) - 2) * t.Info.Stake
				numHeadsUp[player] += 1
			case 2:
				numHeadsUp[player] += 1
			default:
				winnings[player] -= t.Info.Stake
			}
		}

		for _, player := range activePlayers {
			if _, seen := seenPlayer[player]; !seen {
				points[player] = append(points[player], maxPlayers+1)
			}
		}

		for player := range t.BountyHunters {
			knockouts[player] += len(t.BountyHunters[player])
		}
	}

	// Finally, loop through active players and set totals, returning standings
	var standings PlayerStandings

	for _, player := range activePlayers {

		// Remove worst point score at 10 and 20 played tournaments
		pp := points[player]
		sort.Ints(pp)

		if numPlayed[player] >= 10 {
			i := len(pp) - 1
			pp = append(pp[:i], pp[i+1:]...) // delete element at index i (last)
		}
		if numPlayed[player] >= 20 {
			i := len(pp) - 1
			pp = append(pp[:i], pp[i+1:]...) // delete element at index i (last)
		}

		// Now, sum up the points
		sumPoints := 0
		for _, p := range pp {
			sumPoints += p
		}

		// Check if player has enough tournaments
		// TODO: Is this a property of season?
		enough := false
		if numPlayed[player] >= 11 {
			enough = true
		}

		standings = append(standings, &PlayerStanding{
			Player:     player,
			Results:    results[player],
			Winnings:   winnings[player],
			AvgPlace:   float64(sumPlace[player]) / float64(numPlayed[player]),
			Points:     sumPoints,
			NumHeadsUp: numHeadsUp[player],
			NumWins:    numWins[player],
			NumPlayed:  numPlayed[player],
			Enough:     enough,
			NumTotal:   numTotal,
			Knockouts:  knockouts[player],
		})
	}

	return standings
}
// TODO: Split into smaller functions