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
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
|
|
}
|