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 }