mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Refactor tournament progression methods into separate package.
This commit is contained in:
248
tournament/elimination_schedule.go
Normal file
248
tournament/elimination_schedule.go
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
//
|
||||
// Functions for creating and updating the elimination match schedule.
|
||||
|
||||
package tournament
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Team254/cheesy-arena/model"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const ElimMatchSpacingSec = 600
|
||||
|
||||
// Incrementally creates any elimination matches that can be created, based on the results of alliance
|
||||
// selection or prior elimination rounds. Returns the winning alliance once it has been determined.
|
||||
func UpdateEliminationSchedule(database *model.Database, startTime time.Time) ([]model.AllianceTeam, error) {
|
||||
alliances, err := database.GetAllAlliances()
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
winner, err := buildEliminationMatchSet(database, 1, 1, len(alliances))
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
|
||||
// Update the scheduled time for all matches that have yet to be run.
|
||||
matches, err := database.GetMatchesByType("elimination")
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
matchIndex := 0
|
||||
for _, match := range matches {
|
||||
if match.Status == "complete" {
|
||||
continue
|
||||
}
|
||||
match.Time = startTime.Add(time.Duration(matchIndex*ElimMatchSpacingSec) * time.Second)
|
||||
database.SaveMatch(&match)
|
||||
matchIndex++
|
||||
}
|
||||
|
||||
return winner, err
|
||||
}
|
||||
|
||||
// Recursively traverses the elimination bracket downwards, creating matches as necessary. Returns the winner
|
||||
// of the given round if known.
|
||||
func buildEliminationMatchSet(database *model.Database, round int, group int, numAlliances int) ([]model.AllianceTeam, error) {
|
||||
if numAlliances < 2 {
|
||||
return []model.AllianceTeam{}, fmt.Errorf("Must have at least 2 alliances")
|
||||
}
|
||||
roundName, ok := model.ElimRoundNames[round]
|
||||
if !ok {
|
||||
return []model.AllianceTeam{}, fmt.Errorf("Round of depth %d is not supported", round*2)
|
||||
}
|
||||
if round != 1 {
|
||||
roundName += strconv.Itoa(group)
|
||||
}
|
||||
|
||||
// Recurse to figure out who the involved alliances are.
|
||||
var redAlliance, blueAlliance []model.AllianceTeam
|
||||
var err error
|
||||
if numAlliances < 4*round {
|
||||
// This is the first round for some or all alliances and will be at least partially populated from the
|
||||
// alliance selection results.
|
||||
matchups := []int{1, 16, 8, 9, 4, 13, 5, 12, 2, 15, 7, 10, 3, 14, 6, 11}
|
||||
factor := len(matchups) / round
|
||||
redAllianceNumber := matchups[(group-1)*factor]
|
||||
blueAllianceNumber := matchups[(group-1)*factor+factor/2]
|
||||
numDirectAlliances := 4*round - numAlliances
|
||||
if redAllianceNumber <= numDirectAlliances {
|
||||
// The red alliance has a bye or the number of alliances is a power of 2; get from alliance selection.
|
||||
redAlliance, err = database.GetTeamsByAlliance(redAllianceNumber)
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
}
|
||||
if blueAllianceNumber <= numDirectAlliances {
|
||||
// The blue alliance has a bye or the number of alliances is a power of 2; get from alliance selection.
|
||||
blueAlliance, err = database.GetTeamsByAlliance(blueAllianceNumber)
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the alliances aren't known yet, get them from one round down in the bracket.
|
||||
if len(redAlliance) == 0 {
|
||||
redAlliance, err = buildEliminationMatchSet(database, round*2, group*2-1, numAlliances)
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
}
|
||||
if len(blueAlliance) == 0 {
|
||||
blueAlliance, err = buildEliminationMatchSet(database, round*2, group*2, numAlliances)
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Bail if the rounds below are not yet complete and we don't know either alliance competing this round.
|
||||
if len(redAlliance) == 0 && len(blueAlliance) == 0 {
|
||||
return []model.AllianceTeam{}, nil
|
||||
}
|
||||
|
||||
// Check if the match set exists already and if it has been won.
|
||||
var redWins, blueWins, numIncomplete int
|
||||
var ties []*model.Match
|
||||
matches, err := database.GetMatchesByElimRoundGroup(round, group)
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
var unplayedMatches []*model.Match
|
||||
for _, match := range matches {
|
||||
// Update the teams in the match if they are not yet set or are incorrect.
|
||||
if len(redAlliance) != 0 && !(teamInAlliance(match.Red1, redAlliance) &&
|
||||
teamInAlliance(match.Red2, redAlliance) && teamInAlliance(match.Red3, redAlliance)) {
|
||||
positionRedTeams(&match, redAlliance)
|
||||
database.SaveMatch(&match)
|
||||
} else if len(blueAlliance) != 0 && !(teamInAlliance(match.Blue1, blueAlliance) &&
|
||||
teamInAlliance(match.Blue2, blueAlliance) && teamInAlliance(match.Blue3, blueAlliance)) {
|
||||
positionBlueTeams(&match, blueAlliance)
|
||||
database.SaveMatch(&match)
|
||||
}
|
||||
|
||||
if match.Status != "complete" {
|
||||
unplayedMatches = append(unplayedMatches, &match)
|
||||
numIncomplete += 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Check who won.
|
||||
switch match.Winner {
|
||||
case "R":
|
||||
redWins += 1
|
||||
case "B":
|
||||
blueWins += 1
|
||||
case "T":
|
||||
ties = append(ties, &match)
|
||||
default:
|
||||
return []model.AllianceTeam{}, fmt.Errorf("Completed match %d has invalid winner '%s'", match.Id,
|
||||
match.Winner)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete any superfluous matches if the round is won.
|
||||
if redWins == 2 || blueWins == 2 {
|
||||
for _, match := range unplayedMatches {
|
||||
err = database.DeleteMatch(match)
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Bail out and announce the winner of this round.
|
||||
if redWins == 2 {
|
||||
return redAlliance, nil
|
||||
} else {
|
||||
return blueAlliance, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create initial set of matches or recreate any superfluous matches that were deleted but now are needed
|
||||
// due to a revision in who won.
|
||||
if len(matches) == 0 || len(ties) == 0 && numIncomplete == 0 {
|
||||
// Fill in zeroes if only one alliance is known.
|
||||
if len(redAlliance) == 0 {
|
||||
redAlliance = []model.AllianceTeam{{}, {}, {}}
|
||||
} else if len(blueAlliance) == 0 {
|
||||
blueAlliance = []model.AllianceTeam{{}, {}, {}}
|
||||
}
|
||||
if len(redAlliance) < 3 || len(blueAlliance) < 3 {
|
||||
// Raise an error if the alliance selection process gave us less than 3 teams per alliance.
|
||||
return []model.AllianceTeam{}, fmt.Errorf("Alliances must consist of at least 3 teams")
|
||||
}
|
||||
if len(matches) < 1 {
|
||||
err = database.CreateMatch(createMatch(roundName, round, group, 1, redAlliance, blueAlliance))
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
}
|
||||
if len(matches) < 2 {
|
||||
err = database.CreateMatch(createMatch(roundName, round, group, 2, redAlliance, blueAlliance))
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
}
|
||||
if len(matches) < 3 {
|
||||
err = database.CreateMatch(createMatch(roundName, round, group, 3, redAlliance, blueAlliance))
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicate any ties if we have run out of matches. Don't change the team positions, so queueing
|
||||
// personnel can reuse any tied matches without having to print new schedules.
|
||||
if numIncomplete == 0 {
|
||||
for index, tie := range ties {
|
||||
match := createMatch(roundName, round, group, len(matches)+index+1, redAlliance, blueAlliance)
|
||||
match.Red1, match.Red2, match.Red3 = tie.Red1, tie.Red2, tie.Red3
|
||||
match.Blue1, match.Blue2, match.Blue3 = tie.Blue1, tie.Blue2, tie.Blue3
|
||||
err = database.CreateMatch(match)
|
||||
if err != nil {
|
||||
return []model.AllianceTeam{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []model.AllianceTeam{}, nil
|
||||
}
|
||||
|
||||
// Creates a match at the given point in the elimination bracket and populates the teams.
|
||||
func createMatch(roundName string, round int, group int, instance int, redAlliance []model.AllianceTeam,
|
||||
blueAlliance []model.AllianceTeam) *model.Match {
|
||||
match := model.Match{Type: "elimination", DisplayName: fmt.Sprintf("%s-%d", roundName, instance),
|
||||
ElimRound: round, ElimGroup: group, ElimInstance: instance}
|
||||
positionRedTeams(&match, redAlliance)
|
||||
positionBlueTeams(&match, blueAlliance)
|
||||
return &match
|
||||
}
|
||||
|
||||
// Assigns the first three teams from the alliance into the red team slots for the match.
|
||||
func positionRedTeams(match *model.Match, alliance []model.AllianceTeam) {
|
||||
// For the 2015 game, the alliance captain is in the middle, first pick on the left, second on the right.
|
||||
match.Red1 = alliance[1].TeamId
|
||||
match.Red2 = alliance[0].TeamId
|
||||
match.Red3 = alliance[2].TeamId
|
||||
}
|
||||
|
||||
// Assigns the first three teams from the alliance into the blue team slots for the match.
|
||||
func positionBlueTeams(match *model.Match, alliance []model.AllianceTeam) {
|
||||
// For the 2015 game, the alliance captain is in the middle, first pick on the left, second on the right.
|
||||
match.Blue1 = alliance[1].TeamId
|
||||
match.Blue2 = alliance[0].TeamId
|
||||
match.Blue3 = alliance[2].TeamId
|
||||
}
|
||||
|
||||
// Returns true if the given team is part of the given alliance.
|
||||
func teamInAlliance(teamId int, alliance []model.AllianceTeam) bool {
|
||||
for _, allianceTeam := range alliance {
|
||||
if teamId == allianceTeam.TeamId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
751
tournament/elimination_schedule_test.go
Normal file
751
tournament/elimination_schedule_test.go
Normal file
@@ -0,0 +1,751 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
|
||||
package tournament
|
||||
|
||||
import (
|
||||
"github.com/Team254/cheesy-arena/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEliminationScheduleInitial(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
CreateTestAlliances(database, 2)
|
||||
_, err := UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err := database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 3, len(matches)) {
|
||||
assertMatch(t, matches[0], "F-1", 1, 2)
|
||||
assertMatch(t, matches[1], "F-2", 1, 2)
|
||||
assertMatch(t, matches[2], "F-3", 1, 2)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 3)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 6, len(matches)) {
|
||||
assertMatch(t, matches[0], "SF2-1", 2, 3)
|
||||
assertMatch(t, matches[1], "SF2-2", 2, 3)
|
||||
assertMatch(t, matches[2], "SF2-3", 2, 3)
|
||||
assertMatch(t, matches[3], "F-1", 1, 0)
|
||||
assertMatch(t, matches[4], "F-2", 1, 0)
|
||||
assertMatch(t, matches[5], "F-3", 1, 0)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 4)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 6, len(matches)) {
|
||||
assertMatch(t, matches[0], "SF1-1", 1, 4)
|
||||
assertMatch(t, matches[1], "SF2-1", 2, 3)
|
||||
assertMatch(t, matches[2], "SF1-2", 1, 4)
|
||||
assertMatch(t, matches[3], "SF2-2", 2, 3)
|
||||
assertMatch(t, matches[4], "SF1-3", 1, 4)
|
||||
assertMatch(t, matches[5], "SF2-3", 2, 3)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 5)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 9, len(matches)) {
|
||||
assertMatch(t, matches[0], "QF2-1", 4, 5)
|
||||
assertMatch(t, matches[1], "QF2-2", 4, 5)
|
||||
assertMatch(t, matches[2], "QF2-3", 4, 5)
|
||||
assertMatch(t, matches[3], "SF1-1", 1, 0)
|
||||
assertMatch(t, matches[4], "SF2-1", 2, 3)
|
||||
assertMatch(t, matches[5], "SF1-2", 1, 0)
|
||||
assertMatch(t, matches[6], "SF2-2", 2, 3)
|
||||
assertMatch(t, matches[7], "SF1-3", 1, 0)
|
||||
assertMatch(t, matches[8], "SF2-3", 2, 3)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 6)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 12, len(matches)) {
|
||||
assertMatch(t, matches[0], "QF2-1", 4, 5)
|
||||
assertMatch(t, matches[1], "QF4-1", 3, 6)
|
||||
assertMatch(t, matches[2], "QF2-2", 4, 5)
|
||||
assertMatch(t, matches[3], "QF4-2", 3, 6)
|
||||
assertMatch(t, matches[4], "QF2-3", 4, 5)
|
||||
assertMatch(t, matches[5], "QF4-3", 3, 6)
|
||||
assertMatch(t, matches[6], "SF1-1", 1, 0)
|
||||
assertMatch(t, matches[7], "SF2-1", 2, 0)
|
||||
assertMatch(t, matches[8], "SF1-2", 1, 0)
|
||||
assertMatch(t, matches[9], "SF2-2", 2, 0)
|
||||
assertMatch(t, matches[10], "SF1-3", 1, 0)
|
||||
assertMatch(t, matches[11], "SF2-3", 2, 0)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 7)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 12, len(matches)) {
|
||||
assertMatch(t, matches[0], "QF2-1", 4, 5)
|
||||
assertMatch(t, matches[1], "QF3-1", 2, 7)
|
||||
assertMatch(t, matches[2], "QF4-1", 3, 6)
|
||||
assertMatch(t, matches[3], "QF2-2", 4, 5)
|
||||
assertMatch(t, matches[4], "QF3-2", 2, 7)
|
||||
assertMatch(t, matches[5], "QF4-2", 3, 6)
|
||||
assertMatch(t, matches[6], "QF2-3", 4, 5)
|
||||
assertMatch(t, matches[7], "QF3-3", 2, 7)
|
||||
assertMatch(t, matches[8], "QF4-3", 3, 6)
|
||||
assertMatch(t, matches[9], "SF1-1", 1, 0)
|
||||
assertMatch(t, matches[10], "SF1-2", 1, 0)
|
||||
assertMatch(t, matches[11], "SF1-3", 1, 0)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 8)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 12, len(matches)) {
|
||||
assertMatch(t, matches[0], "QF1-1", 1, 8)
|
||||
assertMatch(t, matches[1], "QF2-1", 4, 5)
|
||||
assertMatch(t, matches[2], "QF3-1", 2, 7)
|
||||
assertMatch(t, matches[3], "QF4-1", 3, 6)
|
||||
assertMatch(t, matches[4], "QF1-2", 1, 8)
|
||||
assertMatch(t, matches[5], "QF2-2", 4, 5)
|
||||
assertMatch(t, matches[6], "QF3-2", 2, 7)
|
||||
assertMatch(t, matches[7], "QF4-2", 3, 6)
|
||||
assertMatch(t, matches[8], "QF1-3", 1, 8)
|
||||
assertMatch(t, matches[9], "QF2-3", 4, 5)
|
||||
assertMatch(t, matches[10], "QF3-3", 2, 7)
|
||||
assertMatch(t, matches[11], "QF4-3", 3, 6)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 9)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 15, len(matches)) {
|
||||
assertMatch(t, matches[0], "EF2-1", 8, 9)
|
||||
assertMatch(t, matches[1], "EF2-2", 8, 9)
|
||||
assertMatch(t, matches[2], "EF2-3", 8, 9)
|
||||
assertMatch(t, matches[3], "QF1-1", 1, 0)
|
||||
assertMatch(t, matches[4], "QF2-1", 4, 5)
|
||||
assertMatch(t, matches[5], "QF3-1", 2, 7)
|
||||
assertMatch(t, matches[6], "QF4-1", 3, 6)
|
||||
assertMatch(t, matches[7], "QF1-2", 1, 0)
|
||||
assertMatch(t, matches[8], "QF2-2", 4, 5)
|
||||
assertMatch(t, matches[9], "QF3-2", 2, 7)
|
||||
assertMatch(t, matches[10], "QF4-2", 3, 6)
|
||||
assertMatch(t, matches[11], "QF1-3", 1, 0)
|
||||
assertMatch(t, matches[12], "QF2-3", 4, 5)
|
||||
assertMatch(t, matches[13], "QF3-3", 2, 7)
|
||||
assertMatch(t, matches[14], "QF4-3", 3, 6)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 10)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 18, len(matches)) {
|
||||
assertMatch(t, matches[0], "EF2-1", 8, 9)
|
||||
assertMatch(t, matches[1], "EF6-1", 7, 10)
|
||||
assertMatch(t, matches[2], "EF2-2", 8, 9)
|
||||
assertMatch(t, matches[3], "EF6-2", 7, 10)
|
||||
assertMatch(t, matches[4], "EF2-3", 8, 9)
|
||||
assertMatch(t, matches[5], "EF6-3", 7, 10)
|
||||
assertMatch(t, matches[6], "QF1-1", 1, 0)
|
||||
assertMatch(t, matches[7], "QF2-1", 4, 5)
|
||||
assertMatch(t, matches[8], "QF3-1", 2, 0)
|
||||
assertMatch(t, matches[9], "QF4-1", 3, 6)
|
||||
assertMatch(t, matches[10], "QF1-2", 1, 0)
|
||||
assertMatch(t, matches[11], "QF2-2", 4, 5)
|
||||
assertMatch(t, matches[12], "QF3-2", 2, 0)
|
||||
assertMatch(t, matches[13], "QF4-2", 3, 6)
|
||||
assertMatch(t, matches[14], "QF1-3", 1, 0)
|
||||
assertMatch(t, matches[15], "QF2-3", 4, 5)
|
||||
assertMatch(t, matches[16], "QF3-3", 2, 0)
|
||||
assertMatch(t, matches[17], "QF4-3", 3, 6)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 11)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 21, len(matches)) {
|
||||
assertMatch(t, matches[0], "EF2-1", 8, 9)
|
||||
assertMatch(t, matches[1], "EF6-1", 7, 10)
|
||||
assertMatch(t, matches[2], "EF8-1", 6, 11)
|
||||
assertMatch(t, matches[3], "EF2-2", 8, 9)
|
||||
assertMatch(t, matches[4], "EF6-2", 7, 10)
|
||||
assertMatch(t, matches[5], "EF8-2", 6, 11)
|
||||
assertMatch(t, matches[6], "EF2-3", 8, 9)
|
||||
assertMatch(t, matches[7], "EF6-3", 7, 10)
|
||||
assertMatch(t, matches[8], "EF8-3", 6, 11)
|
||||
assertMatch(t, matches[9], "QF1-1", 1, 0)
|
||||
assertMatch(t, matches[10], "QF2-1", 4, 5)
|
||||
assertMatch(t, matches[11], "QF3-1", 2, 0)
|
||||
assertMatch(t, matches[12], "QF4-1", 3, 0)
|
||||
assertMatch(t, matches[13], "QF1-2", 1, 0)
|
||||
assertMatch(t, matches[14], "QF2-2", 4, 5)
|
||||
assertMatch(t, matches[15], "QF3-2", 2, 0)
|
||||
assertMatch(t, matches[16], "QF4-2", 3, 0)
|
||||
assertMatch(t, matches[17], "QF1-3", 1, 0)
|
||||
assertMatch(t, matches[18], "QF2-3", 4, 5)
|
||||
assertMatch(t, matches[19], "QF3-3", 2, 0)
|
||||
assertMatch(t, matches[20], "QF4-3", 3, 0)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 12)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 24, len(matches)) {
|
||||
assertMatch(t, matches[0], "EF2-1", 8, 9)
|
||||
assertMatch(t, matches[1], "EF4-1", 5, 12)
|
||||
assertMatch(t, matches[2], "EF6-1", 7, 10)
|
||||
assertMatch(t, matches[3], "EF8-1", 6, 11)
|
||||
assertMatch(t, matches[4], "EF2-2", 8, 9)
|
||||
assertMatch(t, matches[5], "EF4-2", 5, 12)
|
||||
assertMatch(t, matches[6], "EF6-2", 7, 10)
|
||||
assertMatch(t, matches[7], "EF8-2", 6, 11)
|
||||
assertMatch(t, matches[8], "EF2-3", 8, 9)
|
||||
assertMatch(t, matches[9], "EF4-3", 5, 12)
|
||||
assertMatch(t, matches[10], "EF6-3", 7, 10)
|
||||
assertMatch(t, matches[11], "EF8-3", 6, 11)
|
||||
assertMatch(t, matches[12], "QF1-1", 1, 0)
|
||||
assertMatch(t, matches[13], "QF2-1", 4, 0)
|
||||
assertMatch(t, matches[14], "QF3-1", 2, 0)
|
||||
assertMatch(t, matches[15], "QF4-1", 3, 0)
|
||||
assertMatch(t, matches[16], "QF1-2", 1, 0)
|
||||
assertMatch(t, matches[17], "QF2-2", 4, 0)
|
||||
assertMatch(t, matches[18], "QF3-2", 2, 0)
|
||||
assertMatch(t, matches[19], "QF4-2", 3, 0)
|
||||
assertMatch(t, matches[20], "QF1-3", 1, 0)
|
||||
assertMatch(t, matches[21], "QF2-3", 4, 0)
|
||||
assertMatch(t, matches[22], "QF3-3", 2, 0)
|
||||
assertMatch(t, matches[23], "QF4-3", 3, 0)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 13)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 24, len(matches)) {
|
||||
assertMatch(t, matches[0], "EF2-1", 8, 9)
|
||||
assertMatch(t, matches[1], "EF3-1", 4, 13)
|
||||
assertMatch(t, matches[2], "EF4-1", 5, 12)
|
||||
assertMatch(t, matches[3], "EF6-1", 7, 10)
|
||||
assertMatch(t, matches[4], "EF8-1", 6, 11)
|
||||
assertMatch(t, matches[5], "EF2-2", 8, 9)
|
||||
assertMatch(t, matches[6], "EF3-2", 4, 13)
|
||||
assertMatch(t, matches[7], "EF4-2", 5, 12)
|
||||
assertMatch(t, matches[8], "EF6-2", 7, 10)
|
||||
assertMatch(t, matches[9], "EF8-2", 6, 11)
|
||||
assertMatch(t, matches[10], "EF2-3", 8, 9)
|
||||
assertMatch(t, matches[11], "EF3-3", 4, 13)
|
||||
assertMatch(t, matches[12], "EF4-3", 5, 12)
|
||||
assertMatch(t, matches[13], "EF6-3", 7, 10)
|
||||
assertMatch(t, matches[14], "EF8-3", 6, 11)
|
||||
assertMatch(t, matches[15], "QF1-1", 1, 0)
|
||||
assertMatch(t, matches[16], "QF3-1", 2, 0)
|
||||
assertMatch(t, matches[17], "QF4-1", 3, 0)
|
||||
assertMatch(t, matches[18], "QF1-2", 1, 0)
|
||||
assertMatch(t, matches[19], "QF3-2", 2, 0)
|
||||
assertMatch(t, matches[20], "QF4-2", 3, 0)
|
||||
assertMatch(t, matches[21], "QF1-3", 1, 0)
|
||||
assertMatch(t, matches[22], "QF3-3", 2, 0)
|
||||
assertMatch(t, matches[23], "QF4-3", 3, 0)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 14)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 24, len(matches)) {
|
||||
assertMatch(t, matches[0], "EF2-1", 8, 9)
|
||||
assertMatch(t, matches[1], "EF3-1", 4, 13)
|
||||
assertMatch(t, matches[2], "EF4-1", 5, 12)
|
||||
assertMatch(t, matches[3], "EF6-1", 7, 10)
|
||||
assertMatch(t, matches[4], "EF7-1", 3, 14)
|
||||
assertMatch(t, matches[5], "EF8-1", 6, 11)
|
||||
assertMatch(t, matches[6], "EF2-2", 8, 9)
|
||||
assertMatch(t, matches[7], "EF3-2", 4, 13)
|
||||
assertMatch(t, matches[8], "EF4-2", 5, 12)
|
||||
assertMatch(t, matches[9], "EF6-2", 7, 10)
|
||||
assertMatch(t, matches[10], "EF7-2", 3, 14)
|
||||
assertMatch(t, matches[11], "EF8-2", 6, 11)
|
||||
assertMatch(t, matches[12], "EF2-3", 8, 9)
|
||||
assertMatch(t, matches[13], "EF3-3", 4, 13)
|
||||
assertMatch(t, matches[14], "EF4-3", 5, 12)
|
||||
assertMatch(t, matches[15], "EF6-3", 7, 10)
|
||||
assertMatch(t, matches[16], "EF7-3", 3, 14)
|
||||
assertMatch(t, matches[17], "EF8-3", 6, 11)
|
||||
assertMatch(t, matches[18], "QF1-1", 1, 0)
|
||||
assertMatch(t, matches[19], "QF3-1", 2, 0)
|
||||
assertMatch(t, matches[20], "QF1-2", 1, 0)
|
||||
assertMatch(t, matches[21], "QF3-2", 2, 0)
|
||||
assertMatch(t, matches[22], "QF1-3", 1, 0)
|
||||
assertMatch(t, matches[23], "QF3-3", 2, 0)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 15)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 24, len(matches)) {
|
||||
assertMatch(t, matches[0], "EF2-1", 8, 9)
|
||||
assertMatch(t, matches[1], "EF3-1", 4, 13)
|
||||
assertMatch(t, matches[2], "EF4-1", 5, 12)
|
||||
assertMatch(t, matches[3], "EF5-1", 2, 15)
|
||||
assertMatch(t, matches[4], "EF6-1", 7, 10)
|
||||
assertMatch(t, matches[5], "EF7-1", 3, 14)
|
||||
assertMatch(t, matches[6], "EF8-1", 6, 11)
|
||||
assertMatch(t, matches[7], "EF2-2", 8, 9)
|
||||
assertMatch(t, matches[8], "EF3-2", 4, 13)
|
||||
assertMatch(t, matches[9], "EF4-2", 5, 12)
|
||||
assertMatch(t, matches[10], "EF5-2", 2, 15)
|
||||
assertMatch(t, matches[11], "EF6-2", 7, 10)
|
||||
assertMatch(t, matches[12], "EF7-2", 3, 14)
|
||||
assertMatch(t, matches[13], "EF8-2", 6, 11)
|
||||
assertMatch(t, matches[14], "EF2-3", 8, 9)
|
||||
assertMatch(t, matches[15], "EF3-3", 4, 13)
|
||||
assertMatch(t, matches[16], "EF4-3", 5, 12)
|
||||
assertMatch(t, matches[17], "EF5-3", 2, 15)
|
||||
assertMatch(t, matches[18], "EF6-3", 7, 10)
|
||||
assertMatch(t, matches[19], "EF7-3", 3, 14)
|
||||
assertMatch(t, matches[20], "EF8-3", 6, 11)
|
||||
assertMatch(t, matches[21], "QF1-1", 1, 0)
|
||||
assertMatch(t, matches[22], "QF1-2", 1, 0)
|
||||
assertMatch(t, matches[23], "QF1-3", 1, 0)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
|
||||
CreateTestAlliances(database, 16)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 24, len(matches)) {
|
||||
assertMatch(t, matches[0], "EF1-1", 1, 16)
|
||||
assertMatch(t, matches[1], "EF2-1", 8, 9)
|
||||
assertMatch(t, matches[2], "EF3-1", 4, 13)
|
||||
assertMatch(t, matches[3], "EF4-1", 5, 12)
|
||||
assertMatch(t, matches[4], "EF5-1", 2, 15)
|
||||
assertMatch(t, matches[5], "EF6-1", 7, 10)
|
||||
assertMatch(t, matches[6], "EF7-1", 3, 14)
|
||||
assertMatch(t, matches[7], "EF8-1", 6, 11)
|
||||
assertMatch(t, matches[8], "EF1-2", 1, 16)
|
||||
assertMatch(t, matches[9], "EF2-2", 8, 9)
|
||||
assertMatch(t, matches[10], "EF3-2", 4, 13)
|
||||
assertMatch(t, matches[11], "EF4-2", 5, 12)
|
||||
assertMatch(t, matches[12], "EF5-2", 2, 15)
|
||||
assertMatch(t, matches[13], "EF6-2", 7, 10)
|
||||
assertMatch(t, matches[14], "EF7-2", 3, 14)
|
||||
assertMatch(t, matches[15], "EF8-2", 6, 11)
|
||||
assertMatch(t, matches[16], "EF1-3", 1, 16)
|
||||
assertMatch(t, matches[17], "EF2-3", 8, 9)
|
||||
assertMatch(t, matches[18], "EF3-3", 4, 13)
|
||||
assertMatch(t, matches[19], "EF4-3", 5, 12)
|
||||
assertMatch(t, matches[20], "EF5-3", 2, 15)
|
||||
assertMatch(t, matches[21], "EF6-3", 7, 10)
|
||||
assertMatch(t, matches[22], "EF7-3", 3, 14)
|
||||
assertMatch(t, matches[23], "EF8-3", 6, 11)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
}
|
||||
|
||||
func TestEliminationScheduleErrors(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
CreateTestAlliances(database, 1)
|
||||
_, err := UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
if assert.NotNil(t, err) {
|
||||
assert.Equal(t, "Must have at least 2 alliances", err.Error())
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
|
||||
CreateTestAlliances(database, 17)
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
if assert.NotNil(t, err) {
|
||||
assert.Equal(t, "Round of depth 32 is not supported", err.Error())
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
|
||||
database.CreateAllianceTeam(&model.AllianceTeam{0, 1, 0, 1})
|
||||
database.CreateAllianceTeam(&model.AllianceTeam{0, 1, 1, 2})
|
||||
database.CreateAllianceTeam(&model.AllianceTeam{0, 2, 0, 3})
|
||||
database.CreateAllianceTeam(&model.AllianceTeam{0, 2, 1, 4})
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
if assert.NotNil(t, err) {
|
||||
assert.Equal(t, "Alliances must consist of at least 3 teams", err.Error())
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
}
|
||||
|
||||
func TestEliminationSchedulePopulatePartialMatch(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
// Final should be updated after semifinal is concluded.
|
||||
CreateTestAlliances(database, 3)
|
||||
UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "SF2-1", "B")
|
||||
scoreMatch(database, "SF2-2", "B")
|
||||
_, err := UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err := database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 5, len(matches)) {
|
||||
assertMatch(t, matches[2], "F-1", 1, 3)
|
||||
assertMatch(t, matches[3], "F-2", 1, 3)
|
||||
assertMatch(t, matches[4], "F-3", 1, 3)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
database.TruncateMatchResults()
|
||||
|
||||
// Final should be generated and populated as both semifinals conclude.
|
||||
CreateTestAlliances(database, 4)
|
||||
UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "SF2-1", "R")
|
||||
scoreMatch(database, "SF2-2", "R")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 8, len(matches)) {
|
||||
assertMatch(t, matches[5], "F-1", 0, 2)
|
||||
assertMatch(t, matches[6], "F-2", 0, 2)
|
||||
assertMatch(t, matches[7], "F-3", 0, 2)
|
||||
}
|
||||
scoreMatch(database, "SF1-1", "R")
|
||||
scoreMatch(database, "SF1-2", "R")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 7, len(matches)) {
|
||||
assertMatch(t, matches[4], "F-1", 1, 2)
|
||||
assertMatch(t, matches[5], "F-2", 1, 2)
|
||||
assertMatch(t, matches[6], "F-3", 1, 2)
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
database.TruncateMatchResults()
|
||||
}
|
||||
|
||||
func TestEliminationScheduleCreateNextRound(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
CreateTestAlliances(database, 4)
|
||||
UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "SF1-1", "B")
|
||||
_, err := UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, _ := database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 6, len(matches))
|
||||
scoreMatch(database, "SF2-1", "B")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 6, len(matches))
|
||||
scoreMatch(database, "SF1-2", "B")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 8, len(matches))
|
||||
scoreMatch(database, "SF2-2", "B")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
if assert.Equal(t, 7, len(matches)) {
|
||||
assertMatch(t, matches[4], "F-1", 4, 3)
|
||||
assertMatch(t, matches[5], "F-2", 4, 3)
|
||||
assertMatch(t, matches[6], "F-3", 4, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEliminationScheduleDetermineWinner(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
// Round with one tie and a sweep.
|
||||
CreateTestAlliances(database, 2)
|
||||
UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "F-1", "T")
|
||||
winner, err := UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, winner)
|
||||
matches, _ := database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 3, len(matches))
|
||||
scoreMatch(database, "F-2", "B")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, winner)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 3, len(matches))
|
||||
scoreMatch(database, "F-3", "B")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
if assert.Nil(t, err) {
|
||||
if assert.Equal(t, 3, len(winner)) {
|
||||
assert.Equal(t, 2, winner[0].TeamId)
|
||||
}
|
||||
}
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 3, len(matches))
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
database.TruncateMatchResults()
|
||||
|
||||
// Round with one tie and a split.
|
||||
CreateTestAlliances(database, 2)
|
||||
UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "F-1", "R")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, winner)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 3, len(matches))
|
||||
scoreMatch(database, "F-2", "T")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, winner)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 3, len(matches))
|
||||
scoreMatch(database, "F-3", "B")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, winner)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 4, len(matches))
|
||||
assert.Equal(t, "F-4", matches[3].DisplayName)
|
||||
scoreMatch(database, "F-4", "T")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, winner)
|
||||
scoreMatch(database, "F-5", "R")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
if assert.Nil(t, err) {
|
||||
if assert.Equal(t, 3, len(winner)) {
|
||||
assert.Equal(t, 1, winner[0].TeamId)
|
||||
}
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
database.TruncateMatchResults()
|
||||
|
||||
// Round with two ties.
|
||||
CreateTestAlliances(database, 2)
|
||||
UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "F-1", "T")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, winner)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 3, len(matches))
|
||||
scoreMatch(database, "F-2", "B")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, winner)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 3, len(matches))
|
||||
scoreMatch(database, "F-3", "T")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, winner)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 5, len(matches))
|
||||
assert.Equal(t, "F-4", matches[3].DisplayName)
|
||||
assert.Equal(t, "F-5", matches[4].DisplayName)
|
||||
scoreMatch(database, "F-4", "B")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
if assert.Nil(t, err) {
|
||||
if assert.Equal(t, 3, len(winner)) {
|
||||
assert.Equal(t, 2, winner[0].TeamId)
|
||||
}
|
||||
}
|
||||
database.TruncateAllianceTeams()
|
||||
database.TruncateMatches()
|
||||
database.TruncateMatchResults()
|
||||
|
||||
// Round with repeated ties.
|
||||
CreateTestAlliances(database, 2)
|
||||
UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "F-1", "T")
|
||||
scoreMatch(database, "F-2", "T")
|
||||
scoreMatch(database, "F-3", "T")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "F-4", "T")
|
||||
scoreMatch(database, "F-5", "T")
|
||||
scoreMatch(database, "F-6", "T")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "F-7", "R")
|
||||
scoreMatch(database, "F-8", "B")
|
||||
scoreMatch(database, "F-9", "R")
|
||||
winner, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
if assert.Nil(t, err) {
|
||||
if assert.Equal(t, 3, len(winner)) {
|
||||
assert.Equal(t, 1, winner[0].TeamId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEliminationScheduleRemoveUnneededMatches(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
CreateTestAlliances(database, 2)
|
||||
UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "F-1", "R")
|
||||
scoreMatch(database, "F-2", "R")
|
||||
_, err := UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, _ := database.GetMatchesByType("elimination")
|
||||
assert.Equal(t, 2, len(matches))
|
||||
|
||||
// Check that the deleted match is recreated if the score is changed.
|
||||
scoreMatch(database, "F-2", "B")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, _ = database.GetMatchesByType("elimination")
|
||||
if assert.Equal(t, 3, len(matches)) {
|
||||
assert.Equal(t, "F-3", matches[2].DisplayName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEliminationScheduleChangePreviousRoundResult(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
CreateTestAlliances(database, 4)
|
||||
_, err := UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
scoreMatch(database, "SF2-1", "R")
|
||||
scoreMatch(database, "SF2-2", "B")
|
||||
scoreMatch(database, "SF2-3", "R")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
scoreMatch(database, "SF2-3", "B")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err := database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 9, len(matches)) {
|
||||
assertMatch(t, matches[6], "F-1", 0, 3)
|
||||
assertMatch(t, matches[7], "F-2", 0, 3)
|
||||
assertMatch(t, matches[8], "F-3", 0, 3)
|
||||
}
|
||||
|
||||
scoreMatch(database, "SF1-1", "R")
|
||||
scoreMatch(database, "SF1-2", "R")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
scoreMatch(database, "SF1-2", "B")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
scoreMatch(database, "SF1-3", "B")
|
||||
_, err = UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
assert.Nil(t, err)
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 9, len(matches)) {
|
||||
assertMatch(t, matches[6], "F-1", 4, 3)
|
||||
assertMatch(t, matches[7], "F-2", 4, 3)
|
||||
assertMatch(t, matches[8], "F-3", 4, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEliminationScheduleUnscoredMatch(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
CreateTestAlliances(database, 2)
|
||||
UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
scoreMatch(database, "F-1", "blorpy")
|
||||
_, err := UpdateEliminationSchedule(database, time.Unix(0, 0))
|
||||
if assert.NotNil(t, err) {
|
||||
assert.Equal(t, "Completed match 1 has invalid winner 'blorpy'", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEliminationScheduleTiming(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
CreateTestAlliances(database, 4)
|
||||
UpdateEliminationSchedule(database, time.Unix(1000, 0))
|
||||
matches, err := database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 6, len(matches)) {
|
||||
assert.True(t, time.Unix(1000, 0).Equal(matches[0].Time))
|
||||
assert.True(t, time.Unix(1600, 0).Equal(matches[1].Time))
|
||||
assert.True(t, time.Unix(2200, 0).Equal(matches[2].Time))
|
||||
assert.True(t, time.Unix(2800, 0).Equal(matches[3].Time))
|
||||
assert.True(t, time.Unix(3400, 0).Equal(matches[4].Time))
|
||||
assert.True(t, time.Unix(4000, 0).Equal(matches[5].Time))
|
||||
}
|
||||
scoreMatch(database, "SF1-1", "R")
|
||||
scoreMatch(database, "SF1-3", "B")
|
||||
UpdateEliminationSchedule(database, time.Unix(5000, 0))
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 6, len(matches)) {
|
||||
assert.True(t, time.Unix(1000, 0).Equal(matches[0].Time))
|
||||
assert.True(t, time.Unix(5000, 0).Equal(matches[1].Time))
|
||||
assert.True(t, time.Unix(5600, 0).Equal(matches[2].Time))
|
||||
assert.True(t, time.Unix(6200, 0).Equal(matches[3].Time))
|
||||
assert.True(t, time.Unix(3400, 0).Equal(matches[4].Time))
|
||||
assert.True(t, time.Unix(6800, 0).Equal(matches[5].Time))
|
||||
}
|
||||
}
|
||||
|
||||
func assertMatch(t *testing.T, match model.Match, displayName string, redAlliance int, blueAlliance int) {
|
||||
assert.Equal(t, displayName, match.DisplayName)
|
||||
assert.Equal(t, redAlliance, match.Red1)
|
||||
assert.Equal(t, blueAlliance, match.Blue1)
|
||||
}
|
||||
|
||||
func scoreMatch(database *model.Database, displayName string, winner string) {
|
||||
match, _ := database.GetMatchByName("elimination", displayName)
|
||||
match.Status = "complete"
|
||||
match.Winner = winner
|
||||
database.SaveMatch(match)
|
||||
}
|
||||
147
tournament/qualification_rankings.go
Normal file
147
tournament/qualification_rankings.go
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2017 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
//
|
||||
// Functions for calculating the qualification rankings.
|
||||
|
||||
package tournament
|
||||
|
||||
import (
|
||||
"github.com/Team254/cheesy-arena/game"
|
||||
"github.com/Team254/cheesy-arena/model"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Determines the rankings from the stored match results, and saves them to the database.
|
||||
func CalculateRankings(database *model.Database) error {
|
||||
matches, err := database.GetMatchesByType("qualification")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rankings := make(map[int]*game.Ranking)
|
||||
for _, match := range matches {
|
||||
if match.Status != "complete" {
|
||||
continue
|
||||
}
|
||||
matchResult, err := database.GetMatchResultForMatch(match.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !match.Red1IsSurrogate {
|
||||
addMatchResultToRankings(rankings, match.Red1, matchResult, true)
|
||||
}
|
||||
if !match.Red2IsSurrogate {
|
||||
addMatchResultToRankings(rankings, match.Red2, matchResult, true)
|
||||
}
|
||||
if !match.Red3IsSurrogate {
|
||||
addMatchResultToRankings(rankings, match.Red3, matchResult, true)
|
||||
}
|
||||
if !match.Blue1IsSurrogate {
|
||||
addMatchResultToRankings(rankings, match.Blue1, matchResult, false)
|
||||
}
|
||||
if !match.Blue2IsSurrogate {
|
||||
addMatchResultToRankings(rankings, match.Blue2, matchResult, false)
|
||||
}
|
||||
if !match.Blue3IsSurrogate {
|
||||
addMatchResultToRankings(rankings, match.Blue3, matchResult, false)
|
||||
}
|
||||
}
|
||||
|
||||
sortedRankings := sortRankings(rankings)
|
||||
for rank, ranking := range sortedRankings {
|
||||
ranking.Rank = rank + 1
|
||||
}
|
||||
err = database.ReplaceAllRankings(sortedRankings)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks all the match results for yellow and red cards, and updates the team model accordingly.
|
||||
func CalculateTeamCards(database *model.Database, matchType string) error {
|
||||
teams, err := database.GetAllTeams()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
teamsMap := make(map[string]model.Team)
|
||||
for _, team := range teams {
|
||||
team.YellowCard = false
|
||||
teamsMap[strconv.Itoa(team.Id)] = team
|
||||
}
|
||||
|
||||
matches, err := database.GetMatchesByType(matchType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, match := range matches {
|
||||
if match.Status != "complete" {
|
||||
continue
|
||||
}
|
||||
matchResult, err := database.GetMatchResultForMatch(match.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mark the team as having a yellow card if they got either a yellow or red in a previous match.
|
||||
for teamId, card := range matchResult.RedCards {
|
||||
if team, ok := teamsMap[teamId]; ok && card != "" {
|
||||
team.YellowCard = true
|
||||
teamsMap[teamId] = team
|
||||
}
|
||||
}
|
||||
for teamId, card := range matchResult.BlueCards {
|
||||
if team, ok := teamsMap[teamId]; ok && card != "" {
|
||||
team.YellowCard = true
|
||||
teamsMap[teamId] = team
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the teams to the database.
|
||||
for _, team := range teamsMap {
|
||||
err = database.SaveTeam(&team)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Incrementally accounts for the given match result in the set of rankings that are being built.
|
||||
func addMatchResultToRankings(rankings map[int]*game.Ranking, teamId int, matchResult *model.MatchResult, isRed bool) {
|
||||
ranking := rankings[teamId]
|
||||
if ranking == nil {
|
||||
ranking = &game.Ranking{TeamId: teamId}
|
||||
rankings[teamId] = ranking
|
||||
}
|
||||
|
||||
// Determine whether the team was disqualified.
|
||||
var cards map[string]string
|
||||
if isRed {
|
||||
cards = matchResult.RedCards
|
||||
} else {
|
||||
cards = matchResult.BlueCards
|
||||
}
|
||||
disqualified := false
|
||||
if card, ok := cards[strconv.Itoa(teamId)]; ok && card == "red" {
|
||||
disqualified = true
|
||||
}
|
||||
|
||||
if isRed {
|
||||
ranking.AddScoreSummary(matchResult.RedScoreSummary(), matchResult.BlueScoreSummary(), disqualified)
|
||||
} else {
|
||||
ranking.AddScoreSummary(matchResult.BlueScoreSummary(), matchResult.RedScoreSummary(), disqualified)
|
||||
}
|
||||
}
|
||||
|
||||
func sortRankings(rankings map[int]*game.Ranking) game.Rankings {
|
||||
var sortedRankings game.Rankings
|
||||
for _, ranking := range rankings {
|
||||
sortedRankings = append(sortedRankings, ranking)
|
||||
}
|
||||
sort.Sort(sortedRankings)
|
||||
return sortedRankings
|
||||
}
|
||||
91
tournament/qualification_rankings_test.go
Normal file
91
tournament/qualification_rankings_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2017 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
|
||||
package tournament
|
||||
|
||||
import (
|
||||
"github.com/Team254/cheesy-arena/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCalculateRankings(t *testing.T) {
|
||||
database := setupTestDb(t)
|
||||
|
||||
setupMatchResultsForRankings(database)
|
||||
err := CalculateRankings(database)
|
||||
assert.Nil(t, err)
|
||||
rankings, err := database.GetAllRankings()
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 6, len(rankings)) {
|
||||
assert.Equal(t, 4, rankings[0].TeamId)
|
||||
assert.Equal(t, 5, rankings[1].TeamId)
|
||||
assert.Equal(t, 6, rankings[2].TeamId)
|
||||
assert.Equal(t, 1, rankings[3].TeamId)
|
||||
assert.Equal(t, 3, rankings[4].TeamId)
|
||||
assert.Equal(t, 2, rankings[5].TeamId)
|
||||
}
|
||||
|
||||
// Test after changing a match result.
|
||||
matchResult3 := model.BuildTestMatchResult(3, 3)
|
||||
matchResult3.RedScore, matchResult3.BlueScore = matchResult3.BlueScore, matchResult3.RedScore
|
||||
err = database.CreateMatchResult(matchResult3)
|
||||
assert.Nil(t, err)
|
||||
err = CalculateRankings(database)
|
||||
assert.Nil(t, err)
|
||||
rankings, err = database.GetAllRankings()
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 6, len(rankings)) {
|
||||
assert.Equal(t, 6, rankings[0].TeamId)
|
||||
assert.Equal(t, 5, rankings[1].TeamId)
|
||||
assert.Equal(t, 4, rankings[2].TeamId)
|
||||
assert.Equal(t, 1, rankings[3].TeamId)
|
||||
assert.Equal(t, 3, rankings[4].TeamId)
|
||||
assert.Equal(t, 2, rankings[5].TeamId)
|
||||
}
|
||||
}
|
||||
|
||||
// Sets up a schedule and results that touches on all possible variables.
|
||||
func setupMatchResultsForRankings(database *model.Database) {
|
||||
match1 := model.Match{Type: "qualification", DisplayName: "1", Red1: 1, Red2: 2, Red3: 3, Blue1: 4, Blue2: 5,
|
||||
Blue3: 6, Status: "complete"}
|
||||
database.CreateMatch(&match1)
|
||||
matchResult1 := model.BuildTestMatchResult(match1.Id, 1)
|
||||
matchResult1.RedCards = map[string]string{"2": "red"}
|
||||
database.CreateMatchResult(matchResult1)
|
||||
|
||||
match2 := model.Match{Type: "qualification", DisplayName: "2", Red1: 1, Red2: 3, Red3: 5, Blue1: 2, Blue2: 4,
|
||||
Blue3: 6, Status: "complete", Red2IsSurrogate: true, Blue3IsSurrogate: true}
|
||||
database.CreateMatch(&match2)
|
||||
matchResult2 := model.BuildTestMatchResult(match2.Id, 1)
|
||||
matchResult2.BlueScore = matchResult2.RedScore
|
||||
database.CreateMatchResult(matchResult2)
|
||||
|
||||
match3 := model.Match{Type: "qualification", DisplayName: "3", Red1: 6, Red2: 5, Red3: 4, Blue1: 3, Blue2: 2,
|
||||
Blue3: 1, Status: "complete", Red3IsSurrogate: true}
|
||||
database.CreateMatch(&match3)
|
||||
matchResult3 := model.BuildTestMatchResult(match3.Id, 1)
|
||||
database.CreateMatchResult(matchResult3)
|
||||
matchResult3 = model.NewMatchResult()
|
||||
matchResult3.MatchId = match3.Id
|
||||
matchResult3.PlayNumber = 2
|
||||
database.CreateMatchResult(matchResult3)
|
||||
|
||||
match4 := model.Match{Type: "practice", DisplayName: "1", Red1: 1, Red2: 2, Red3: 3, Blue1: 4, Blue2: 5,
|
||||
Blue3: 6, Status: "complete"}
|
||||
database.CreateMatch(&match4)
|
||||
matchResult4 := model.BuildTestMatchResult(match4.Id, 1)
|
||||
database.CreateMatchResult(matchResult4)
|
||||
|
||||
match5 := model.Match{Type: "elimination", DisplayName: "F-1", Red1: 1, Red2: 2, Red3: 3, Blue1: 4, Blue2: 5,
|
||||
Blue3: 6, Status: "complete"}
|
||||
database.CreateMatch(&match5)
|
||||
matchResult5 := model.BuildTestMatchResult(match5.Id, 1)
|
||||
database.CreateMatchResult(matchResult5)
|
||||
|
||||
match6 := model.Match{Type: "qualification", DisplayName: "4", Red1: 7, Red2: 8, Red3: 9, Blue1: 10, Blue2: 11,
|
||||
Blue3: 12, Status: ""}
|
||||
database.CreateMatch(&match6)
|
||||
matchResult6 := model.BuildTestMatchResult(match6.Id, 1)
|
||||
database.CreateMatchResult(matchResult6)
|
||||
}
|
||||
103
tournament/schedule.go
Normal file
103
tournament/schedule.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
//
|
||||
// Functions for creating practice and qualification match schedules.
|
||||
|
||||
package tournament
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"github.com/Team254/cheesy-arena/model"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var schedulesDir = "schedules"
|
||||
|
||||
const TeamsPerMatch = 6
|
||||
|
||||
type ScheduleBlock struct {
|
||||
StartTime time.Time
|
||||
NumMatches int
|
||||
MatchSpacingSec int
|
||||
}
|
||||
|
||||
// Creates a random schedule for the given parameters and returns it as a list of matches.
|
||||
func BuildRandomSchedule(teams []model.Team, scheduleBlocks []ScheduleBlock, matchType string) ([]model.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))
|
||||
|
||||
// Adjust the number of matches to remove any excess from non-perfect block scheduling.
|
||||
numMatches = int(math.Ceil(float64(numTeams) * float64(matchesPerTeam) / TeamsPerMatch))
|
||||
|
||||
file, err := os.Open(fmt.Sprintf("%s/%d_%d.csv", schedulesDir, numTeams, matchesPerTeam))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("No schedule template 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([]model.Match, numMatches)
|
||||
for i, anonMatch := range anonSchedule {
|
||||
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)
|
||||
}
|
||||
|
||||
// Fill in the match times.
|
||||
matchIndex := 0
|
||||
for _, block := range scheduleBlocks {
|
||||
for i := 0; i < block.NumMatches && matchIndex < numMatches; i++ {
|
||||
matches[matchIndex].Time = block.StartTime.Add(time.Duration(i*block.MatchSpacingSec) * time.Second)
|
||||
matchIndex++
|
||||
}
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// Returns the total number of matches that can be run within the given schedule blocks.
|
||||
func countMatches(scheduleBlocks []ScheduleBlock) int {
|
||||
numMatches := 0
|
||||
for _, block := range scheduleBlocks {
|
||||
numMatches += block.NumMatches
|
||||
}
|
||||
return numMatches
|
||||
}
|
||||
123
tournament/schedule_test.go
Normal file
123
tournament/schedule_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
|
||||
package tournament
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Team254/cheesy-arena/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
schedulesDir = "../schedules"
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestNonExistentSchedule(t *testing.T) {
|
||||
teams := make([]model.Team, 6)
|
||||
scheduleBlocks := []ScheduleBlock{{time.Unix(0, 0).UTC(), 2, 60}}
|
||||
_, err := BuildRandomSchedule(teams, scheduleBlocks, "test")
|
||||
expectedErr := "No schedule template exists for 6 teams and 2 matches"
|
||||
if assert.NotNil(t, err) {
|
||||
assert.Equal(t, expectedErr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMalformedSchedule(t *testing.T) {
|
||||
filename := fmt.Sprintf("%s/6_1.csv", schedulesDir)
|
||||
scheduleFile, _ := os.Create(filename)
|
||||
defer os.Remove(filename)
|
||||
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([]model.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 assert.NotNil(t, err) {
|
||||
assert.Equal(t, expectedErr, err.Error())
|
||||
}
|
||||
|
||||
os.Remove(filename)
|
||||
scheduleFile, _ = os.Create(filename)
|
||||
scheduleFile.WriteString("1,0,asdf,0,3,0,4,0,5,0,6,0\n")
|
||||
scheduleFile.Close()
|
||||
_, err = BuildRandomSchedule(teams, scheduleBlocks, "test")
|
||||
if assert.NotNil(t, err) {
|
||||
assert.Contains(t, err.Error(), "strconv.Atoi")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScheduleTeams(t *testing.T) {
|
||||
rand.Seed(0)
|
||||
|
||||
numTeams := 18
|
||||
teams := make([]model.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")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, model.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])
|
||||
assert.Equal(t, model.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])
|
||||
assert.Equal(t, model.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])
|
||||
assert.Equal(t, model.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])
|
||||
assert.Equal(t, model.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])
|
||||
assert.Equal(t, model.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])
|
||||
|
||||
// Check with excess room for matches in the schedule.
|
||||
scheduleBlocks = []ScheduleBlock{{time.Unix(0, 0).UTC(), 7, 60}}
|
||||
matches, err = BuildRandomSchedule(teams, scheduleBlocks, "test")
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestScheduleTiming(t *testing.T) {
|
||||
teams := make([]model.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")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, time.Unix(100, 0).UTC(), matches[0].Time)
|
||||
assert.Equal(t, time.Unix(775, 0).UTC(), matches[9].Time)
|
||||
assert.Equal(t, time.Unix(20000, 0).UTC(), matches[10].Time)
|
||||
assert.Equal(t, time.Unix(24000, 0).UTC(), matches[14].Time)
|
||||
assert.Equal(t, time.Unix(100000, 0).UTC(), matches[15].Time)
|
||||
assert.Equal(t, time.Unix(100406, 0).UTC(), matches[29].Time)
|
||||
}
|
||||
|
||||
func TestScheduleSurrogates(t *testing.T) {
|
||||
rand.Seed(0)
|
||||
|
||||
numTeams := 38
|
||||
teams := make([]model.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
tournament/test_helpers.go
Normal file
23
tournament/test_helpers.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2017 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
//
|
||||
// Helper methods for use in tests in this package and others.
|
||||
|
||||
package tournament
|
||||
|
||||
import (
|
||||
"github.com/Team254/cheesy-arena/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func CreateTestAlliances(database *model.Database, allianceCount int) {
|
||||
for i := 1; i <= allianceCount; i++ {
|
||||
database.CreateAllianceTeam(&model.AllianceTeam{0, i, 0, i})
|
||||
database.CreateAllianceTeam(&model.AllianceTeam{0, i, 1, i})
|
||||
database.CreateAllianceTeam(&model.AllianceTeam{0, i, 2, i})
|
||||
}
|
||||
}
|
||||
|
||||
func setupTestDb(t *testing.T) *model.Database {
|
||||
return model.SetupTestDb(t, "tournament", "..")
|
||||
}
|
||||
Reference in New Issue
Block a user