Refactor alliance storage to use a single object per alliance instead of one per alliance-team association.

This commit is contained in:
Patrick Fairbank
2022-07-31 12:08:43 -07:00
parent e83ec98408
commit 38518b0dd4
22 changed files with 411 additions and 451 deletions

View File

@@ -70,7 +70,7 @@ type Arena struct {
SavedMatchResult *model.MatchResult
SavedRankings game.Rankings
AllianceStationDisplayMode string
AllianceSelectionAlliances [][]model.AllianceTeam
AllianceSelectionAlliances []model.Alliance
LowerThird *model.LowerThird
ShowLowerThird bool
MuteMatchSounds bool

84
model/alliance.go Normal file
View File

@@ -0,0 +1,84 @@
// Copyright 2022 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Model and datastore CRUD methods for a playoff alliance.
package model
import "sort"
type Alliance struct {
Id int `db:"id,manual"`
TeamIds []int
Lineup [3]int
}
func (database *Database) CreateAlliance(alliance *Alliance) error {
return database.allianceTable.create(alliance)
}
func (database *Database) GetAllianceById(id int) (*Alliance, error) {
return database.allianceTable.getById(id)
}
func (database *Database) UpdateAlliance(alliance *Alliance) error {
return database.allianceTable.update(alliance)
}
func (database *Database) DeleteAlliance(id int) error {
return database.allianceTable.delete(id)
}
func (database *Database) TruncateAlliances() error {
return database.allianceTable.truncate()
}
func (database *Database) GetAllAlliances() ([]Alliance, error) {
alliances, err := database.allianceTable.getAll()
if err != nil {
return nil, err
}
sort.Slice(alliances, func(i, j int) bool {
return alliances[i].Id < alliances[j].Id
})
return alliances, nil
}
// Returns two arrays containing the IDs of any teams for the red and blue alliances, respectively, who are part of the
// elimination alliance but are not playing in the given match.
// If the given match isn't an elimination match, empty arrays are returned.
func (database *Database) GetOffFieldTeamIds(match *Match) ([]int, []int, error) {
redOffFieldTeams, err := database.getOffFieldTeamIdsForAlliance(
match.ElimRedAlliance, match.Red1, match.Red2, match.Red3,
)
if err != nil {
return nil, nil, err
}
blueOffFieldTeams, err := database.getOffFieldTeamIdsForAlliance(
match.ElimBlueAlliance, match.Blue1, match.Blue2, match.Blue3,
)
if err != nil {
return nil, nil, err
}
return redOffFieldTeams, blueOffFieldTeams, nil
}
func (database *Database) getOffFieldTeamIdsForAlliance(allianceId int, teamId1, teamId2, teamId3 int) ([]int, error) {
if allianceId == 0 {
return []int{}, nil
}
alliance, err := database.GetAllianceById(allianceId)
if err != nil {
return nil, err
}
offFieldTeamIds := []int{}
for _, allianceTeamId := range alliance.TeamIds {
if allianceTeamId != teamId1 && allianceTeamId != teamId2 && allianceTeamId != teamId3 {
offFieldTeamIds = append(offFieldTeamIds, allianceTeamId)
}
}
return offFieldTeamIds, nil
}

View File

@@ -1,113 +0,0 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Model and datastore CRUD methods for an alliance-team association.
package model
import "sort"
type AllianceTeam struct {
Id int `db:"id"`
AllianceId int
PickPosition int
TeamId int
}
func (database *Database) CreateAllianceTeam(allianceTeam *AllianceTeam) error {
return database.allianceTeamTable.create(allianceTeam)
}
func (database *Database) GetTeamsByAlliance(allianceId int) ([]AllianceTeam, error) {
allianceTeams, err := database.allianceTeamTable.getAll()
if err != nil {
return nil, err
}
sort.Slice(allianceTeams, func(i, j int) bool {
return allianceTeams[i].PickPosition < allianceTeams[j].PickPosition
})
var matchingAllianceTeams []AllianceTeam
for _, allianceTeam := range allianceTeams {
if allianceTeam.AllianceId == allianceId {
matchingAllianceTeams = append(matchingAllianceTeams, allianceTeam)
}
}
return matchingAllianceTeams, nil
}
func (database *Database) UpdateAllianceTeam(allianceTeam *AllianceTeam) error {
return database.allianceTeamTable.update(allianceTeam)
}
func (database *Database) DeleteAllianceTeam(id int) error {
return database.allianceTeamTable.delete(id)
}
func (database *Database) TruncateAllianceTeams() error {
return database.allianceTeamTable.truncate()
}
func (database *Database) GetAllAlliances() ([][]AllianceTeam, error) {
allianceTeams, err := database.allianceTeamTable.getAll()
if err != nil {
return nil, err
}
sort.Slice(allianceTeams, func(i, j int) bool {
if allianceTeams[i].AllianceId == allianceTeams[j].AllianceId {
return allianceTeams[i].PickPosition < allianceTeams[j].PickPosition
}
return allianceTeams[i].AllianceId < allianceTeams[j].AllianceId
})
alliances := make([][]AllianceTeam, 0)
// Format the sorted list of teams into a two-dimensional slice.
currentAllianceId := -1
for _, allianceTeam := range allianceTeams {
if allianceTeam.AllianceId != currentAllianceId {
currentAllianceId = allianceTeam.AllianceId
alliances = append(alliances, make([]AllianceTeam, 0))
}
alliances[len(alliances)-1] = append(alliances[len(alliances)-1], allianceTeam)
}
return alliances, nil
}
// Returns two arrays containing the IDs of any teams for the red and blue alliances, respectively, who are part of the
// elimination alliance but are not playing in the given match.
// If the given match isn't an elimination match, empty arrays are returned.
func (database *Database) GetOffFieldTeamIds(match *Match) ([]int, []int, error) {
redOffFieldTeams, err := database.getOffFieldTeamIdsForAlliance(
match.ElimRedAlliance, match.Red1, match.Red2, match.Red3,
)
if err != nil {
return nil, nil, err
}
blueOffFieldTeams, err := database.getOffFieldTeamIdsForAlliance(
match.ElimBlueAlliance, match.Blue1, match.Blue2, match.Blue3,
)
if err != nil {
return nil, nil, err
}
return redOffFieldTeams, blueOffFieldTeams, nil
}
func (database *Database) getOffFieldTeamIdsForAlliance(allianceId int, teamId1, teamId2, teamId3 int) ([]int, error) {
if allianceId == 0 {
return []int{}, nil
}
allianceTeams, err := database.GetTeamsByAlliance(allianceId)
if err != nil {
return nil, err
}
offFieldTeamIds := []int{}
for _, allianceTeam := range allianceTeams {
if allianceTeam.TeamId != teamId1 && allianceTeam.TeamId != teamId2 && allianceTeam.TeamId != teamId3 {
offFieldTeamIds = append(offFieldTeamIds, allianceTeam.TeamId)
}
}
return offFieldTeamIds, nil
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Copyright 2022 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package model
@@ -12,68 +12,43 @@ func TestGetNonexistentAlliance(t *testing.T) {
db := setupTestDb(t)
defer db.Close()
allianceTeams, err := db.GetTeamsByAlliance(1114)
alliance, err := db.GetAllianceById(1114)
assert.Nil(t, err)
assert.Empty(t, allianceTeams)
assert.Nil(t, alliance)
}
func TestAllianceTeamCrud(t *testing.T) {
func TestAllianceCrud(t *testing.T) {
db := setupTestDb(t)
defer db.Close()
allianceTeam := AllianceTeam{0, 1, 0, 254}
db.CreateAllianceTeam(&allianceTeam)
allianceTeams, err := db.GetTeamsByAlliance(1)
alliance := Alliance{Id: 3, TeamIds: []int{254, 1114, 296, 1503}, Lineup: [3]int{1114, 254, 296}}
assert.Nil(t, db.CreateAlliance(&alliance))
alliance2, err := db.GetAllianceById(3)
assert.Nil(t, err)
assert.Equal(t, 1, len(allianceTeams))
assert.Equal(t, allianceTeam, allianceTeams[0])
assert.Equal(t, alliance, *alliance2)
allianceTeam.TeamId = 1114
db.UpdateAllianceTeam(&allianceTeam)
allianceTeams, err = db.GetTeamsByAlliance(1)
alliance.TeamIds = append(alliance.TeamIds, 296)
assert.Nil(t, db.UpdateAlliance(&alliance))
alliance2, err = db.GetAllianceById(3)
assert.Nil(t, err)
assert.Equal(t, 1, len(allianceTeams))
assert.Equal(t, allianceTeam.TeamId, allianceTeams[0].TeamId)
assert.Equal(t, alliance, *alliance2)
db.DeleteAllianceTeam(allianceTeam.Id)
allianceTeams, err = db.GetTeamsByAlliance(1)
assert.Nil(t, db.DeleteAlliance(alliance.Id))
alliance2, err = db.GetAllianceById(3)
assert.Nil(t, err)
assert.Empty(t, allianceTeams)
}
func TestGetTeamsByAlliance(t *testing.T) {
db := setupTestDb(t)
defer db.Close()
BuildTestAlliances(db)
allianceTeams, err := db.GetTeamsByAlliance(1)
assert.Nil(t, err)
if assert.Equal(t, 5, len(allianceTeams)) {
assert.Equal(t, 254, allianceTeams[0].TeamId)
assert.Equal(t, 469, allianceTeams[1].TeamId)
assert.Equal(t, 2848, allianceTeams[2].TeamId)
assert.Equal(t, 74, allianceTeams[3].TeamId)
assert.Equal(t, 3175, allianceTeams[4].TeamId)
}
allianceTeams, err = db.GetTeamsByAlliance(2)
assert.Nil(t, err)
if assert.Equal(t, 3, len(allianceTeams)) {
assert.Equal(t, 1718, allianceTeams[0].TeamId)
assert.Equal(t, 2451, allianceTeams[1].TeamId)
assert.Equal(t, 1619, allianceTeams[2].TeamId)
}
assert.Nil(t, alliance2)
}
func TestTruncateAllianceTeams(t *testing.T) {
db := setupTestDb(t)
defer db.Close()
allianceTeam := AllianceTeam{0, 1, 0, 254}
db.CreateAllianceTeam(&allianceTeam)
db.TruncateAllianceTeams()
allianceTeams, err := db.GetTeamsByAlliance(1)
alliance := Alliance{Id: 1, TeamIds: []int{148, 118, 125}, Lineup: [3]int{118, 148, 125}}
assert.Nil(t, db.CreateAlliance(&alliance))
assert.Nil(t, db.TruncateAlliances())
alliance2, err := db.GetAllianceById(1)
assert.Nil(t, err)
assert.Empty(t, allianceTeams)
assert.Nil(t, alliance2)
}
func TestGetAllAlliances(t *testing.T) {
@@ -88,18 +63,10 @@ func TestGetAllAlliances(t *testing.T) {
alliances, err = db.GetAllAlliances()
assert.Nil(t, err)
if assert.Equal(t, 2, len(alliances)) {
if assert.Equal(t, 5, len(alliances[0])) {
assert.Equal(t, 254, alliances[0][0].TeamId)
assert.Equal(t, 469, alliances[0][1].TeamId)
assert.Equal(t, 2848, alliances[0][2].TeamId)
assert.Equal(t, 74, alliances[0][3].TeamId)
assert.Equal(t, 3175, alliances[0][4].TeamId)
}
if assert.Equal(t, 3, len(alliances[1])) {
assert.Equal(t, 1718, alliances[1][0].TeamId)
assert.Equal(t, 2451, alliances[1][1].TeamId)
assert.Equal(t, 1619, alliances[1][2].TeamId)
}
assert.Equal(t, 1, alliances[0].Id)
assert.Equal(t, []int{254, 469, 2848, 74, 3175}, alliances[0].TeamIds)
assert.Equal(t, 2, alliances[1].Id)
assert.Equal(t, []int{1718, 2451, 1619}, alliances[1].TeamIds)
}
}

View File

@@ -23,7 +23,7 @@ var BaseDir = "." // Mutable for testing
type Database struct {
Path string
bolt *bbolt.DB
allianceTeamTable *table[AllianceTeam]
allianceTable *table[Alliance]
awardTable *table[Award]
eventSettingsTable *table[EventSettings]
lowerThirdTable *table[LowerThird]
@@ -46,7 +46,7 @@ func OpenDatabase(filename string) (*Database, error) {
}
// Register tables.
if database.allianceTeamTable, err = newTable[AllianceTeam](&database); err != nil {
if database.allianceTable, err = newTable[Alliance](&database); err != nil {
return nil, err
}
if database.awardTable, err = newTable[Award](&database); err != nil {

View File

@@ -31,12 +31,6 @@ func BuildTestMatchResult(matchId int, playNumber int) *MatchResult {
}
func BuildTestAlliances(database *Database) {
database.CreateAllianceTeam(&AllianceTeam{0, 2, 0, 1718})
database.CreateAllianceTeam(&AllianceTeam{0, 1, 3, 74})
database.CreateAllianceTeam(&AllianceTeam{0, 1, 1, 469})
database.CreateAllianceTeam(&AllianceTeam{0, 1, 0, 254})
database.CreateAllianceTeam(&AllianceTeam{0, 1, 2, 2848})
database.CreateAllianceTeam(&AllianceTeam{0, 2, 2, 1619})
database.CreateAllianceTeam(&AllianceTeam{0, 2, 1, 2451})
database.CreateAllianceTeam(&AllianceTeam{0, 1, 4, 3175})
database.CreateAlliance(&Alliance{Id: 2, TeamIds: []int{1718, 2451, 1619}, Lineup: [3]int{2451, 1718, 1619}})
database.CreateAlliance(&Alliance{Id: 1, TeamIds: []int{254, 469, 2848, 74, 3175}, Lineup: [3]int{469, 254, 2848}})
}

View File

@@ -367,8 +367,8 @@ func (client *TbaClient) PublishAlliances(database *model.Database) error {
// Build a JSON object of TBA-format alliances.
tbaAlliances := make([][]string, len(alliances))
for i, alliance := range alliances {
for _, team := range alliance {
tbaAlliances[i] = append(tbaAlliances[i], getTbaTeam(team.TeamId))
for _, allianceTeamId := range alliance.TeamIds {
tbaAlliances[i] = append(tbaAlliances[i], getTbaTeam(allianceTeamId))
}
}
jsonBody, err := json.Marshal(tbaAlliances)

View File

@@ -145,7 +145,7 @@ var handlePlaySound = function(sound) {
// Handles a websocket message to update the alliance selection screen.
var handleAllianceSelection = function(alliances) {
if (alliances && alliances.length > 0) {
var numColumns = alliances[0].length + 1;
var numColumns = alliances[0].TeamIds.length + 1;
$.each(alliances, function(k, v) {
v.Index = k + 1;
});

View File

@@ -55,7 +55,7 @@
<th>Captain</th>
<th>Pick 1</th>
<th>Pick 2</th>
{{if index .Alliances 0 | len | eq 4}}
{{if (index .Alliances 0).TeamIds | len | eq 4}}
<th>Pick 3</th>
{{end}}
</tr>
@@ -63,9 +63,9 @@
<tbody>
{{range $i, $alliance := .Alliances}}
<tr>
<td class="col-lg-2">{{(index $alliance 0).AllianceId}}</td>
{{range $j, $team := $alliance}}
{{if eq $team.TeamId 0}}
<td class="col-lg-2">{{add $i 1}}</td>
{{range $j, $allianceTeamId := $alliance.TeamIds}}
{{if eq $allianceTeamId 0}}
<td class="col-lg-2">
<input type="text" class="form-control input-sm" name="selection{{$i}}_{{$j}}" value=""
{{if and (eq $i $.NextRow) (eq $j $.NextCol)}}autofocus{{end}}
@@ -74,7 +74,7 @@
{{else}}
<td class="col-lg-2">
<input type="text" class="form-control input-sm" name="selection{{$i}}_{{$j}}"
value="{{$team.TeamId}}" oninput="$(this).parent().addClass('has-warning');" />
value="{{$allianceTeamId}}" oninput="$(this).parent().addClass('has-warning');" />
</td>
{{end}}
{{end}}

View File

@@ -128,8 +128,8 @@
{{"{{#each alliances}}"}}
<tr>
<td class="alliance-cell">{{"{{Index}}"}}</td>
{{"{{#each this}}"}}
<td class="selection-cell">{{"{{#if TeamId}}"}}{{"{{TeamId}}"}}{{"{{/if}}"}}</td>
{{"{{#each this.TeamIds}}"}}
<td class="selection-cell">{{"{{#if this}}"}}{{"{{this}}"}}{{"{{/if}}"}}</td>
{{"{{/each}}"}}
</tr>
{{"{{/each}}"}}

View File

@@ -89,16 +89,19 @@ func DeleteAward(database *model.Database, awardId int) error {
// Generates awards and lower thirds for the tournament winners and finalists.
func CreateOrUpdateWinnerAndFinalistAwards(database *model.Database, winnerAllianceId, finalistAllianceId int) error {
var winnerAllianceTeams, finalistAllianceTeams []model.AllianceTeam
var winnerAlliance, finalistAlliance *model.Alliance
var err error
if winnerAllianceTeams, err = database.GetTeamsByAlliance(winnerAllianceId); err != nil {
if winnerAlliance, err = database.GetAllianceById(winnerAllianceId); err != nil {
return err
}
if finalistAllianceTeams, err = database.GetTeamsByAlliance(finalistAllianceId); err != nil {
if finalistAlliance, err = database.GetAllianceById(finalistAllianceId); err != nil {
return err
}
if len(winnerAllianceTeams) == 0 || len(finalistAllianceTeams) == 0 {
return fmt.Errorf("Input alliances do not contain any teams.")
if winnerAlliance == nil || finalistAlliance == nil {
return fmt.Errorf("Winner and/or finalist alliances do not exist.")
}
if len(winnerAlliance.TeamIds) == 0 || len(finalistAlliance.TeamIds) == 0 {
return fmt.Errorf("Winner and/or finalist alliances do not contain teams.")
}
// Clear out any awards that may exist if the final match was scored more than once.
@@ -117,28 +120,34 @@ func CreateOrUpdateWinnerAndFinalistAwards(database *model.Database, winnerAllia
}
// Create the finalist awards first since they're usually presented first.
finalistAward := model.Award{AwardName: "Finalist", Type: model.FinalistAward,
TeamId: finalistAllianceTeams[0].TeamId}
finalistAward := model.Award{
AwardName: "Finalist",
Type: model.FinalistAward,
TeamId: finalistAlliance.TeamIds[0],
}
if err = CreateOrUpdateAward(database, &finalistAward, true); err != nil {
return err
}
for _, allianceTeam := range finalistAllianceTeams[1:] {
for _, allianceTeamId := range finalistAlliance.TeamIds[1:] {
finalistAward.Id = 0
finalistAward.TeamId = allianceTeam.TeamId
finalistAward.TeamId = allianceTeamId
if err = CreateOrUpdateAward(database, &finalistAward, false); err != nil {
return err
}
}
// Create the winner awards.
winnerAward := model.Award{AwardName: "Winner", Type: model.WinnerAward,
TeamId: winnerAllianceTeams[0].TeamId}
winnerAward := model.Award{
AwardName: "Winner",
Type: model.WinnerAward,
TeamId: winnerAlliance.TeamIds[0],
}
if err = CreateOrUpdateAward(database, &winnerAward, true); err != nil {
return err
}
for _, allianceTeam := range winnerAllianceTeams[1:] {
for _, allianceTeamId := range winnerAlliance.TeamIds[1:] {
winnerAward.Id = 0
winnerAward.TeamId = allianceTeam.TeamId
winnerAward.TeamId = allianceTeamId
if err = CreateOrUpdateAward(database, &winnerAward, false); err != nil {
return err
}

View File

@@ -91,56 +91,62 @@ func TestCreateOrUpdateAwardWithoutIntro(t *testing.T) {
func TestCreateOrUpdateWinnerAndFinalistAwards(t *testing.T) {
database := setupTestDb(t)
CreateTestAlliances(database, 2)
database.CreateTeam(&model.Team{Id: 1})
database.CreateTeam(&model.Team{Id: 10})
database.CreateTeam(&model.Team{Id: 100})
database.CreateTeam(&model.Team{Id: 2})
database.CreateTeam(&model.Team{Id: 20})
database.CreateTeam(&model.Team{Id: 200})
database.CreateTeam(&model.Team{Id: 101})
database.CreateTeam(&model.Team{Id: 102})
database.CreateTeam(&model.Team{Id: 103})
database.CreateTeam(&model.Team{Id: 104})
database.CreateTeam(&model.Team{Id: 201})
database.CreateTeam(&model.Team{Id: 202})
database.CreateTeam(&model.Team{Id: 203})
database.CreateTeam(&model.Team{Id: 204})
err := CreateOrUpdateWinnerAndFinalistAwards(database, 2, 1)
assert.Nil(t, err)
awards, _ := database.GetAllAwards()
if assert.Equal(t, 6, len(awards)) {
assert.Equal(t, model.Award{1, model.FinalistAward, "Finalist", 1, ""}, awards[0])
assert.Equal(t, model.Award{2, model.FinalistAward, "Finalist", 10, ""}, awards[1])
assert.Equal(t, model.Award{3, model.FinalistAward, "Finalist", 100, ""}, awards[2])
assert.Equal(t, model.Award{4, model.WinnerAward, "Winner", 2, ""}, awards[3])
assert.Equal(t, model.Award{5, model.WinnerAward, "Winner", 20, ""}, awards[4])
assert.Equal(t, model.Award{6, model.WinnerAward, "Winner", 200, ""}, awards[5])
if assert.Equal(t, 8, len(awards)) {
assert.Equal(t, model.Award{1, model.FinalistAward, "Finalist", 101, ""}, awards[0])
assert.Equal(t, model.Award{2, model.FinalistAward, "Finalist", 102, ""}, awards[1])
assert.Equal(t, model.Award{3, model.FinalistAward, "Finalist", 103, ""}, awards[2])
assert.Equal(t, model.Award{4, model.FinalistAward, "Finalist", 104, ""}, awards[3])
assert.Equal(t, model.Award{5, model.WinnerAward, "Winner", 201, ""}, awards[4])
assert.Equal(t, model.Award{6, model.WinnerAward, "Winner", 202, ""}, awards[5])
assert.Equal(t, model.Award{7, model.WinnerAward, "Winner", 203, ""}, awards[6])
assert.Equal(t, model.Award{8, model.WinnerAward, "Winner", 204, ""}, awards[7])
}
lowerThirds, _ := database.GetAllLowerThirds()
if assert.Equal(t, 8, len(lowerThirds)) {
if assert.Equal(t, 10, len(lowerThirds)) {
assert.Equal(t, "Finalist", lowerThirds[0].TopText)
assert.Equal(t, "", lowerThirds[0].BottomText)
assert.Equal(t, "Finalist", lowerThirds[1].TopText)
assert.Equal(t, "Team 1, ", lowerThirds[1].BottomText)
assert.Equal(t, "Winner", lowerThirds[4].TopText)
assert.Equal(t, "", lowerThirds[4].BottomText)
assert.Equal(t, "Team 101, ", lowerThirds[1].BottomText)
assert.Equal(t, "Winner", lowerThirds[5].TopText)
assert.Equal(t, "Team 2, ", lowerThirds[5].BottomText)
assert.Equal(t, "", lowerThirds[5].BottomText)
assert.Equal(t, "Winner", lowerThirds[6].TopText)
assert.Equal(t, "Team 201, ", lowerThirds[6].BottomText)
}
err = CreateOrUpdateWinnerAndFinalistAwards(database, 1, 2)
assert.Nil(t, err)
awards, _ = database.GetAllAwards()
if assert.Equal(t, 6, len(awards)) {
assert.Equal(t, model.Award{7, model.FinalistAward, "Finalist", 2, ""}, awards[0])
assert.Equal(t, model.Award{8, model.FinalistAward, "Finalist", 20, ""}, awards[1])
assert.Equal(t, model.Award{9, model.FinalistAward, "Finalist", 200, ""}, awards[2])
assert.Equal(t, model.Award{10, model.WinnerAward, "Winner", 1, ""}, awards[3])
assert.Equal(t, model.Award{11, model.WinnerAward, "Winner", 10, ""}, awards[4])
assert.Equal(t, model.Award{12, model.WinnerAward, "Winner", 100, ""}, awards[5])
if assert.Equal(t, 8, len(awards)) {
assert.Equal(t, model.Award{9, model.FinalistAward, "Finalist", 201, ""}, awards[0])
assert.Equal(t, model.Award{10, model.FinalistAward, "Finalist", 202, ""}, awards[1])
assert.Equal(t, model.Award{11, model.FinalistAward, "Finalist", 203, ""}, awards[2])
assert.Equal(t, model.Award{12, model.FinalistAward, "Finalist", 204, ""}, awards[3])
assert.Equal(t, model.Award{13, model.WinnerAward, "Winner", 101, ""}, awards[4])
assert.Equal(t, model.Award{14, model.WinnerAward, "Winner", 102, ""}, awards[5])
assert.Equal(t, model.Award{15, model.WinnerAward, "Winner", 103, ""}, awards[6])
assert.Equal(t, model.Award{16, model.WinnerAward, "Winner", 104, ""}, awards[7])
}
lowerThirds, _ = database.GetAllLowerThirds()
if assert.Equal(t, 8, len(lowerThirds)) {
if assert.Equal(t, 10, len(lowerThirds)) {
assert.Equal(t, "Finalist", lowerThirds[0].TopText)
assert.Equal(t, "", lowerThirds[0].BottomText)
assert.Equal(t, "Finalist", lowerThirds[1].TopText)
assert.Equal(t, "Team 2, ", lowerThirds[1].BottomText)
assert.Equal(t, "Winner", lowerThirds[4].TopText)
assert.Equal(t, "", lowerThirds[4].BottomText)
assert.Equal(t, "Team 201, ", lowerThirds[1].BottomText)
assert.Equal(t, "Winner", lowerThirds[5].TopText)
assert.Equal(t, "Team 1, ", lowerThirds[5].BottomText)
assert.Equal(t, "", lowerThirds[5].BottomText)
assert.Equal(t, "Winner", lowerThirds[6].TopText)
assert.Equal(t, "Team 101, ", lowerThirds[6].BottomText)
}
}

View File

@@ -43,54 +43,60 @@ func UpdateEliminationSchedule(database *model.Database, startTime time.Time) (b
matchIndex++
}
return len(winner) > 0, err
return winner != nil, err
}
// Updates the alliance, if necessary, to include whoever played in the match, in case there was a substitute.
func UpdateAlliance(database *model.Database, matchTeamIds [3]int, allianceId int) error {
allianceTeams, err := database.GetTeamsByAlliance(allianceId)
alliance, err := database.GetAllianceById(allianceId)
if err != nil {
return err
}
changed := false
if matchTeamIds != alliance.Lineup {
alliance.Lineup = matchTeamIds
changed = true
}
for _, teamId := range matchTeamIds {
found := false
for _, allianceTeam := range allianceTeams {
if teamId == allianceTeam.TeamId {
for _, allianceTeamId := range alliance.TeamIds {
if teamId == allianceTeamId {
found = true
break
}
}
if !found {
newAllianceTeam := model.AllianceTeam{AllianceId: allianceId, PickPosition: len(allianceTeams),
TeamId: teamId}
allianceTeams = append(allianceTeams, newAllianceTeam)
if err := database.CreateAllianceTeam(&newAllianceTeam); err != nil {
return err
}
alliance.TeamIds = append(alliance.TeamIds, teamId)
changed = true
}
}
if changed {
return database.UpdateAlliance(alliance)
}
return nil
}
// 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) {
func buildEliminationMatchSet(
database *model.Database, round int, group int, numAlliances int,
) (*model.Alliance, error) {
if numAlliances < 2 {
return []model.AllianceTeam{}, fmt.Errorf("Must have at least 2 alliances")
return nil, 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)
return nil, 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 redAlliance, blueAlliance *model.Alliance
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
@@ -102,47 +108,37 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
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)
redAlliance, err = database.GetAllianceById(redAllianceNumber)
if err != nil {
return []model.AllianceTeam{}, err
}
if len(redAlliance) >= 3 {
// Swap the teams around to match the positions dictated by the rules.
redAlliance[0], redAlliance[1], redAlliance[2] = redAlliance[1], redAlliance[0], redAlliance[2]
return nil, 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)
blueAlliance, err = database.GetAllianceById(blueAllianceNumber)
if err != nil {
return []model.AllianceTeam{}, err
}
if len(blueAlliance) >= 3 {
// Swap the teams around to match the positions dictated by the rules.
blueAlliance[0], blueAlliance[1], blueAlliance[2] = blueAlliance[1], blueAlliance[0], blueAlliance[2]
return nil, err
}
}
}
// If the alliances aren't known yet, get them from one round down in the bracket.
if len(redAlliance) == 0 {
if redAlliance == nil {
redAlliance, err = buildEliminationMatchSet(database, round*2, group*2-1, numAlliances)
if err != nil {
return []model.AllianceTeam{}, err
return nil, err
}
}
if len(blueAlliance) == 0 {
if blueAlliance == nil {
blueAlliance, err = buildEliminationMatchSet(database, round*2, group*2, numAlliances)
if err != nil {
return []model.AllianceTeam{}, err
return nil, 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
if redAlliance == nil && blueAlliance == nil {
return nil, nil
}
// Check if the match set exists already and if it has been won.
@@ -150,24 +146,24 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
var ties []model.Match
matches, err := database.GetMatchesByElimRoundGroup(round, group)
if err != nil {
return []model.AllianceTeam{}, err
return nil, err
}
var unplayedMatches []model.Match
for _, match := range matches {
if !match.IsComplete() {
// Update the teams in the match if they are not yet set or are incorrect.
if len(redAlliance) != 0 && !(match.Red1 == redAlliance[0].TeamId && match.Red2 == redAlliance[1].TeamId &&
match.Red3 == redAlliance[2].TeamId) {
if redAlliance != nil && !(match.Red1 == redAlliance.Lineup[0] && match.Red2 == redAlliance.Lineup[1] &&
match.Red3 == redAlliance.Lineup[2]) {
positionRedTeams(&match, redAlliance)
match.ElimRedAlliance = redAlliance[0].AllianceId
match.ElimRedAlliance = redAlliance.Id
if err = database.UpdateMatch(&match); err != nil {
return nil, err
}
}
if len(blueAlliance) != 0 && !(match.Blue1 == blueAlliance[0].TeamId &&
match.Blue2 == blueAlliance[1].TeamId && match.Blue3 == blueAlliance[2].TeamId) {
if blueAlliance != nil && !(match.Blue1 == blueAlliance.Lineup[0] &&
match.Blue2 == blueAlliance.Lineup[1] && match.Blue3 == blueAlliance.Lineup[2]) {
positionBlueTeams(&match, blueAlliance)
match.ElimBlueAlliance = blueAlliance[0].AllianceId
match.ElimBlueAlliance = blueAlliance.Id
if err = database.UpdateMatch(&match); err != nil {
return nil, err
}
@@ -178,16 +174,6 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
continue
}
// Reorder the teams based on the last complete match, so that new and unplayed matches use the same positions.
err = reorderTeams(match.Red1, match.Red2, match.Red3, redAlliance)
if err != nil {
return []model.AllianceTeam{}, err
}
err = reorderTeams(match.Blue1, match.Blue2, match.Blue3, blueAlliance)
if err != nil {
return []model.AllianceTeam{}, err
}
// Check who won.
switch match.Status {
case model.RedWonMatch:
@@ -197,8 +183,7 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
case model.TieMatch:
ties = append(ties, match)
default:
return []model.AllianceTeam{}, fmt.Errorf("Completed match %d has invalid winner '%s'", match.Id,
match.Status)
return nil, fmt.Errorf("Completed match %d has invalid winner '%s'", match.Id, match.Status)
}
}
@@ -207,7 +192,7 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
for _, match := range unplayedMatches {
err = database.DeleteMatch(match.Id)
if err != nil {
return []model.AllianceTeam{}, err
return nil, err
}
}
@@ -222,32 +207,26 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
// 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 {
if redAlliance != nil && len(redAlliance.TeamIds) < 3 || blueAlliance != nil && len(blueAlliance.TeamIds) < 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")
return nil, 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
return nil, err
}
}
if len(matches) < 2 {
err = database.CreateMatch(createMatch(roundName, round, group, 2, redAlliance, blueAlliance))
if err != nil {
return []model.AllianceTeam{}, err
return nil, err
}
}
if len(matches) < 3 {
err = database.CreateMatch(createMatch(roundName, round, group, 3, redAlliance, blueAlliance))
if err != nil {
return []model.AllianceTeam{}, err
return nil, err
}
}
}
@@ -258,54 +237,51 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
err = database.CreateMatch(createMatch(roundName, round, group, len(matches)+index+1, redAlliance,
blueAlliance))
if err != nil {
return []model.AllianceTeam{}, err
return nil, err
}
}
}
return []model.AllianceTeam{}, nil
return nil, 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,
blueAlliance []model.AllianceTeam) *model.Match {
match := model.Match{Type: "elimination", DisplayName: fmt.Sprintf("%s-%d", roundName, instance),
ElimRound: round, ElimGroup: group, ElimInstance: instance, ElimRedAlliance: redAlliance[0].AllianceId,
ElimBlueAlliance: blueAlliance[0].AllianceId}
positionRedTeams(&match, redAlliance)
positionBlueTeams(&match, blueAlliance)
func createMatch(
roundName string,
round int,
group int,
instance int,
redAlliance,
blueAlliance *model.Alliance,
) *model.Match {
match := model.Match{
Type: "elimination",
DisplayName: fmt.Sprintf("%s-%d", roundName, instance),
ElimRound: round,
ElimGroup: group,
ElimInstance: instance,
}
if redAlliance != nil {
match.ElimRedAlliance = redAlliance.Id
positionRedTeams(&match, redAlliance)
}
if blueAlliance != nil {
match.ElimBlueAlliance = blueAlliance.Id
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) {
match.Red1 = alliance[0].TeamId
match.Red2 = alliance[1].TeamId
match.Red3 = alliance[2].TeamId
// Assigns the lineup from the alliance into the red team slots for the match.
func positionRedTeams(match *model.Match, alliance *model.Alliance) {
match.Red1 = alliance.Lineup[0]
match.Red2 = alliance.Lineup[1]
match.Red3 = alliance.Lineup[2]
}
// Assigns the first three teams from the alliance into the blue team slots for the match.
func positionBlueTeams(match *model.Match, alliance []model.AllianceTeam) {
match.Blue1 = alliance[0].TeamId
match.Blue2 = alliance[1].TeamId
match.Blue3 = alliance[2].TeamId
}
// Swaps the order of teams in the alliance to match the match positioning.
func reorderTeams(team1, team2, team3 int, alliance []model.AllianceTeam) error {
for i, team := range []int{team1, team2, team3} {
found := false
for j, oldTeam := range alliance {
if team == oldTeam.TeamId {
alliance[i], alliance[j] = alliance[j], alliance[i]
found = true
break
}
}
if !found {
return fmt.Errorf("Team %d not found in alliance %v", team, alliance)
}
}
return nil
// Assigns the lineup from the alliance into the blue team slots for the match.
func positionBlueTeams(match *model.Match, alliance *model.Alliance) {
match.Blue1 = alliance.Lineup[0]
match.Blue2 = alliance.Lineup[1]
match.Blue3 = alliance.Lineup[2]
}

View File

@@ -23,7 +23,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[1], "F-2", 1, 2)
assertMatch(t, matches[2], "F-3", 1, 2)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 3)
@@ -39,7 +39,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[4], "F-2", 1, 0)
assertMatch(t, matches[5], "F-3", 1, 0)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 4)
@@ -55,7 +55,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[4], "SF1-3", 1, 4)
assertMatch(t, matches[5], "SF2-3", 2, 3)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 5)
@@ -74,7 +74,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[7], "SF1-3", 1, 0)
assertMatch(t, matches[8], "SF2-3", 2, 3)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 6)
@@ -96,7 +96,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[10], "SF1-3", 1, 0)
assertMatch(t, matches[11], "SF2-3", 2, 0)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 7)
@@ -118,7 +118,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[10], "SF1-2", 1, 0)
assertMatch(t, matches[11], "SF1-3", 1, 0)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 8)
@@ -140,7 +140,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[10], "QF3-3", 2, 7)
assertMatch(t, matches[11], "QF4-3", 3, 6)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 9)
@@ -165,7 +165,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[13], "QF3-3", 2, 7)
assertMatch(t, matches[14], "QF4-3", 3, 6)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 10)
@@ -193,7 +193,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[16], "QF3-3", 2, 0)
assertMatch(t, matches[17], "QF4-3", 3, 6)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 11)
@@ -224,7 +224,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[19], "QF3-3", 2, 0)
assertMatch(t, matches[20], "QF4-3", 3, 0)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 12)
@@ -258,7 +258,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[22], "QF3-3", 2, 0)
assertMatch(t, matches[23], "QF4-3", 3, 0)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 13)
@@ -292,7 +292,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[22], "QF3-3", 2, 0)
assertMatch(t, matches[23], "QF4-3", 3, 0)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 14)
@@ -326,7 +326,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[22], "QF1-3", 1, 0)
assertMatch(t, matches[23], "QF3-3", 2, 0)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 15)
@@ -360,7 +360,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[22], "QF1-2", 1, 0)
assertMatch(t, matches[23], "QF1-3", 1, 0)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
CreateTestAlliances(database, 16)
@@ -394,7 +394,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
assertMatch(t, matches[22], "EF7-3", 3, 14)
assertMatch(t, matches[23], "EF8-3", 6, 11)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
}
@@ -406,24 +406,22 @@ func TestEliminationScheduleErrors(t *testing.T) {
if assert.NotNil(t, err) {
assert.Equal(t, "Must have at least 2 alliances", err.Error())
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
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.TruncateAlliances()
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})
database.CreateAlliance(&model.Alliance{Id: 1, TeamIds: []int{1, 2}})
database.CreateAlliance(&model.Alliance{Id: 2, TeamIds: []int{3, 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()
database.TruncateAlliances()
}
func TestEliminationSchedulePopulatePartialMatch(t *testing.T) {
@@ -443,7 +441,7 @@ func TestEliminationSchedulePopulatePartialMatch(t *testing.T) {
assertMatch(t, matches[3], "F-2", 1, 3)
assertMatch(t, matches[4], "F-3", 1, 3)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
database.TruncateMatchResults()
@@ -472,7 +470,7 @@ func TestEliminationSchedulePopulatePartialMatch(t *testing.T) {
assertMatch(t, matches[5], "F-2", 1, 2)
assertMatch(t, matches[6], "F-3", 1, 2)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
database.TruncateMatchResults()
}
@@ -533,7 +531,7 @@ func TestEliminationScheduleDetermineWinner(t *testing.T) {
}
matches, _ = database.GetMatchesByType("elimination")
assert.Equal(t, 3, len(matches))
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
database.TruncateMatchResults()
@@ -568,7 +566,7 @@ func TestEliminationScheduleDetermineWinner(t *testing.T) {
if assert.Nil(t, err) {
assert.True(t, won)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
database.TruncateMatchResults()
@@ -600,7 +598,7 @@ func TestEliminationScheduleDetermineWinner(t *testing.T) {
if assert.Nil(t, err) {
assert.True(t, won)
}
database.TruncateAllianceTeams()
database.TruncateAlliances()
database.TruncateMatches()
database.TruncateMatchResults()
@@ -737,20 +735,22 @@ func TestEliminationScheduleTeamPositions(t *testing.T) {
matches, _ := database.GetMatchesByType("elimination")
match1 := matches[0]
match2 := matches[1]
assert.Equal(t, 10, match1.Red1)
assert.Equal(t, 1, match1.Red2)
assert.Equal(t, 100, match1.Red3)
assert.Equal(t, 30, match2.Blue1)
assert.Equal(t, 3, match2.Blue2)
assert.Equal(t, 300, match2.Blue3)
assert.Equal(t, 102, match1.Red1)
assert.Equal(t, 101, match1.Red2)
assert.Equal(t, 103, match1.Red3)
assert.Equal(t, 302, match2.Blue1)
assert.Equal(t, 301, match2.Blue2)
assert.Equal(t, 303, match2.Blue3)
// Shuffle the team positions and check that the subsequent matches in the same round have the same ones.
match1.Red1, match1.Red2 = match1.Red2, match1.Red1
match2.Blue1, match2.Blue3 = match2.Blue3, match2.Blue1
match1.Red1, match1.Red2 = match1.Red2, 104
match2.Blue1, match2.Blue3 = 305, match2.Blue1
database.UpdateMatch(&match1)
database.UpdateMatch(&match2)
scoreMatch(database, "SF1-1", model.RedWonMatch)
scoreMatch(database, "SF2-1", model.BlueWonMatch)
assert.Nil(t, UpdateAlliance(database, [3]int{match1.Red1, match1.Red2, match1.Red3}, 1))
assert.Nil(t, UpdateAlliance(database, [3]int{match2.Blue1, match2.Blue2, match2.Blue3}, 3))
UpdateEliminationSchedule(database, time.Unix(1000, 0))
matches, _ = database.GetMatchesByType("elimination")
if assert.Equal(t, 6, len(matches)) {
@@ -783,10 +783,28 @@ func TestEliminationScheduleTeamPositions(t *testing.T) {
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.Red2)
assert.Equal(t, redAlliance, match.ElimRedAlliance)
assert.Equal(t, blueAlliance, match.Blue2)
assert.Equal(t, blueAlliance, match.ElimBlueAlliance)
if redAlliance > 0 {
assert.Equal(t, 100*redAlliance+2, match.Red1)
assert.Equal(t, 100*redAlliance+1, match.Red2)
assert.Equal(t, 100*redAlliance+3, match.Red3)
} else {
assert.Equal(t, 0, match.Red1)
assert.Equal(t, 0, match.Red2)
assert.Equal(t, 0, match.Red3)
}
if blueAlliance > 0 {
assert.Equal(t, 100*blueAlliance+2, match.Blue1)
assert.Equal(t, 100*blueAlliance+1, match.Blue2)
assert.Equal(t, 100*blueAlliance+3, match.Blue3)
} else {
assert.Equal(t, 0, match.Blue1)
assert.Equal(t, 0, match.Blue2)
assert.Equal(t, 0, match.Blue3)
}
}
func scoreMatch(database *model.Database, displayName string, winner model.MatchStatus) {

View File

@@ -12,9 +12,12 @@ import (
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, 10 * i})
database.CreateAllianceTeam(&model.AllianceTeam{0, i, 2, 100 * i})
alliance := model.Alliance{
Id: i,
TeamIds: []int{100*i + 1, 100*i + 2, 100*i + 3, 100*i + 4},
Lineup: [3]int{100*i + 2, 100*i + 1, 100*i + 3},
}
database.CreateAlliance(&alliance)
}
}

View File

@@ -51,10 +51,10 @@ func (web *Web) allianceSelectionPostHandler(w http.ResponseWriter, r *http.Requ
// Iterate through all selections and update the alliances.
for i, alliance := range web.arena.AllianceSelectionAlliances {
for j := range alliance {
for j := range alliance.TeamIds {
teamString := r.PostFormValue(fmt.Sprintf("selection%d_%d", i, j))
if teamString == "" {
web.arena.AllianceSelectionAlliances[i][j].TeamId = 0
web.arena.AllianceSelectionAlliances[i].TeamIds[j] = 0
} else {
teamId, err := strconv.Atoi(teamString)
if err != nil {
@@ -71,7 +71,7 @@ func (web *Web) allianceSelectionPostHandler(w http.ResponseWriter, r *http.Requ
}
found = true
team.Picked = true
web.arena.AllianceSelectionAlliances[i][j].TeamId = teamId
web.arena.AllianceSelectionAlliances[i].TeamIds[j] = teamId
break
}
}
@@ -104,16 +104,14 @@ func (web *Web) allianceSelectionStartHandler(w http.ResponseWriter, r *http.Req
}
// Create a blank alliance set matching the event configuration.
web.arena.AllianceSelectionAlliances = make([][]model.AllianceTeam, web.arena.EventSettings.NumElimAlliances)
web.arena.AllianceSelectionAlliances = make([]model.Alliance, web.arena.EventSettings.NumElimAlliances)
teamsPerAlliance := 3
if web.arena.EventSettings.SelectionRound3Order != "" {
teamsPerAlliance = 4
}
for i := 0; i < web.arena.EventSettings.NumElimAlliances; i++ {
web.arena.AllianceSelectionAlliances[i] = make([]model.AllianceTeam, teamsPerAlliance)
for j := 0; j < teamsPerAlliance; j++ {
web.arena.AllianceSelectionAlliances[i][j] = model.AllianceTeam{AllianceId: i + 1, PickPosition: j}
}
web.arena.AllianceSelectionAlliances[i].Id = i + 1
web.arena.AllianceSelectionAlliances[i].TeamIds = make([]int, teamsPerAlliance)
}
// Populate the ranked list of teams.
@@ -156,12 +154,12 @@ func (web *Web) allianceSelectionResetHandler(w http.ResponseWriter, r *http.Req
}
// Delete the saved alliances.
if err = web.arena.Database.TruncateAllianceTeams(); err != nil {
if err = web.arena.Database.TruncateAlliances(); err != nil {
handleWebErr(w, err)
return
}
web.arena.AllianceSelectionAlliances = [][]model.AllianceTeam{}
web.arena.AllianceSelectionAlliances = []model.Alliance{}
cachedRankedTeams = []*RankedTeam{}
web.arena.AllianceSelectionNotifier.Notify()
http.Redirect(w, r, "/alliance_selection", 303)
@@ -187,8 +185,8 @@ func (web *Web) allianceSelectionFinalizeHandler(w http.ResponseWriter, r *http.
// Check that all spots are filled.
for _, alliance := range web.arena.AllianceSelectionAlliances {
for _, team := range alliance {
if team.TeamId <= 0 {
for _, allianceTeamId := range alliance.TeamIds {
if allianceTeamId <= 0 {
web.renderAllianceSelection(w, r, "Can't finalize alliance selection until all spots have been filled.")
return
}
@@ -197,12 +195,16 @@ func (web *Web) allianceSelectionFinalizeHandler(w http.ResponseWriter, r *http.
// Save alliances to the database.
for _, alliance := range web.arena.AllianceSelectionAlliances {
for _, team := range alliance {
err := web.arena.Database.CreateAllianceTeam(&team)
if err != nil {
handleWebErr(w, err)
return
}
// Populate the initial lineup according to the tournament rules (alliance captain in the middle, first pick on
// the left, second pick on the right).
alliance.Lineup[0] = alliance.TeamIds[1]
alliance.Lineup[1] = alliance.TeamIds[0]
alliance.Lineup[2] = alliance.TeamIds[2]
err := web.arena.Database.CreateAlliance(&alliance)
if err != nil {
handleWebErr(w, err)
return
}
}
@@ -271,7 +273,7 @@ func (web *Web) renderAllianceSelection(w http.ResponseWriter, r *http.Request,
nextRow, nextCol := web.determineNextCell()
data := struct {
*model.EventSettings
Alliances [][]model.AllianceTeam
Alliances []model.Alliance
RankedTeams []*RankedTeam
NextRow int
NextCol int
@@ -311,10 +313,10 @@ func (web *Web) canResetAllianceSelection() bool {
func (web *Web) determineNextCell() (int, int) {
// Check the first two columns.
for i, alliance := range web.arena.AllianceSelectionAlliances {
if alliance[0].TeamId == 0 {
if alliance.TeamIds[0] == 0 {
return i, 0
}
if alliance[1].TeamId == 0 {
if alliance.TeamIds[1] == 0 {
return i, 1
}
}
@@ -322,13 +324,13 @@ func (web *Web) determineNextCell() (int, int) {
// Check the third column.
if web.arena.EventSettings.SelectionRound2Order == "F" {
for i, alliance := range web.arena.AllianceSelectionAlliances {
if alliance[2].TeamId == 0 {
if alliance.TeamIds[2] == 0 {
return i, 2
}
}
} else {
for i := len(web.arena.AllianceSelectionAlliances) - 1; i >= 0; i-- {
if web.arena.AllianceSelectionAlliances[i][2].TeamId == 0 {
if web.arena.AllianceSelectionAlliances[i].TeamIds[2] == 0 {
return i, 2
}
}
@@ -337,13 +339,13 @@ func (web *Web) determineNextCell() (int, int) {
// Check the fourth column.
if web.arena.EventSettings.SelectionRound3Order == "F" {
for i, alliance := range web.arena.AllianceSelectionAlliances {
if alliance[3].TeamId == 0 {
if alliance.TeamIds[3] == 0 {
return i, 3
}
}
} else if web.arena.EventSettings.SelectionRound3Order == "L" {
for i := len(web.arena.AllianceSelectionAlliances) - 1; i >= 0; i-- {
if web.arena.AllianceSelectionAlliances[i][3].TeamId == 0 {
if web.arena.AllianceSelectionAlliances[i].TeamIds[3] == 0 {
return i, 3
}
}

View File

@@ -13,7 +13,7 @@ import (
func TestAllianceSelection(t *testing.T) {
web := setupTestWeb(t)
web.arena.AllianceSelectionAlliances = [][]model.AllianceTeam{}
web.arena.AllianceSelectionAlliances = []model.Alliance{}
cachedRankedTeams = []*RankedTeam{}
web.arena.EventSettings.NumElimAlliances = 15
web.arena.EventSettings.SelectionRound3Order = "L"
@@ -31,7 +31,7 @@ func TestAllianceSelection(t *testing.T) {
recorder = web.postHttpResponse("/alliance_selection/start", "")
assert.Equal(t, 303, recorder.Code)
if assert.Equal(t, 15, len(web.arena.AllianceSelectionAlliances)) {
assert.Equal(t, 4, len(web.arena.AllianceSelectionAlliances[0]))
assert.Equal(t, 4, len(web.arena.AllianceSelectionAlliances[0].TeamIds))
}
recorder = web.getHttpResponse("/alliance_selection")
assert.Contains(t, recorder.Body.String(), "Captain")
@@ -47,13 +47,13 @@ func TestAllianceSelection(t *testing.T) {
recorder = web.postHttpResponse("/alliance_selection/start", "")
assert.Equal(t, 303, recorder.Code)
if assert.Equal(t, 3, len(web.arena.AllianceSelectionAlliances)) {
assert.Equal(t, 3, len(web.arena.AllianceSelectionAlliances[0]))
assert.Equal(t, 3, len(web.arena.AllianceSelectionAlliances[0].TeamIds))
}
// Update one team at a time.
recorder = web.postHttpResponse("/alliance_selection", "selection0_0=110")
assert.Equal(t, 303, recorder.Code)
assert.Equal(t, 110, web.arena.AllianceSelectionAlliances[0][0].TeamId)
assert.Equal(t, 110, web.arena.AllianceSelectionAlliances[0].TeamIds[0])
recorder = web.getHttpResponse("/alliance_selection")
assert.Contains(t, recorder.Body.String(), "\"110\"")
assert.NotContains(t, recorder.Body.String(), ">110<")
@@ -61,9 +61,9 @@ func TestAllianceSelection(t *testing.T) {
// Update multiple teams at a time.
recorder = web.postHttpResponse("/alliance_selection", "selection0_0=101&selection0_1=102&selection1_0=103")
assert.Equal(t, 303, recorder.Code)
assert.Equal(t, 101, web.arena.AllianceSelectionAlliances[0][0].TeamId)
assert.Equal(t, 102, web.arena.AllianceSelectionAlliances[0][1].TeamId)
assert.Equal(t, 103, web.arena.AllianceSelectionAlliances[1][0].TeamId)
assert.Equal(t, 101, web.arena.AllianceSelectionAlliances[0].TeamIds[0])
assert.Equal(t, 102, web.arena.AllianceSelectionAlliances[0].TeamIds[1])
assert.Equal(t, 103, web.arena.AllianceSelectionAlliances[1].TeamIds[0])
recorder = web.getHttpResponse("/alliance_selection")
assert.Contains(t, recorder.Body.String(), ">110<")
@@ -81,9 +81,14 @@ func TestAllianceSelection(t *testing.T) {
alliances, err := web.arena.Database.GetAllAlliances()
assert.Nil(t, err)
if assert.Equal(t, 3, len(alliances)) {
assert.Equal(t, 101, alliances[0][0].TeamId)
assert.Equal(t, 105, alliances[1][1].TeamId)
assert.Equal(t, 109, alliances[2][2].TeamId)
assert.Equal(t, 101, alliances[0].TeamIds[0])
assert.Equal(t, 105, alliances[1].TeamIds[1])
assert.Equal(t, 109, alliances[2].TeamIds[2])
// Check that the initial lineup is populated correctly.
assert.Equal(t, 102, alliances[0].Lineup[0])
assert.Equal(t, 101, alliances[0].Lineup[1])
assert.Equal(t, 103, alliances[0].Lineup[2])
}
matches, err := web.arena.Database.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -93,7 +98,7 @@ func TestAllianceSelection(t *testing.T) {
func TestAllianceSelectionErrors(t *testing.T) {
web := setupTestWeb(t)
web.arena.AllianceSelectionAlliances = [][]model.AllianceTeam{}
web.arena.AllianceSelectionAlliances = []model.Alliance{}
cachedRankedTeams = []*RankedTeam{}
web.arena.EventSettings.NumElimAlliances = 2
for i := 1; i <= 6; i++ {
@@ -144,7 +149,7 @@ func TestAllianceSelectionErrors(t *testing.T) {
recorder = web.postHttpResponse("/alliance_selection", "selection0_0=asdf")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "already been finalized")
web.arena.AllianceSelectionAlliances = [][]model.AllianceTeam{}
web.arena.AllianceSelectionAlliances = []model.Alliance{}
cachedRankedTeams = []*RankedTeam{}
recorder = web.postHttpResponse("/alliance_selection/start", "")
assert.Equal(t, 200, recorder.Code)
@@ -154,7 +159,7 @@ func TestAllianceSelectionErrors(t *testing.T) {
func TestAllianceSelectionReset(t *testing.T) {
web := setupTestWeb(t)
web.arena.AllianceSelectionAlliances = [][]model.AllianceTeam{}
web.arena.AllianceSelectionAlliances = []model.Alliance{}
cachedRankedTeams = []*RankedTeam{}
web.arena.EventSettings.NumElimAlliances = 2
for i := 1; i <= 6; i++ {
@@ -210,7 +215,7 @@ func TestAllianceSelectionReset(t *testing.T) {
func TestAllianceSelectionAutofocus(t *testing.T) {
web := setupTestWeb(t)
web.arena.AllianceSelectionAlliances = [][]model.AllianceTeam{}
web.arena.AllianceSelectionAlliances = []model.Alliance{}
cachedRankedTeams = []*RankedTeam{}
web.arena.EventSettings.NumElimAlliances = 2
@@ -222,35 +227,35 @@ func TestAllianceSelectionAutofocus(t *testing.T) {
i, j := web.determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 0, j)
web.arena.AllianceSelectionAlliances[0][0].TeamId = 1
web.arena.AllianceSelectionAlliances[0].TeamIds[0] = 1
i, j = web.determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 1, j)
web.arena.AllianceSelectionAlliances[0][1].TeamId = 2
web.arena.AllianceSelectionAlliances[0].TeamIds[1] = 2
i, j = web.determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 0, j)
web.arena.AllianceSelectionAlliances[1][0].TeamId = 3
web.arena.AllianceSelectionAlliances[1].TeamIds[0] = 3
i, j = web.determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 1, j)
web.arena.AllianceSelectionAlliances[1][1].TeamId = 4
web.arena.AllianceSelectionAlliances[1].TeamIds[1] = 4
i, j = web.determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 2, j)
web.arena.AllianceSelectionAlliances[0][2].TeamId = 5
web.arena.AllianceSelectionAlliances[0].TeamIds[2] = 5
i, j = web.determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 2, j)
web.arena.AllianceSelectionAlliances[1][2].TeamId = 6
web.arena.AllianceSelectionAlliances[1].TeamIds[2] = 6
i, j = web.determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 3, j)
web.arena.AllianceSelectionAlliances[0][3].TeamId = 7
web.arena.AllianceSelectionAlliances[0].TeamIds[3] = 7
i, j = web.determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 3, j)
web.arena.AllianceSelectionAlliances[1][3].TeamId = 8
web.arena.AllianceSelectionAlliances[1].TeamIds[3] = 8
i, j = web.determineNextCell()
assert.Equal(t, -1, i)
assert.Equal(t, -1, j)
@@ -265,35 +270,35 @@ func TestAllianceSelectionAutofocus(t *testing.T) {
i, j = web.determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 0, j)
web.arena.AllianceSelectionAlliances[0][0].TeamId = 1
web.arena.AllianceSelectionAlliances[0].TeamIds[0] = 1
i, j = web.determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 1, j)
web.arena.AllianceSelectionAlliances[0][1].TeamId = 2
web.arena.AllianceSelectionAlliances[0].TeamIds[1] = 2
i, j = web.determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 0, j)
web.arena.AllianceSelectionAlliances[1][0].TeamId = 3
web.arena.AllianceSelectionAlliances[1].TeamIds[0] = 3
i, j = web.determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 1, j)
web.arena.AllianceSelectionAlliances[1][1].TeamId = 4
web.arena.AllianceSelectionAlliances[1].TeamIds[1] = 4
i, j = web.determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 2, j)
web.arena.AllianceSelectionAlliances[1][2].TeamId = 5
web.arena.AllianceSelectionAlliances[1].TeamIds[2] = 5
i, j = web.determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 2, j)
web.arena.AllianceSelectionAlliances[0][2].TeamId = 6
web.arena.AllianceSelectionAlliances[0].TeamIds[2] = 6
i, j = web.determineNextCell()
assert.Equal(t, 1, i)
assert.Equal(t, 3, j)
web.arena.AllianceSelectionAlliances[1][3].TeamId = 7
web.arena.AllianceSelectionAlliances[1].TeamIds[3] = 7
i, j = web.determineNextCell()
assert.Equal(t, 0, i)
assert.Equal(t, 3, j)
web.arena.AllianceSelectionAlliances[0][3].TeamId = 8
web.arena.AllianceSelectionAlliances[0].TeamIds[3] = 8
i, j = web.determineNextCell()
assert.Equal(t, -1, i)
assert.Equal(t, -1, j)

View File

@@ -109,21 +109,21 @@ func TestAlliancesApi(t *testing.T) {
recorder := web.getHttpResponse("/api/alliances")
assert.Equal(t, 200, recorder.Code)
assert.Equal(t, "application/json", recorder.HeaderMap["Content-Type"][0])
var alliances [][]model.AllianceTeam
var alliances []model.Alliance
err := json.Unmarshal([]byte(recorder.Body.String()), &alliances)
assert.Nil(t, err)
if assert.Equal(t, 2, len(alliances)) {
if assert.Equal(t, 5, len(alliances[0])) {
assert.Equal(t, 254, alliances[0][0].TeamId)
assert.Equal(t, 469, alliances[0][1].TeamId)
assert.Equal(t, 2848, alliances[0][2].TeamId)
assert.Equal(t, 74, alliances[0][3].TeamId)
assert.Equal(t, 3175, alliances[0][4].TeamId)
if assert.Equal(t, 5, len(alliances[0].TeamIds)) {
assert.Equal(t, 254, alliances[0].TeamIds[0])
assert.Equal(t, 469, alliances[0].TeamIds[1])
assert.Equal(t, 2848, alliances[0].TeamIds[2])
assert.Equal(t, 74, alliances[0].TeamIds[3])
assert.Equal(t, 3175, alliances[0].TeamIds[4])
}
if assert.Equal(t, 3, len(alliances[1])) {
assert.Equal(t, 1718, alliances[1][0].TeamId)
assert.Equal(t, 2451, alliances[1][1].TeamId)
assert.Equal(t, 1619, alliances[1][2].TeamId)
if assert.Equal(t, 3, len(alliances[1].TeamIds)) {
assert.Equal(t, 1718, alliances[1].TeamIds[0])
assert.Equal(t, 2451, alliances[1].TeamIds[1])
assert.Equal(t, 1619, alliances[1].TeamIds[2])
}
}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/Team254/cheesy-arena-lite/field"
"github.com/Team254/cheesy-arena-lite/game"
"github.com/Team254/cheesy-arena-lite/model"
"github.com/Team254/cheesy-arena-lite/tournament"
"github.com/Team254/cheesy-arena-lite/websocket"
gorillawebsocket "github.com/gorilla/websocket"
"github.com/mitchellh/mapstructure"
@@ -181,7 +182,11 @@ func TestCommitEliminationTie(t *testing.T) {
assert.Nil(t, err)
match, _ = web.arena.Database.GetMatchById(1)
assert.Equal(t, model.TieMatch, match.Status)
tournament.CreateTestAlliances(web.arena.Database, 2)
match.Type = "elimination"
match.ElimRedAlliance = 1
match.ElimBlueAlliance = 2
web.arena.Database.UpdateMatch(match)
web.commitMatchScore(match, matchResult, true)
match, _ = web.arena.Database.GetMatchById(1)

View File

@@ -113,13 +113,13 @@ func (web *Web) findBackupTeams(rankings game.Rankings) (game.Rankings, map[int]
pickedBackups := make(map[int]bool)
for _, alliance := range picks {
for _, team := range alliance {
for i, allianceTeamId := range alliance.TeamIds {
// Teams in third in an alliance are backups at events that use 3 team alliances.
if team.PickPosition == 3 {
pickedBackups[team.TeamId] = true
if i == 3 {
pickedBackups[allianceTeamId] = true
continue
}
pickedTeams[team.TeamId] = true
pickedTeams[allianceTeamId] = true
}
}
@@ -269,7 +269,7 @@ func (web *Web) couponsPdfReportHandler(w http.ResponseWriter, r *http.Request)
for i := page * 4; i < page*4+4 && i < len(alliances); i++ {
pdf.SetFillColor(220, 220, 220)
allianceCaptain := alliances[i][0].TeamId
allianceCaptain := alliances[i].TeamIds[0]
pdf.RoundedRect(cSideMargin, float64(heightAcc), cWidth, cHeight, 4, "1234", "D")
timeoutX := cSideMargin + (cWidth * 0.5)

View File

@@ -217,11 +217,14 @@ func (web *Web) clearDbHandler(w http.ResponseWriter, r *http.Request) {
handleWebErr(w, err)
return
}
err = web.arena.Database.TruncateAllianceTeams()
err = web.arena.Database.TruncateAlliances()
if err != nil {
handleWebErr(w, err)
return
}
web.arena.AllianceSelectionAlliances = []model.Alliance{}
cachedRankedTeams = []*RankedTeam{}
http.Redirect(w, r, "/setup/settings", 303)
}

View File

@@ -54,7 +54,7 @@ func TestSetupSettingsClearDb(t *testing.T) {
assert.Nil(t, web.arena.Database.CreateMatch(&model.Match{Type: "qualification"}))
assert.Nil(t, web.arena.Database.CreateMatchResult(new(model.MatchResult)))
assert.Nil(t, web.arena.Database.CreateRanking(&game.Ranking{TeamId: 254}))
assert.Nil(t, web.arena.Database.CreateAllianceTeam(new(model.AllianceTeam)))
assert.Nil(t, web.arena.Database.CreateAlliance(&model.Alliance{Id: 1}))
recorder := web.postHttpResponse("/setup/db/clear", "")
assert.Equal(t, 303, recorder.Code)
@@ -68,6 +68,7 @@ func TestSetupSettingsClearDb(t *testing.T) {
assert.Empty(t, rankings)
alliances, _ := web.arena.Database.GetAllAlliances()
assert.Empty(t, alliances)
assert.Empty(t, web.arena.AllianceSelectionAlliances)
}
func TestSetupSettingsBackupRestoreDb(t *testing.T) {