From b1dfe5936915693f664cd44f4d245e3f59455548 Mon Sep 17 00:00:00 2001 From: Patrick Fairbank Date: Sun, 21 Aug 2022 11:10:51 -0700 Subject: [PATCH] Add a playoff alliances report. --- bracket/bracket.go | 27 +++++++++--- bracket/bracket_test.go | 22 ++++++++++ bracket/matchup.go | 16 +++---- templates/base.html | 3 +- templates/bracket.svg | 4 +- web/reports.go | 94 +++++++++++++++++++++++++++++++++++++++-- web/web.go | 15 ++++--- 7 files changed, 154 insertions(+), 27 deletions(-) diff --git a/bracket/bracket.go b/bracket/bracket.go index 7b7c241..b10d0b8 100644 --- a/bracket/bracket.go +++ b/bracket/bracket.go @@ -164,17 +164,17 @@ func createMatchupGraph( // Returns the winning alliance ID of the entire bracket, or 0 if it is not yet known. func (bracket *Bracket) Winner() int { - return bracket.FinalsMatchup.winner() + return bracket.FinalsMatchup.Winner() } // Returns the finalist alliance ID of the entire bracket, or 0 if it is not yet known. func (bracket *Bracket) Finalist() int { - return bracket.FinalsMatchup.loser() + return bracket.FinalsMatchup.Loser() } // Returns true if the bracket has been won, and false if it is still to be determined. func (bracket *Bracket) IsComplete() bool { - return bracket.FinalsMatchup.isComplete() + return bracket.FinalsMatchup.IsComplete() } // Returns a slice of all matchups contained within the bracket. @@ -230,12 +230,20 @@ func (bracket *Bracket) Update(database *model.Database, startTime *time.Time) e return nil } -// Prints out each matchup within the bracket in level order, backwards from finals to earlier rounds, for debugging. -func (bracket *Bracket) print() { +// Performs a traversal of the bracket in reverse order of rounds and invokes the given function for each visited +// matchup. +func (bracket *Bracket) ReverseRoundOrderTraversal(visitFunction func(*Matchup)) { matchupQueue := []*Matchup{bracket.FinalsMatchup} for len(matchupQueue) > 0 { + // Reorder the queue since graph depth doesn't necessarily equate to round. + sort.Slice(matchupQueue, func(i, j int) bool { + if matchupQueue[i].Round == matchupQueue[j].Round { + return matchupQueue[i].Group < matchupQueue[j].Group + } + return matchupQueue[i].Round > matchupQueue[j].Round + }) matchup := matchupQueue[0] - fmt.Printf("%+v\n\n", matchup) + visitFunction(matchup) matchupQueue = matchupQueue[1:] if matchup != nil { if matchup.redAllianceSourceMatchup != nil && matchup.redAllianceSource.useWinner { @@ -247,3 +255,10 @@ func (bracket *Bracket) print() { } } } + +// Prints out each matchup within the bracket in level order, backwards from finals to earlier rounds, for debugging. +func (bracket *Bracket) print() { + bracket.ReverseRoundOrderTraversal(func(matchup *Matchup) { + fmt.Printf("%+v\n\n", matchup) + }) +} diff --git a/bracket/bracket_test.go b/bracket/bracket_test.go index 7b0f909..9d202df 100644 --- a/bracket/bracket_test.go +++ b/bracket/bracket_test.go @@ -209,3 +209,25 @@ func TestBracketGetMatchup(t *testing.T) { } assert.Nil(t, matchup) } + +func TestBracketLevelOrderTraversal(t *testing.T) { + database := setupTestDb(t) + tournament.CreateTestAlliances(database, 8) + bracket, err := NewSingleEliminationBracket(8) + assert.Nil(t, err) + + var displayNames []string + bracket.ReverseRoundOrderTraversal(func(matchup *Matchup) { + displayNames = append(displayNames, matchup.displayName) + }) + assert.Equal(t, []string{"F", "SF1", "SF2", "QF1", "QF2", "QF3", "QF4"}, displayNames) + + bracket, err = NewDoubleEliminationBracket(8) + assert.Nil(t, err) + + displayNames = nil + bracket.ReverseRoundOrderTraversal(func(matchup *Matchup) { + displayNames = append(displayNames, matchup.displayName) + }) + assert.Equal(t, []string{"F", "13", "11", "12", "9", "10", "5", "6", "7", "8", "1", "2", "3", "4"}, displayNames) +} diff --git a/bracket/matchup.go b/bracket/matchup.go index 9de1776..e795de9 100644 --- a/bracket/matchup.go +++ b/bracket/matchup.go @@ -135,7 +135,7 @@ func (matchup *Matchup) StatusText() (string, string) { } // Returns the winning alliance ID of the matchup, or 0 if it is not yet known. -func (matchup *Matchup) winner() int { +func (matchup *Matchup) Winner() int { if matchup.RedAllianceWins >= matchup.NumWinsToAdvance { return matchup.RedAllianceId } @@ -146,7 +146,7 @@ func (matchup *Matchup) winner() int { } // Returns the losing alliance ID of the matchup, or 0 if it is not yet known. -func (matchup *Matchup) loser() int { +func (matchup *Matchup) Loser() int { if matchup.RedAllianceWins >= matchup.NumWinsToAdvance { return matchup.BlueAllianceId } @@ -157,8 +157,8 @@ func (matchup *Matchup) loser() int { } // Returns true if the matchup has been won, and false if it is still to be determined. -func (matchup *Matchup) isComplete() bool { - return matchup.winner() > 0 +func (matchup *Matchup) IsComplete() bool { + return matchup.Winner() > 0 } // Returns true if the matchup represents the final matchup in the bracket. @@ -184,16 +184,16 @@ func (matchup *Matchup) update(database *model.Database) error { // Populate the alliance IDs from the lower matchups (or with a zero value if they are not yet complete). if matchup.redAllianceSourceMatchup != nil { if matchup.redAllianceSource.useWinner { - matchup.RedAllianceId = matchup.redAllianceSourceMatchup.winner() + matchup.RedAllianceId = matchup.redAllianceSourceMatchup.Winner() } else { - matchup.RedAllianceId = matchup.redAllianceSourceMatchup.loser() + matchup.RedAllianceId = matchup.redAllianceSourceMatchup.Loser() } } if matchup.blueAllianceSourceMatchup != nil { if matchup.blueAllianceSource.useWinner { - matchup.BlueAllianceId = matchup.blueAllianceSourceMatchup.winner() + matchup.BlueAllianceId = matchup.blueAllianceSourceMatchup.Winner() } else { - matchup.BlueAllianceId = matchup.blueAllianceSourceMatchup.loser() + matchup.BlueAllianceId = matchup.blueAllianceSourceMatchup.Loser() } } diff --git a/templates/base.html b/templates/base.html index aba8da0..24301f3 100755 --- a/templates/base.html +++ b/templates/base.html @@ -56,7 +56,8 @@
  • Qualification Schedule
  • Playoff Schedule
  • Standings
  • -
  • Bracket
  • +
  • Playoff Alliances
  • +
  • Playoff Bracket
  • Backup Teams
  • Playoff Alliance Coupons
  • Team Connection Status
  • diff --git a/templates/bracket.svg b/templates/bracket.svg index 9e7104f..b73a619 100644 --- a/templates/bracket.svg +++ b/templates/bracket.svg @@ -11,7 +11,7 @@ @font-face { font-family: 'FuturaLT-Bold'; src: url("data:@file/vnd.ms-opentype;base64,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");} - + #background rect { @@ -199,7 +199,7 @@ .bracket_double #match_5_1 {transform: translate(1302px, 567px);} .bracket_double #match_6_1 {transform: translate(1598px, 417px);} - + .bracket_16 #match_1_1 {transform: translate(94px, 158px);} .bracket_16 #match_1_2 {transform: translate(94px, 348px);} diff --git a/web/reports.go b/web/reports.go index e14c29e..8e25665 100755 --- a/web/reports.go +++ b/web/reports.go @@ -9,7 +9,9 @@ import ( "bytes" "errors" "fmt" + "github.com/Team254/cheesy-arena-lite/bracket" "github.com/Team254/cheesy-arena-lite/game" + "github.com/Team254/cheesy-arena-lite/model" "github.com/Team254/cheesy-arena-lite/tournament" "github.com/gorilla/mux" "github.com/jung-kurt/gofpdf" @@ -101,19 +103,19 @@ func (web *Web) rankingsPdfReportHandler(w http.ResponseWriter, r *http.Request) func (web *Web) findBackupTeams(rankings game.Rankings) (game.Rankings, map[int]bool, error) { var pruned game.Rankings - picks, err := web.arena.Database.GetAllAlliances() + alliances, err := web.arena.Database.GetAllAlliances() if err != nil { return nil, nil, err } - if len(picks) == 0 { + if len(alliances) == 0 { return nil, nil, errors.New("backup teams report is unavailable until alliances have been selected") } pickedTeams := make(map[int]bool) pickedBackups := make(map[int]bool) - for _, alliance := range picks { + for _, alliance := range alliances { for i, allianceTeamId := range alliance.TeamIds { // Teams in third in an alliance are backups at events that use 3 team alliances. if i == 3 { @@ -589,6 +591,92 @@ func (web *Web) wpaKeysCsvReportHandler(w http.ResponseWriter, r *http.Request) } } +// Generates a PDF-formatted report of the playoff alliances and the teams contained within. +func (web *Web) alliancesPdfReportHandler(w http.ResponseWriter, r *http.Request) { + alliances, err := web.arena.Database.GetAllAlliances() + if err != nil { + handleWebErr(w, err) + return + } + + // Traverse the bracket to register the furthest level that the alliance has achieved. + allianceStatuses := make(map[int]string) + if web.arena.PlayoffBracket.IsComplete() { + allianceStatuses[web.arena.PlayoffBracket.Winner()] = "Winner\n " + allianceStatuses[web.arena.PlayoffBracket.Finalist()] = "Finalist\n " + } + web.arena.PlayoffBracket.ReverseRoundOrderTraversal(func(matchup *bracket.Matchup) { + if matchup.IsComplete() { + if _, ok := allianceStatuses[matchup.Loser()]; !ok { + allianceStatuses[matchup.Loser()] = fmt.Sprintf("Eliminated in\n%s", matchup.LongDisplayName()) + } + } else { + if matchup.RedAllianceId > 0 { + allianceStatuses[matchup.RedAllianceId] = fmt.Sprintf("Playing in\n%s", matchup.LongDisplayName()) + } + if matchup.BlueAllianceId > 0 { + allianceStatuses[matchup.BlueAllianceId] = fmt.Sprintf("Playing in\n%s", matchup.LongDisplayName()) + } + } + }) + + teams, err := web.arena.Database.GetAllTeams() + if err != nil { + handleWebErr(w, err) + return + } + teamsMap := make(map[int]model.Team, len(teams)) + for _, team := range teams { + teamsMap[team.Id] = team + } + + // The widths of the table columns in mm, stored here so that they can be referenced for each row. + colWidths := map[string]float64{"Alliance": 23, "Id": 12, "Name": 80, "Location": 80} + rowHeight := 6.5 + + pdf := gofpdf.New("P", "mm", "Letter", "font") + pdf.AddPage() + pdf.SetFont("Arial", "B", 10) + pdf.SetFillColor(220, 220, 220) + + // Render table header row. + pdf.CellFormat(195, rowHeight, "Playoff Alliances - "+web.arena.EventSettings.Name, "", 1, "C", false, 0, "") + pdf.CellFormat(colWidths["Alliance"], rowHeight, "Alliance", "1", 0, "C", true, 0, "") + pdf.CellFormat(colWidths["Id"], rowHeight, "Team", "1", 0, "C", true, 0, "") + pdf.CellFormat(colWidths["Name"], rowHeight, "Name", "1", 0, "C", true, 0, "") + pdf.CellFormat(colWidths["Location"], rowHeight, "Location", "1", 1, "C", true, 0, "") + pdf.SetFont("Arial", "", 10) + xStart := pdf.GetX() + for _, alliance := range alliances { + yStart := pdf.GetY() + pdf.MultiCell( + colWidths["Alliance"], + rowHeight*float64(len(alliance.TeamIds))/5, + fmt.Sprintf(" \n%d\n%s\n ", alliance.Id, allianceStatuses[alliance.Id]), + "1", + "C", + false, + ) + pdf.SetY(yStart) + for _, teamId := range alliance.TeamIds { + pdf.SetX(xStart + colWidths["Alliance"]) + team := teamsMap[teamId] + pdf.CellFormat(colWidths["Id"], rowHeight, strconv.Itoa(team.Id), "1", 0, "L", false, 0, "") + pdf.CellFormat(colWidths["Name"], rowHeight, team.Nickname, "1", 0, "L", false, 0, "") + location := fmt.Sprintf("%s, %s, %s", team.City, team.StateProv, team.Country) + pdf.CellFormat(colWidths["Location"], rowHeight, location, "1", 1, "L", 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 PDF-formatted report of the playoff bracket, relying on the browser to convert SVG to PDF (since no // suitable Go library for doing so appears to exist). func (web *Web) bracketPdfReportHandler(w http.ResponseWriter, r *http.Request) { diff --git a/web/web.go b/web/web.go index 4fb6252..f19b906 100755 --- a/web/web.go +++ b/web/web.go @@ -149,17 +149,18 @@ func (web *Web) newHandler() http.Handler { router.HandleFunc("/match_review", web.matchReviewHandler).Methods("GET") router.HandleFunc("/match_review/{matchId}/edit", web.matchReviewEditGetHandler).Methods("GET") router.HandleFunc("/match_review/{matchId}/edit", web.matchReviewEditPostHandler).Methods("POST") - router.HandleFunc("/reports/csv/rankings", web.rankingsCsvReportHandler).Methods("GET") - router.HandleFunc("/reports/pdf/rankings", web.rankingsPdfReportHandler).Methods("GET") - router.HandleFunc("/reports/pdf/bracket", web.bracketPdfReportHandler).Methods("GET") router.HandleFunc("/reports/csv/backups", web.backupTeamsCsvReportHandler).Methods("GET") - router.HandleFunc("/reports/pdf/backups", web.backupsPdfReportHandler).Methods("GET") - router.HandleFunc("/reports/pdf/coupons", web.couponsPdfReportHandler).Methods("GET") + router.HandleFunc("/reports/csv/rankings", web.rankingsCsvReportHandler).Methods("GET") router.HandleFunc("/reports/csv/schedule/{type}", web.scheduleCsvReportHandler).Methods("GET") - router.HandleFunc("/reports/pdf/schedule/{type}", web.schedulePdfReportHandler).Methods("GET") router.HandleFunc("/reports/csv/teams", web.teamsCsvReportHandler).Methods("GET") - router.HandleFunc("/reports/pdf/teams", web.teamsPdfReportHandler).Methods("GET") router.HandleFunc("/reports/csv/wpa_keys", web.wpaKeysCsvReportHandler).Methods("GET") + router.HandleFunc("/reports/pdf/alliances", web.alliancesPdfReportHandler).Methods("GET") + router.HandleFunc("/reports/pdf/backups", web.backupsPdfReportHandler).Methods("GET") + router.HandleFunc("/reports/pdf/bracket", web.bracketPdfReportHandler).Methods("GET") + router.HandleFunc("/reports/pdf/coupons", web.couponsPdfReportHandler).Methods("GET") + router.HandleFunc("/reports/pdf/rankings", web.rankingsPdfReportHandler).Methods("GET") + router.HandleFunc("/reports/pdf/schedule/{type}", web.schedulePdfReportHandler).Methods("GET") + router.HandleFunc("/reports/pdf/teams", web.teamsPdfReportHandler).Methods("GET") router.HandleFunc("/setup/awards", web.awardsGetHandler).Methods("GET") router.HandleFunc("/setup/awards", web.awardsPostHandler).Methods("POST") router.HandleFunc("/setup/awards/publish", web.awardsPublishHandler).Methods("POST")