From 2a228a4725d19be74ba961e9c9184d8a6f9c9a20 Mon Sep 17 00:00:00 2001 From: Patrick Fairbank Date: Wed, 17 Aug 2022 18:36:21 -0700 Subject: [PATCH] Refactor playoff series status text on audience display to use matchup data. --- bracket/bracket.go | 29 +++++++++++++++-------- bracket/bracket_test.go | 33 +++++++++++++++++++++++---- bracket/double_elimination.go | 28 +++++++++++------------ bracket/matchup.go | 36 ++++++++++++++--------------- bracket/single_elimination.go | 30 ++++++++++++------------ field/arena_notifiers.go | 43 +++++++++++++++++------------------ 6 files changed, 115 insertions(+), 84 deletions(-) diff --git a/bracket/bracket.go b/bracket/bracket.go index 4dec370..528377f 100644 --- a/bracket/bracket.go +++ b/bracket/bracket.go @@ -13,6 +13,7 @@ import ( type Bracket struct { FinalsMatchup *Matchup + matchupMap map[matchupKey]*Matchup } const ElimMatchSpacingSec = 600 @@ -26,14 +27,13 @@ func newBracket(matchupTemplates []matchupTemplate, finalsMatchupKey matchupKey, } // Recursively build the bracket, starting with the finals matchup. - finalsMatchup, _, err := createMatchupGraph( - finalsMatchupKey, true, matchupTemplateMap, numAlliances, make(map[matchupKey]*Matchup), - ) + matchupMap := make(map[matchupKey]*Matchup) + finalsMatchup, _, err := createMatchupGraph(finalsMatchupKey, true, matchupTemplateMap, numAlliances, matchupMap) if err != nil { return nil, err } - return &Bracket{FinalsMatchup: finalsMatchup}, nil + return &Bracket{FinalsMatchup: finalsMatchup, matchupMap: matchupMap}, nil } // Recursive helper method to create the current matchup node and all of its children. @@ -153,8 +153,8 @@ func createMatchupGraph( matchupTemplate: matchupTemplate, RedAllianceId: redByeAllianceId, BlueAllianceId: blueByeAllianceId, - RedAllianceSourceMatchup: redAllianceSourceMatchup, - BlueAllianceSourceMatchup: blueAllianceSourceMatchup, + redAllianceSourceMatchup: redAllianceSourceMatchup, + blueAllianceSourceMatchup: blueAllianceSourceMatchup, } matchupMap[matchupKey] = matchup } @@ -176,6 +176,15 @@ func (bracket *Bracket) IsComplete() bool { return bracket.FinalsMatchup.isComplete() } +// Returns the matchup for the given round and group, or an error if it doesn't exist within the bracket. +func (bracket *Bracket) GetMatchup(round, group int) (*Matchup, error) { + matchupKey := newMatchupKey(round, group) + if matchup, ok := bracket.matchupMap[matchupKey]; ok { + return matchup, nil + } + return nil, fmt.Errorf("bracket does not contain matchup for key %+v", matchupKey) +} + // Traverses the bracket to update the state of each matchup based on match results, counting wins and creating or // deleting matches as required. func (bracket *Bracket) Update(database *model.Database, startTime *time.Time) error { @@ -213,11 +222,11 @@ func (bracket *Bracket) print() { fmt.Printf("%+v\n\n", matchup) matchupQueue = matchupQueue[1:] if matchup != nil { - if matchup.RedAllianceSourceMatchup != nil && matchup.redAllianceSource.useWinner { - matchupQueue = append(matchupQueue, matchup.RedAllianceSourceMatchup) + if matchup.redAllianceSourceMatchup != nil && matchup.redAllianceSource.useWinner { + matchupQueue = append(matchupQueue, matchup.redAllianceSourceMatchup) } - if matchup.BlueAllianceSourceMatchup != nil && matchup.blueAllianceSource.useWinner { - matchupQueue = append(matchupQueue, matchup.BlueAllianceSourceMatchup) + if matchup.blueAllianceSourceMatchup != nil && matchup.blueAllianceSource.useWinner { + matchupQueue = append(matchupQueue, matchup.blueAllianceSourceMatchup) } } } diff --git a/bracket/bracket_test.go b/bracket/bracket_test.go index e35f99e..31a3fe7 100644 --- a/bracket/bracket_test.go +++ b/bracket/bracket_test.go @@ -34,35 +34,35 @@ func TestNewBracketInverseSeeding(t *testing.T) { { matchupKey: newMatchupKey(1, 1), displayNameFormat: "QF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 8}, blueAllianceSource: allianceSource{allianceId: 1}, }, { matchupKey: newMatchupKey(1, 2), displayNameFormat: "QF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 5}, blueAllianceSource: allianceSource{allianceId: 4}, }, { matchupKey: newMatchupKey(2, 1), displayNameFormat: "SF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(1, 2), blueAllianceSource: newWinnerAllianceSource(1, 1), }, { matchupKey: newMatchupKey(2, 2), displayNameFormat: "SF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 3}, blueAllianceSource: allianceSource{allianceId: 2}, }, { matchupKey: newMatchupKey(3, 1), displayNameFormat: "F-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(2, 1), blueAllianceSource: newWinnerAllianceSource(2, 2), }, @@ -169,3 +169,26 @@ func TestBracketUpdateTeamPositions(t *testing.T) { } } } + +func TestBracketGetMatchup(t *testing.T) { + database := setupTestDb(t) + + tournament.CreateTestAlliances(database, 4) + bracket, err := NewSingleEliminationBracket(4) + assert.Nil(t, err) + assert.Nil(t, bracket.Update(database, &dummyStartTime)) + + matchup, err := bracket.GetMatchup(4, 1) + assert.Nil(t, err) + assert.Equal(t, newMatchupKey(4, 1), matchup.matchupKey) + + matchup, err = bracket.GetMatchup(3, 2) + assert.Nil(t, err) + assert.Equal(t, newMatchupKey(3, 2), matchup.matchupKey) + + matchup, err = bracket.GetMatchup(2, 1) + if assert.NotNil(t, err) { + assert.Equal(t, "bracket does not contain matchup for key {round:2 group:1}", err.Error()) + } + assert.Nil(t, matchup) +} diff --git a/bracket/double_elimination.go b/bracket/double_elimination.go index aaf546c..04c2a5c 100644 --- a/bracket/double_elimination.go +++ b/bracket/double_elimination.go @@ -19,98 +19,98 @@ var doubleEliminationBracketMatchupTemplates = []matchupTemplate{ { matchupKey: newMatchupKey(1, 1), displayNameFormat: "1", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: allianceSource{allianceId: 1}, blueAllianceSource: allianceSource{allianceId: 8}, }, { matchupKey: newMatchupKey(1, 2), displayNameFormat: "2", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: allianceSource{allianceId: 4}, blueAllianceSource: allianceSource{allianceId: 5}, }, { matchupKey: newMatchupKey(1, 3), displayNameFormat: "3", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: allianceSource{allianceId: 3}, blueAllianceSource: allianceSource{allianceId: 6}, }, { matchupKey: newMatchupKey(1, 4), displayNameFormat: "4", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: allianceSource{allianceId: 2}, blueAllianceSource: allianceSource{allianceId: 7}, }, { matchupKey: newMatchupKey(2, 1), displayNameFormat: "5", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: newLoserAllianceSource(1, 1), blueAllianceSource: newLoserAllianceSource(1, 2), }, { matchupKey: newMatchupKey(2, 2), displayNameFormat: "6", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: newLoserAllianceSource(1, 3), blueAllianceSource: newLoserAllianceSource(1, 4), }, { matchupKey: newMatchupKey(2, 3), displayNameFormat: "7", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: newWinnerAllianceSource(1, 1), blueAllianceSource: newWinnerAllianceSource(1, 2), }, { matchupKey: newMatchupKey(2, 4), displayNameFormat: "8", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: newWinnerAllianceSource(1, 3), blueAllianceSource: newWinnerAllianceSource(1, 4), }, { matchupKey: newMatchupKey(3, 1), displayNameFormat: "9", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: newLoserAllianceSource(2, 3), blueAllianceSource: newWinnerAllianceSource(2, 2), }, { matchupKey: newMatchupKey(3, 2), displayNameFormat: "10", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: newLoserAllianceSource(2, 4), blueAllianceSource: newWinnerAllianceSource(2, 1), }, { matchupKey: newMatchupKey(4, 1), displayNameFormat: "11", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: newWinnerAllianceSource(3, 1), blueAllianceSource: newWinnerAllianceSource(3, 2), }, { matchupKey: newMatchupKey(4, 2), displayNameFormat: "12", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: newWinnerAllianceSource(2, 3), blueAllianceSource: newWinnerAllianceSource(2, 4), }, { matchupKey: newMatchupKey(5, 1), displayNameFormat: "13", - numWinsToAdvance: 1, + NumWinsToAdvance: 1, redAllianceSource: newLoserAllianceSource(4, 2), blueAllianceSource: newWinnerAllianceSource(4, 1), }, { matchupKey: newMatchupKey(6, 1), displayNameFormat: "F-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(4, 2), blueAllianceSource: newWinnerAllianceSource(5, 1), }, diff --git a/bracket/matchup.go b/bracket/matchup.go index 961f03a..672cb29 100644 --- a/bracket/matchup.go +++ b/bracket/matchup.go @@ -34,7 +34,7 @@ type matchupKey struct { type matchupTemplate struct { matchupKey displayNameFormat string - numWinsToAdvance int + NumWinsToAdvance int redAllianceSource allianceSource blueAllianceSource allianceSource } @@ -43,8 +43,8 @@ type matchupTemplate struct { // in a playoff tournament. type Matchup struct { matchupTemplate - RedAllianceSourceMatchup *Matchup - BlueAllianceSourceMatchup *Matchup + redAllianceSourceMatchup *Matchup + blueAllianceSourceMatchup *Matchup RedAllianceId int BlueAllianceId int RedAllianceWins int @@ -82,10 +82,10 @@ func (matchupTemplate *matchupTemplate) displayName(instance int) string { // Returns the winning alliance ID of the matchup, or 0 if it is not yet known. func (matchup *Matchup) winner() int { - if matchup.RedAllianceWins >= matchup.numWinsToAdvance { + if matchup.RedAllianceWins >= matchup.NumWinsToAdvance { return matchup.RedAllianceId } - if matchup.BlueAllianceWins >= matchup.numWinsToAdvance { + if matchup.BlueAllianceWins >= matchup.NumWinsToAdvance { return matchup.BlueAllianceId } return 0 @@ -93,10 +93,10 @@ 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 { - if matchup.RedAllianceWins >= matchup.numWinsToAdvance { + if matchup.RedAllianceWins >= matchup.NumWinsToAdvance { return matchup.BlueAllianceId } - if matchup.BlueAllianceWins >= matchup.numWinsToAdvance { + if matchup.BlueAllianceWins >= matchup.NumWinsToAdvance { return matchup.RedAllianceId } return 0 @@ -111,30 +111,30 @@ func (matchup *Matchup) isComplete() bool { // results, counting wins and creating or deleting matches as required. func (matchup *Matchup) update(database *model.Database) error { // Update child matchups first. Only recurse down winner links to avoid visiting a node twice. - if matchup.RedAllianceSourceMatchup != nil && matchup.redAllianceSource.useWinner { - if err := matchup.RedAllianceSourceMatchup.update(database); err != nil { + if matchup.redAllianceSourceMatchup != nil && matchup.redAllianceSource.useWinner { + if err := matchup.redAllianceSourceMatchup.update(database); err != nil { return err } } - if matchup.BlueAllianceSourceMatchup != nil && matchup.blueAllianceSource.useWinner { - if err := matchup.BlueAllianceSourceMatchup.update(database); err != nil { + if matchup.blueAllianceSourceMatchup != nil && matchup.blueAllianceSource.useWinner { + if err := matchup.blueAllianceSourceMatchup.update(database); err != nil { return err } } // 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.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.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() } } @@ -212,7 +212,7 @@ func (matchup *Matchup) update(database *model.Database) error { if matchup.BlueAllianceWins > maxWins { maxWins = matchup.BlueAllianceWins } - numUnplayedMatchesNeeded := matchup.numWinsToAdvance - maxWins + numUnplayedMatchesNeeded := matchup.NumWinsToAdvance - maxWins if len(unplayedMatches) > numUnplayedMatchesNeeded { // Delete any superfluous matches off the end of the list. for i := 0; i < len(unplayedMatches)-numUnplayedMatchesNeeded; i++ { diff --git a/bracket/single_elimination.go b/bracket/single_elimination.go index 01616d2..25dea78 100644 --- a/bracket/single_elimination.go +++ b/bracket/single_elimination.go @@ -23,105 +23,105 @@ var singleEliminationBracketMatchupTemplates = []matchupTemplate{ { matchupKey: newMatchupKey(1, 1), displayNameFormat: "EF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 1}, blueAllianceSource: allianceSource{allianceId: 16}, }, { matchupKey: newMatchupKey(1, 2), displayNameFormat: "EF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 8}, blueAllianceSource: allianceSource{allianceId: 9}, }, { matchupKey: newMatchupKey(1, 3), displayNameFormat: "EF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 4}, blueAllianceSource: allianceSource{allianceId: 13}, }, { matchupKey: newMatchupKey(1, 4), displayNameFormat: "EF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 5}, blueAllianceSource: allianceSource{allianceId: 12}, }, { matchupKey: newMatchupKey(1, 5), displayNameFormat: "EF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 2}, blueAllianceSource: allianceSource{allianceId: 15}, }, { matchupKey: newMatchupKey(1, 6), displayNameFormat: "EF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 7}, blueAllianceSource: allianceSource{allianceId: 10}, }, { matchupKey: newMatchupKey(1, 7), displayNameFormat: "EF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 3}, blueAllianceSource: allianceSource{allianceId: 14}, }, { matchupKey: newMatchupKey(1, 8), displayNameFormat: "EF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: allianceSource{allianceId: 6}, blueAllianceSource: allianceSource{allianceId: 11}, }, { matchupKey: newMatchupKey(2, 1), displayNameFormat: "QF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(1, 1), blueAllianceSource: newWinnerAllianceSource(1, 2), }, { matchupKey: newMatchupKey(2, 2), displayNameFormat: "QF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(1, 3), blueAllianceSource: newWinnerAllianceSource(1, 4), }, { matchupKey: newMatchupKey(2, 3), displayNameFormat: "QF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(1, 5), blueAllianceSource: newWinnerAllianceSource(1, 6), }, { matchupKey: newMatchupKey(2, 4), displayNameFormat: "QF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(1, 7), blueAllianceSource: newWinnerAllianceSource(1, 8), }, { matchupKey: newMatchupKey(3, 1), displayNameFormat: "SF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(2, 1), blueAllianceSource: newWinnerAllianceSource(2, 2), }, { matchupKey: newMatchupKey(3, 2), displayNameFormat: "SF${group}-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(2, 3), blueAllianceSource: newWinnerAllianceSource(2, 4), }, { matchupKey: newMatchupKey(4, 1), displayNameFormat: "F-${instance}", - numWinsToAdvance: 2, + NumWinsToAdvance: 2, redAllianceSource: newWinnerAllianceSource(3, 1), blueAllianceSource: newWinnerAllianceSource(3, 2), }, diff --git a/field/arena_notifiers.go b/field/arena_notifiers.go index 22cd8e9..7cbdf24 100755 --- a/field/arena_notifiers.go +++ b/field/arena_notifiers.go @@ -7,6 +7,7 @@ package field import ( "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/network" @@ -180,31 +181,23 @@ func (arena *Arena) generateRealtimeScoreMessage() interface{} { func (arena *Arena) generateScorePostedMessage() interface{} { // For elimination matches, summarize the state of the series. var seriesStatus, seriesLeader string + var matchup *bracket.Matchup if arena.SavedMatch.Type == "elimination" { - matches, _ := arena.Database.GetMatchesByElimRoundGroup(arena.SavedMatch.ElimRound, arena.SavedMatch.ElimGroup) - var redWins, blueWins int - for _, match := range matches { - if match.Status == model.RedWonMatch { - redWins++ - } else if match.Status == model.BlueWonMatch { - blueWins++ - } - } - - if redWins == 2 { - seriesStatus = fmt.Sprintf("Red Wins Series %d-%d", redWins, blueWins) + matchup, _ = arena.PlayoffBracket.GetMatchup(arena.SavedMatch.ElimRound, arena.SavedMatch.ElimGroup) + if matchup.RedAllianceWins >= matchup.NumWinsToAdvance { + seriesStatus = fmt.Sprintf("Red Wins Series %d-%d", matchup.RedAllianceWins, matchup.BlueAllianceWins) seriesLeader = "red" - } else if blueWins == 2 { - seriesStatus = fmt.Sprintf("Blue Wins Series %d-%d", blueWins, redWins) + } else if matchup.BlueAllianceWins >= matchup.NumWinsToAdvance { + seriesStatus = fmt.Sprintf("Blue Wins Series %d-%d", matchup.BlueAllianceWins, matchup.RedAllianceWins) seriesLeader = "blue" - } else if redWins > blueWins { - seriesStatus = fmt.Sprintf("Red Leads Series %d-%d", redWins, blueWins) + } else if matchup.RedAllianceWins > matchup.BlueAllianceWins { + seriesStatus = fmt.Sprintf("Red Leads Series %d-%d", matchup.RedAllianceWins, matchup.BlueAllianceWins) seriesLeader = "red" - } else if blueWins > redWins { - seriesStatus = fmt.Sprintf("Blue Leads Series %d-%d", blueWins, redWins) + } else if matchup.BlueAllianceWins > matchup.RedAllianceWins { + seriesStatus = fmt.Sprintf("Blue Leads Series %d-%d", matchup.BlueAllianceWins, matchup.RedAllianceWins) seriesLeader = "blue" } else { - seriesStatus = fmt.Sprintf("Series Tied %d-%d", redWins, blueWins) + seriesStatus = fmt.Sprintf("Series Tied %d-%d", matchup.RedAllianceWins, matchup.BlueAllianceWins) } } @@ -221,9 +214,15 @@ func (arena *Arena) generateScorePostedMessage() interface{} { Rankings map[int]game.Ranking SeriesStatus string SeriesLeader string - }{arena.SavedMatch.CapitalizedType(), arena.SavedMatch, arena.SavedMatchResult.RedScoreSummary(), - arena.SavedMatchResult.BlueScoreSummary(), rankings, - seriesStatus, seriesLeader} + }{ + arena.SavedMatch.CapitalizedType(), + arena.SavedMatch, + arena.SavedMatchResult.RedScoreSummary(), + arena.SavedMatchResult.BlueScoreSummary(), + rankings, + seriesStatus, + seriesLeader, + } } // Constructs the data object for one alliance sent to the audience display for the realtime scoring overlay.