Refactor playoff series status text on audience display to use matchup data.

This commit is contained in:
Patrick Fairbank
2022-08-17 18:36:21 -07:00
parent 476daa8c5c
commit 2a228a4725
6 changed files with 115 additions and 84 deletions

View File

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

View File

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

View File

@@ -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),
},

View File

@@ -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++ {

View File

@@ -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),
},

View File

@@ -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.