Add defense randomization and schedule report.

This commit is contained in:
Patrick Fairbank
2016-08-13 16:31:36 -07:00
parent f985107c1c
commit e0c5acb8f0
8 changed files with 120 additions and 11 deletions

View File

@@ -45,6 +45,8 @@ type Match struct {
BlueDefense5 string
}
var placeableDefenses = []string{"CDF", "M", "R", "RW", "RT"}
func (database *Database) CreateMatch(match *Match) error {
return database.matchMap.Insert(match)
}

View File

@@ -235,6 +235,68 @@ func SchedulePdfReportHandler(w http.ResponseWriter, r *http.Request) {
}
}
// Generates a PDF-formatted report of the defenses schedule.
func DefensesPdfReportHandler(w http.ResponseWriter, r *http.Request) {
if !UserIsReader(w, r) {
return
}
vars := mux.Vars(r)
matches, err := db.GetMatchesByType(vars["type"])
if err != nil {
handleWebErr(w, err)
return
}
// The widths of the table columns in mm, stored here so that they can be referenced for each row.
colWidths := map[string]float64{"Type": 25, "Match": 15, "Defense": 15.5}
rowHeight := 6.5
pdf := gofpdf.New("P", "mm", "Letter", "font")
pdf.AddPage()
// Render table header row.
pdf.SetFont("Arial", "B", 10)
pdf.SetFillColor(220, 220, 220)
pdf.CellFormat(195, rowHeight, "Defenses Schedule - "+eventSettings.Name, "", 1, "C", false, 0, "")
pdf.CellFormat(colWidths["Type"], rowHeight, "Type", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Match"], rowHeight, "Match", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Red 1", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Red 2", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Red 3", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Red 4", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Red 5", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Blue 1", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Blue 2", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Blue 3", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Blue 4", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, "Blue 5", "1", 1, "C", true, 0, "")
pdf.SetFont("Arial", "", 10)
for _, match := range matches {
// Render match defenses row.
pdf.CellFormat(colWidths["Type"], rowHeight, match.CapitalizedType(), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Match"], rowHeight, match.DisplayName, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.RedDefense1, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.RedDefense2, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.RedDefense3, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.RedDefense4, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.RedDefense5, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.BlueDefense1, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.BlueDefense2, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.BlueDefense3, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.BlueDefense4, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Defense"], rowHeight, match.BlueDefense5, "1", 1, "C", false, 0, "")
}
// Write out the PDF file as the HTTP response.
w.Header().Set("Content-Type", "application/pdf")
err = pdf.Output(w)
if err != nil {
handleWebErr(w, err)
return
}
}
// Generates a CSV-formatted report of the team list.
func TeamsCsvReportHandler(w http.ResponseWriter, r *http.Request) {
if !UserIsReader(w, r) {

View File

@@ -60,9 +60,11 @@ func TestScheduleCsvReport(t *testing.T) {
assert.Equal(t, 200, recorder.Code)
assert.Equal(t, "text/plain", recorder.HeaderMap["Content-Type"][0])
expectedBody := "Match,Type,Time,Red1,Red1IsSurrogate,Red2,Red2IsSurrogate,Red3,Red3IsSurrogate,Blue1," +
"Blue1IsSurrogate,Blue2,Blue2IsSurrogate,Blue3,Blue3IsSurrogate\n1,qualification," +
"1969-12-31 16:00:00 -0800 PST,1,false,2,false,3,false,4,true,5,true,6,true\n" +
"2,qualification,1969-12-31 16:10:00 -0800 PST,7,true,8,true,9,true,10,false,11,false,12,false\n\n"
"Blue1IsSurrogate,Blue2,Blue2IsSurrogate,Blue3,Blue3IsSurrogate,RedDefense1,RedDefense2," +
"RedDefense3,RedDefense4,RedDefense5,BlueDefense1,BlueDefense2,BlueDefense3,BlueDefense4," +
"BlueDefense5\n1,qualification,1969-12-31 16:00:00 -0800 PST,1,false,2,false,3,false,4,true,5,true," +
"6,true,,,,,,,,,,\n2,qualification,1969-12-31 16:10:00 -0800 PST,7,true,8,true,9,true,10,false,11,false,12," +
"false,,,,,,,,,,\n\n"
assert.Equal(t, expectedBody, recorder.Body.String())
}

View File

@@ -88,6 +88,8 @@ func BuildRandomSchedule(teams []Team, scheduleBlocks []ScheduleBlock, matchType
}
}
randomizeDefenses(matches, numTeams)
return matches, nil
}
@@ -99,3 +101,28 @@ func countMatches(scheduleBlocks []ScheduleBlock) int {
}
return numMatches
}
// Fills in a random set of defenses per round of all teams playing.
func randomizeDefenses(matches []Match, numTeams int) {
// Take the floor, to err on the side of a team missing a set of defenses instead of seeing it twice.
matchesPerRound := numTeams / 6
var defenseShuffle []int
for i := 0; i < len(matches); i++ {
if i%matchesPerRound == 0 {
// Pick a new set of defenses.
defenseShuffle = rand.Perm(len(placeableDefenses))
}
matches[i].RedDefense1 = "LB"
matches[i].RedDefense2 = placeableDefenses[defenseShuffle[0]]
matches[i].RedDefense3 = placeableDefenses[defenseShuffle[1]]
matches[i].RedDefense4 = placeableDefenses[defenseShuffle[2]]
matches[i].RedDefense5 = placeableDefenses[defenseShuffle[3]]
matches[i].BlueDefense1 = "LB"
matches[i].BlueDefense2 = placeableDefenses[defenseShuffle[0]]
matches[i].BlueDefense3 = placeableDefenses[defenseShuffle[1]]
matches[i].BlueDefense4 = placeableDefenses[defenseShuffle[2]]
matches[i].BlueDefense5 = placeableDefenses[defenseShuffle[3]]
}
}

View File

@@ -56,17 +56,29 @@ func TestScheduleTeams(t *testing.T) {
matches, err := BuildRandomSchedule(teams, scheduleBlocks, "test")
assert.Nil(t, err)
assert.Equal(t, Match{Type: "test", DisplayName: "1", Time: time.Unix(0, 0).UTC(), Red1: 115, Red2: 111,
Red3: 108, Blue1: 109, Blue2: 116, Blue3: 117}, matches[0])
Red3: 108, Blue1: 109, Blue2: 116, Blue3: 117, RedDefense1: "LB", RedDefense2: "RW", RedDefense3: "RT",
RedDefense4: "R", RedDefense5: "M", BlueDefense1: "LB", BlueDefense2: "RW", BlueDefense3: "RT",
BlueDefense4: "R", BlueDefense5: "M"}, matches[0])
assert.Equal(t, Match{Type: "test", DisplayName: "2", Time: time.Unix(60, 0).UTC(), Red1: 114, Red2: 112,
Red3: 103, Blue1: 101, Blue2: 104, Blue3: 118}, matches[1])
Red3: 103, Blue1: 101, Blue2: 104, Blue3: 118, RedDefense1: "LB", RedDefense2: "RW", RedDefense3: "RT",
RedDefense4: "R", RedDefense5: "M", BlueDefense1: "LB", BlueDefense2: "RW", BlueDefense3: "RT",
BlueDefense4: "R", BlueDefense5: "M"}, matches[1])
assert.Equal(t, Match{Type: "test", DisplayName: "3", Time: time.Unix(120, 0).UTC(), Red1: 110, Red2: 107,
Red3: 105, Blue1: 106, Blue2: 113, Blue3: 102}, matches[2])
Red3: 105, Blue1: 106, Blue2: 113, Blue3: 102, RedDefense1: "LB", RedDefense2: "RW", RedDefense3: "RT",
RedDefense4: "R", RedDefense5: "M", BlueDefense1: "LB", BlueDefense2: "RW", BlueDefense3: "RT",
BlueDefense4: "R", BlueDefense5: "M"}, matches[2])
assert.Equal(t, Match{Type: "test", DisplayName: "4", Time: time.Unix(180, 0).UTC(), Red1: 112, Red2: 108,
Red3: 109, Blue1: 101, Blue2: 111, Blue3: 103}, matches[3])
Red3: 109, Blue1: 101, Blue2: 111, Blue3: 103, RedDefense1: "LB", RedDefense2: "RT", RedDefense3: "M",
RedDefense4: "CDF", RedDefense5: "R", BlueDefense1: "LB", BlueDefense2: "RT", BlueDefense3: "M",
BlueDefense4: "CDF", BlueDefense5: "R"}, matches[3])
assert.Equal(t, Match{Type: "test", DisplayName: "5", Time: time.Unix(240, 0).UTC(), Red1: 113, Red2: 117,
Red3: 115, Blue1: 110, Blue2: 114, Blue3: 102}, matches[4])
Red3: 115, Blue1: 110, Blue2: 114, Blue3: 102, RedDefense1: "LB", RedDefense2: "RT", RedDefense3: "M",
RedDefense4: "CDF", RedDefense5: "R", BlueDefense1: "LB", BlueDefense2: "RT", BlueDefense3: "M",
BlueDefense4: "CDF", BlueDefense5: "R"}, matches[4])
assert.Equal(t, Match{Type: "test", DisplayName: "6", Time: time.Unix(300, 0).UTC(), Red1: 118, Red2: 105,
Red3: 106, Blue1: 107, Blue2: 104, Blue3: 116}, matches[5])
Red3: 106, Blue1: 107, Blue2: 104, Blue3: 116, RedDefense1: "LB", RedDefense2: "RT", RedDefense3: "M",
RedDefense4: "CDF", RedDefense5: "R", BlueDefense1: "LB", BlueDefense2: "RT", BlueDefense3: "M",
BlueDefense4: "CDF", BlueDefense5: "R"}, matches[5])
// Check with excess room for matches in the schedule.
scheduleBlocks = []ScheduleBlock{{time.Unix(0, 0).UTC(), 7, 60}}

View File

@@ -60,8 +60,11 @@
<li class="dropdown-header">PDF Reports</li>
<li><a target="_blank" href="/reports/pdf/teams">Team List</a></li>
<li><a target="_blank" href="/reports/pdf/schedule/practice">Practice Schedule</a></li>
<li><a target="_blank" href="/reports/pdf/defenses/practice">Practice Defenses</a></li>
<li><a target="_blank" href="/reports/pdf/schedule/qualification">Qualification Schedule</a></li>
<li><a target="_blank" href="/reports/pdf/defenses/qualification">Qualification Defenses</a></li>
<li><a target="_blank" href="/reports/pdf/schedule/elimination">Playoff Schedule</a></li>
<li><a target="_blank" href="/reports/pdf/defenses/elimination">Playoff Defenses</a></li>
<li><a target="_blank" href="/reports/pdf/rankings">Standings</a></li>
<li class="divider"></li>
<li class="dropdown-header">CSV Data Export</li>

View File

@@ -1,3 +1,3 @@
Match,Type,Time,Red1,Red1IsSurrogate,Red2,Red2IsSurrogate,Red3,Red3IsSurrogate,Blue1,Blue1IsSurrogate,Blue2,Blue2IsSurrogate,Blue3,Blue3IsSurrogate
{{range $match := .}}{{$match.DisplayName}},{{$match.Type}},{{$match.Time.Local}},{{$match.Red1}},{{$match.Red1IsSurrogate}},{{$match.Red2}},{{$match.Red2IsSurrogate}},{{$match.Red3}},{{$match.Red3IsSurrogate}},{{$match.Blue1}},{{$match.Blue1IsSurrogate}},{{$match.Blue2}},{{$match.Blue2IsSurrogate}},{{$match.Blue3}},{{$match.Blue3IsSurrogate}}
Match,Type,Time,Red1,Red1IsSurrogate,Red2,Red2IsSurrogate,Red3,Red3IsSurrogate,Blue1,Blue1IsSurrogate,Blue2,Blue2IsSurrogate,Blue3,Blue3IsSurrogate,RedDefense1,RedDefense2,RedDefense3,RedDefense4,RedDefense5,BlueDefense1,BlueDefense2,BlueDefense3,BlueDefense4,BlueDefense5
{{range $match := .}}{{$match.DisplayName}},{{$match.Type}},{{$match.Time.Local}},{{$match.Red1}},{{$match.Red1IsSurrogate}},{{$match.Red2}},{{$match.Red2IsSurrogate}},{{$match.Red3}},{{$match.Red3IsSurrogate}},{{$match.Blue1}},{{$match.Blue1IsSurrogate}},{{$match.Blue2}},{{$match.Blue2IsSurrogate}},{{$match.Blue3}},{{$match.Blue3IsSurrogate}},{{$match.RedDefense1}},{{$match.RedDefense2}},{{$match.RedDefense3}},{{$match.RedDefense4}},{{$match.RedDefense5}},{{$match.BlueDefense1}},{{$match.BlueDefense2}},{{$match.BlueDefense3}},{{$match.BlueDefense4}},{{$match.BlueDefense5}}
{{end}}
1 Match,Type,Time,Red1,Red1IsSurrogate,Red2,Red2IsSurrogate,Red3,Red3IsSurrogate,Blue1,Blue1IsSurrogate,Blue2,Blue2IsSurrogate,Blue3,Blue3IsSurrogate Match,Type,Time,Red1,Red1IsSurrogate,Red2,Red2IsSurrogate,Red3,Red3IsSurrogate,Blue1,Blue1IsSurrogate,Blue2,Blue2IsSurrogate,Blue3,Blue3IsSurrogate,RedDefense1,RedDefense2,RedDefense3,RedDefense4,RedDefense5,BlueDefense1,BlueDefense2,BlueDefense3,BlueDefense4,BlueDefense5
2 {{range $match := .}}{{$match.DisplayName}},{{$match.Type}},{{$match.Time.Local}},{{$match.Red1}},{{$match.Red1IsSurrogate}},{{$match.Red2}},{{$match.Red2IsSurrogate}},{{$match.Red3}},{{$match.Red3IsSurrogate}},{{$match.Blue1}},{{$match.Blue1IsSurrogate}},{{$match.Blue2}},{{$match.Blue2IsSurrogate}},{{$match.Blue3}},{{$match.Blue3IsSurrogate}} {{range $match := .}}{{$match.DisplayName}},{{$match.Type}},{{$match.Time.Local}},{{$match.Red1}},{{$match.Red1IsSurrogate}},{{$match.Red2}},{{$match.Red2IsSurrogate}},{{$match.Red3}},{{$match.Red3IsSurrogate}},{{$match.Blue1}},{{$match.Blue1IsSurrogate}},{{$match.Blue2}},{{$match.Blue2IsSurrogate}},{{$match.Blue3}},{{$match.Blue3IsSurrogate}},{{$match.RedDefense1}},{{$match.RedDefense2}},{{$match.RedDefense3}},{{$match.RedDefense4}},{{$match.RedDefense5}},{{$match.BlueDefense1}},{{$match.BlueDefense2}},{{$match.BlueDefense3}},{{$match.BlueDefense4}},{{$match.BlueDefense5}}
3 {{end}} {{end}}

1
web.go
View File

@@ -193,6 +193,7 @@ func newHandler() http.Handler {
router.HandleFunc("/reports/pdf/rankings", RankingsPdfReportHandler).Methods("GET")
router.HandleFunc("/reports/csv/schedule/{type}", ScheduleCsvReportHandler).Methods("GET")
router.HandleFunc("/reports/pdf/schedule/{type}", SchedulePdfReportHandler).Methods("GET")
router.HandleFunc("/reports/pdf/defenses/{type}", DefensesPdfReportHandler).Methods("GET")
router.HandleFunc("/reports/csv/teams", TeamsCsvReportHandler).Methods("GET")
router.HandleFunc("/reports/pdf/teams", TeamsPdfReportHandler).Methods("GET")
router.HandleFunc("/reports/csv/wpa_keys", WpaKeysCsvReportHandler).Methods("GET")