mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 21:56:50 -04:00
Added schedule generation.
This commit is contained in:
@@ -10,9 +10,12 @@ import (
|
||||
func TestEventSettingsReadWrite(t *testing.T) {
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
|
||||
db, _ := OpenDatabase(testDbPath)
|
||||
db, err := OpenDatabase(testDbPath)
|
||||
if err != nil {
|
||||
t.Error("Error:", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
eventSettings, err := db.GetEventSettings()
|
||||
if err != nil {
|
||||
t.Error("Error:", err)
|
||||
|
||||
3
main.go
3
main.go
@@ -6,9 +6,12 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
fmt.Println("Cheesy Arena")
|
||||
}
|
||||
|
||||
|
||||
97
schedule.go
Normal file
97
schedule.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
//
|
||||
// Functions for creating practice, qualification and elimination match schedules.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const schedulesDir = "schedules"
|
||||
const teamsPerMatch = 6
|
||||
|
||||
type ScheduleBlock struct {
|
||||
startTime time.Time
|
||||
numMatches int
|
||||
matchSpacingSec int
|
||||
}
|
||||
|
||||
func BuildRandomSchedule(teams []Team, scheduleBlocks []ScheduleBlock, matchType string) ([]Match, error) {
|
||||
// Load the anonymized, pre-randomized match schedule for the given number of teams and matches per team.
|
||||
numTeams := len(teams)
|
||||
numMatches := countMatches(scheduleBlocks)
|
||||
matchesPerTeam := int(float32(numMatches*teamsPerMatch) / float32(numTeams))
|
||||
file, err := os.Open(fmt.Sprintf("%s/%d_%d.csv", schedulesDir, numTeams, matchesPerTeam))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("No schedule exists for %d teams and %d matches", numTeams, matchesPerTeam)
|
||||
}
|
||||
defer file.Close()
|
||||
reader := csv.NewReader(file)
|
||||
csvLines, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(csvLines) != numMatches {
|
||||
return nil, fmt.Errorf("Schedule file contains %d matches, expected %d", len(csvLines), numMatches)
|
||||
}
|
||||
|
||||
// Convert string fields from schedule to integers.
|
||||
anonSchedule := make([][12]int, numMatches)
|
||||
for i := 0; i < numMatches; i++ {
|
||||
for j := 0; j < 12; j++ {
|
||||
anonSchedule[i][j], err = strconv.Atoi(csvLines[i][j])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a random permutation of the team ordering to fill into the pre-randomized schedule.
|
||||
teamShuffle := rand.Perm(numTeams)
|
||||
matches := make([]Match, numMatches)
|
||||
for i, anonMatch := range anonSchedule {
|
||||
matches[i].Id = i + 1
|
||||
matches[i].Type = matchType
|
||||
matches[i].DisplayName = strconv.Itoa(i + 1)
|
||||
matches[i].Red1 = teams[teamShuffle[anonMatch[0]-1]].Id
|
||||
matches[i].Red1IsSurrogate = (anonMatch[1] == 1)
|
||||
matches[i].Red2 = teams[teamShuffle[anonMatch[2]-1]].Id
|
||||
matches[i].Red2IsSurrogate = (anonMatch[3] == 1)
|
||||
matches[i].Red3 = teams[teamShuffle[anonMatch[4]-1]].Id
|
||||
matches[i].Red3IsSurrogate = (anonMatch[5] == 1)
|
||||
matches[i].Blue1 = teams[teamShuffle[anonMatch[6]-1]].Id
|
||||
matches[i].Blue1IsSurrogate = (anonMatch[7] == 1)
|
||||
matches[i].Blue2 = teams[teamShuffle[anonMatch[8]-1]].Id
|
||||
matches[i].Blue2IsSurrogate = (anonMatch[9] == 1)
|
||||
matches[i].Blue3 = teams[teamShuffle[anonMatch[10]-1]].Id
|
||||
matches[i].Blue3IsSurrogate = (anonMatch[11] == 1)
|
||||
matches[i].Status = ""
|
||||
matches[i].StartedAt = time.Unix(0, 0).UTC()
|
||||
}
|
||||
|
||||
// Fill in the match times.
|
||||
matchIndex := 0
|
||||
for _, block := range scheduleBlocks {
|
||||
for i := 0; i < block.numMatches; i++ {
|
||||
matches[matchIndex].Time = block.startTime.Add(time.Duration(i*block.matchSpacingSec) * time.Second)
|
||||
matchIndex++
|
||||
}
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func countMatches(scheduleBlocks []ScheduleBlock) int {
|
||||
numMatches := 0
|
||||
for _, block := range scheduleBlocks {
|
||||
numMatches += block.numMatches
|
||||
}
|
||||
return numMatches
|
||||
}
|
||||
125
schedule_test.go
Normal file
125
schedule_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNonExistentSchedule(t *testing.T) {
|
||||
teams := make([]Team, 6)
|
||||
scheduleBlocks := []ScheduleBlock{{time.Unix(0, 0).UTC(), 2, 60}}
|
||||
_, err := BuildRandomSchedule(teams, scheduleBlocks, "test")
|
||||
expectedErr := "No schedule exists for 6 teams and 2 matches"
|
||||
if err == nil || err.Error() != expectedErr {
|
||||
t.Errorf("Expected '%s', got '%v'", expectedErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMalformedSchedule(t *testing.T) {
|
||||
scheduleFile, _ := os.Create("schedules/6_1.csv")
|
||||
defer os.Remove("schedules/6_1.csv")
|
||||
scheduleFile.WriteString("1,0,2,0,3,0,4,0,5,0,6,0\n6,0,5,0,4,0,3,0,2,0,1,0\n")
|
||||
scheduleFile.Close()
|
||||
teams := make([]Team, 6)
|
||||
scheduleBlocks := []ScheduleBlock{{time.Unix(0, 0).UTC(), 1, 60}}
|
||||
_, err := BuildRandomSchedule(teams, scheduleBlocks, "test")
|
||||
expectedErr := "Schedule file contains 2 matches, expected 1"
|
||||
if err == nil || err.Error() != expectedErr {
|
||||
t.Errorf("Expected '%s', got '%v'", expectedErr, err)
|
||||
}
|
||||
|
||||
os.Remove("schedules/6_1.csv")
|
||||
scheduleFile, _ = os.Create("schedules/6_1.csv")
|
||||
scheduleFile.WriteString("1,0,asdf,0,3,0,4,0,5,0,6,0\n")
|
||||
scheduleFile.Close()
|
||||
_, err = BuildRandomSchedule(teams, scheduleBlocks, "test")
|
||||
if err == nil {
|
||||
t.Errorf("Expected string parsing error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScheduleTeams(t *testing.T) {
|
||||
rand.Seed(0)
|
||||
|
||||
numTeams := 18
|
||||
teams := make([]Team, numTeams)
|
||||
for i := 0; i < numTeams; i++ {
|
||||
teams[i].Id = i + 101
|
||||
}
|
||||
scheduleBlocks := []ScheduleBlock{{time.Unix(0, 0).UTC(), 6, 60}}
|
||||
matches, err := BuildRandomSchedule(teams, scheduleBlocks, "test")
|
||||
if err != nil {
|
||||
t.Error("Error:", err)
|
||||
}
|
||||
assertMatchEqual(t, Match{1, "test", "1", time.Unix(0, 0).UTC(), 107, false, 102, false, 117, false, 115,
|
||||
false, 106, false, 116, false, "", time.Unix(0, 0).UTC()}, matches[0])
|
||||
assertMatchEqual(t, Match{2, "test", "2", time.Unix(60, 0).UTC(), 109, false, 113, false, 104, false, 108,
|
||||
false, 112, false, 118, false, "", time.Unix(0, 0).UTC()}, matches[1])
|
||||
assertMatchEqual(t, Match{3, "test", "3", time.Unix(120, 0).UTC(), 103, false, 111, false, 105, false, 114,
|
||||
false, 101, false, 110, false, "", time.Unix(0, 0).UTC()}, matches[2])
|
||||
assertMatchEqual(t, Match{4, "test", "4", time.Unix(180, 0).UTC(), 102, false, 104, false, 115, false, 117,
|
||||
false, 118, false, 113, false, "", time.Unix(0, 0).UTC()}, matches[3])
|
||||
assertMatchEqual(t, Match{5, "test", "5", time.Unix(240, 0).UTC(), 108, false, 114, false, 106, false, 103,
|
||||
false, 101, false, 116, false, "", time.Unix(0, 0).UTC()}, matches[4])
|
||||
assertMatchEqual(t, Match{6, "test", "6", time.Unix(300, 0).UTC(), 109, false, 112, false, 111, false, 110,
|
||||
false, 107, false, 105, false, "", time.Unix(0, 0).UTC()}, matches[5])
|
||||
}
|
||||
|
||||
func TestScheduleTiming(t *testing.T) {
|
||||
teams := make([]Team, 18)
|
||||
scheduleBlocks := []ScheduleBlock{{time.Unix(100, 0).UTC(), 10, 75},
|
||||
{time.Unix(20000, 0).UTC(), 5, 1000},
|
||||
{time.Unix(100000, 0).UTC(), 15, 29}}
|
||||
matches, err := BuildRandomSchedule(teams, scheduleBlocks, "test")
|
||||
if err != nil {
|
||||
t.Error("Error:", err)
|
||||
}
|
||||
assertMatchTime(t, time.Unix(100, 0).UTC(), matches[0])
|
||||
assertMatchTime(t, time.Unix(775, 0).UTC(), matches[9])
|
||||
assertMatchTime(t, time.Unix(20000, 0).UTC(), matches[10])
|
||||
assertMatchTime(t, time.Unix(24000, 0).UTC(), matches[14])
|
||||
assertMatchTime(t, time.Unix(100000, 0).UTC(), matches[15])
|
||||
assertMatchTime(t, time.Unix(100406, 0).UTC(), matches[29])
|
||||
}
|
||||
|
||||
func TestScheduleSurrogates(t *testing.T) {
|
||||
rand.Seed(0)
|
||||
|
||||
numTeams := 38
|
||||
teams := make([]Team, numTeams)
|
||||
for i := 0; i < numTeams; i++ {
|
||||
teams[i].Id = i + 101
|
||||
}
|
||||
scheduleBlocks := []ScheduleBlock{{time.Unix(0, 0).UTC(), 64, 60}}
|
||||
matches, _ := BuildRandomSchedule(teams, scheduleBlocks, "test")
|
||||
for i, match := range matches {
|
||||
if i == 13 || i == 14 {
|
||||
if !match.Red1IsSurrogate || match.Red2IsSurrogate || match.Red3IsSurrogate ||
|
||||
!match.Blue1IsSurrogate || match.Blue2IsSurrogate || match.Blue3IsSurrogate {
|
||||
t.Errorf("Surrogates wrong for match %d", i+1)
|
||||
}
|
||||
} else {
|
||||
if match.Red1IsSurrogate || match.Red2IsSurrogate || match.Red3IsSurrogate ||
|
||||
match.Blue1IsSurrogate || match.Blue2IsSurrogate || match.Blue3IsSurrogate {
|
||||
t.Errorf("Expected match %d to be free of surrogates", i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertMatchEqual(t *testing.T, expectedMatch Match, actualMatch Match) {
|
||||
if actualMatch != expectedMatch {
|
||||
t.Errorf("Expected '%v', got '%v'", expectedMatch, actualMatch)
|
||||
}
|
||||
}
|
||||
|
||||
func assertMatchTime(t *testing.T, expectedTime time.Time, match Match) {
|
||||
if match.Time != expectedTime {
|
||||
t.Errorf("Expected '%v', got '%v'", expectedTime, match)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user