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

182 lines
4.4 KiB

package derby
import (
"math/rand"
"time"
)
// Racer represents a racer for heat generation
type Racer struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
CarNumber string `json:"car_number"`
}
// Heat represents a single race with 4 lanes
type Heat struct {
HeatNum int `json:"heat_num"`
Lane1ID *int64 `json:"lane1_id"`
Lane2ID *int64 `json:"lane2_id"`
Lane3ID *int64 `json:"lane3_id"`
Lane4ID *int64 `json:"lane4_id"`
}
func CreateRacer(id int64, firstName string, lastName string, carNumber string) Racer {
return Racer{ID: id, FirstName: firstName, LastName: lastName, CarNumber: carNumber}
}
// GenerateHeats creates a set of heats for a group of racers
// Each racer should race in each lane once, with different opponents when possible
func GenerateHeats(racers []Racer) []Heat {
// If no racers, return empty heats
if len(racers) == 0 {
return []Heat{}
}
// Seed the random number generator
rand.Seed(time.Now().UnixNano())
// Shuffle racers to randomize initial order
shuffledRacers := make([]Racer, len(racers))
copy(shuffledRacers, racers)
rand.Shuffle(len(shuffledRacers), func(i, j int) {
shuffledRacers[i], shuffledRacers[j] = shuffledRacers[j], shuffledRacers[i]
})
// Track which lanes each racer has used
racerLanes := make(map[int64]map[int]bool)
for _, racer := range racers {
racerLanes[racer.ID] = make(map[int]bool)
}
// Track which racers have raced against each other
racerOpponents := make(map[int64]map[int64]bool)
for _, racer := range racers {
racerOpponents[racer.ID] = make(map[int64]bool)
}
// Calculate number of heats needed
// Each racer needs to race in 4 lanes, so total races = (racers * 4) / 4 = racers
// But we need to round up to ensure all racers get all lanes
numHeats := len(racers)
if len(racers) < 4 {
numHeats = 1
} else if len(racers)%4 != 0 {
// Add extra heats to ensure all racers get all lanes
numHeats = (len(racers) + 3) / 4 * 4
}
// Generate heats
heats := make([]Heat, 0, numHeats)
heatNum := 1
// Create a queue of racers that need to race in each lane
laneQueues := make([][]int64, 4)
for i := 0; i < 4; i++ {
laneQueues[i] = make([]int64, 0, len(racers))
for _, racer := range shuffledRacers {
laneQueues[i] = append(laneQueues[i], racer.ID)
}
// Shuffle each lane queue differently
rand.Shuffle(len(laneQueues[i]), func(j, k int) {
laneQueues[i][j], laneQueues[i][k] = laneQueues[i][k], laneQueues[i][j]
})
}
// Keep generating heats until all racers have raced in all lanes
for {
// Check if all racers have raced in all lanes
allDone := true
for _, racer := range racers {
if len(racerLanes[racer.ID]) < 4 {
allDone = false
break
}
}
if allDone {
break
}
// Create a new heat
heat := Heat{
HeatNum: heatNum,
}
heatNum++
// Fill lanes
filledLanes := 0
usedRacers := make(map[int64]bool)
// Try to fill each lane
for lane := 1; lane <= 4; lane++ {
// Find a racer for this lane
var selectedRacer *int64 = nil
// First try racers who haven't used this lane yet
for i := 0; i < len(laneQueues[lane-1]); i++ {
racerID := laneQueues[lane-1][i]
// Skip if racer already in this heat
if usedRacers[racerID] {
continue
}
// Skip if racer already used this lane
if racerLanes[racerID][lane] {
continue
}
// Use this racer
selectedRacer = &racerID
// Remove from queue
laneQueues[lane-1] = append(laneQueues[lane-1][:i], laneQueues[lane-1][i+1:]...)
break
}
// If no racer found, leave lane empty
if selectedRacer != nil {
// Mark lane as used for this racer
racerLanes[*selectedRacer][lane] = true
// Mark racer as used in this heat
usedRacers[*selectedRacer] = true
// Update opponents
for otherRacerID := range usedRacers {
if otherRacerID != *selectedRacer {
racerOpponents[*selectedRacer][otherRacerID] = true
racerOpponents[otherRacerID][*selectedRacer] = true
}
}
filledLanes++
}
// Set lane in heat
switch lane {
case 1:
heat.Lane1ID = selectedRacer
case 2:
heat.Lane2ID = selectedRacer
case 3:
heat.Lane3ID = selectedRacer
case 4:
heat.Lane4ID = selectedRacer
}
}
// Only add heat if at least one lane is filled
if filledLanes > 0 {
heats = append(heats, heat)
} else {
// If we couldn't fill any lanes, we're done
break
}
}
return heats
}