Add field to Ranking model to track previous rank.

This commit is contained in:
Patrick Fairbank
2020-03-26 21:38:58 -07:00
parent 9d3100e65a
commit 2f7d186554
12 changed files with 120 additions and 48 deletions

View File

@@ -2,6 +2,7 @@
CREATE TABLE rankings (
teamid INTEGER PRIMARY KEY,
rank int,
previousrank int,
rankingfieldsjson text
);

View File

@@ -64,6 +64,7 @@ type Arena struct {
AudienceDisplayMode string
SavedMatch *model.Match
SavedMatchResult *model.MatchResult
SavedRankings game.Rankings
AllianceStationDisplayMode string
AllianceSelectionAlliances [][]model.AllianceTeam
LowerThird *model.LowerThird

View File

@@ -21,12 +21,13 @@ type RankingFields struct {
}
type Ranking struct {
TeamId int
Rank int
TeamId int
Rank int
PreviousRank int
RankingFields
}
type Rankings []*Ranking
type Rankings []Ranking
func (fields *RankingFields) AddScoreSummary(ownScore *ScoreSummary, opponentScore *ScoreSummary, disqualified bool) {
fields.Played += 1

View File

@@ -38,16 +38,16 @@ func TestAddScoreSummary(t *testing.T) {
func TestSortRankings(t *testing.T) {
// Check tiebreakers.
rankings := make(Rankings, 10)
rankings[0] = &Ranking{1, 0, RankingFields{50, 50, 50, 50, 0.49, 3, 2, 1, 0, 10}}
rankings[1] = &Ranking{2, 0, RankingFields{50, 50, 50, 50, 0.51, 3, 2, 1, 0, 10}}
rankings[2] = &Ranking{3, 0, RankingFields{50, 50, 50, 49, 0.50, 3, 2, 1, 0, 10}}
rankings[3] = &Ranking{4, 0, RankingFields{50, 50, 50, 51, 0.50, 3, 2, 1, 0, 10}}
rankings[4] = &Ranking{5, 0, RankingFields{50, 50, 49, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[5] = &Ranking{6, 0, RankingFields{50, 50, 51, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[6] = &Ranking{7, 0, RankingFields{50, 49, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[7] = &Ranking{8, 0, RankingFields{50, 51, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[8] = &Ranking{9, 0, RankingFields{49, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[9] = &Ranking{10, 0, RankingFields{51, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[0] = Ranking{1, 0, 0, RankingFields{50, 50, 50, 50, 0.49, 3, 2, 1, 0, 10}}
rankings[1] = Ranking{2, 0, 0, RankingFields{50, 50, 50, 50, 0.51, 3, 2, 1, 0, 10}}
rankings[2] = Ranking{3, 0, 0, RankingFields{50, 50, 50, 49, 0.50, 3, 2, 1, 0, 10}}
rankings[3] = Ranking{4, 0, 0, RankingFields{50, 50, 50, 51, 0.50, 3, 2, 1, 0, 10}}
rankings[4] = Ranking{5, 0, 0, RankingFields{50, 50, 49, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[5] = Ranking{6, 0, 0, RankingFields{50, 50, 51, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[6] = Ranking{7, 0, 0, RankingFields{50, 49, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[7] = Ranking{8, 0, 0, RankingFields{50, 51, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[8] = Ranking{9, 0, 0, RankingFields{49, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[9] = Ranking{10, 0, 0, RankingFields{51, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
sort.Sort(rankings)
assert.Equal(t, 10, rankings[0].TeamId)
assert.Equal(t, 8, rankings[1].TeamId)
@@ -62,9 +62,9 @@ func TestSortRankings(t *testing.T) {
// Check with unequal number of matches played.
rankings = make(Rankings, 3)
rankings[0] = &Ranking{1, 0, RankingFields{10, 25, 25, 25, 0.49, 3, 2, 1, 0, 5}}
rankings[1] = &Ranking{2, 0, RankingFields{19, 50, 50, 50, 0.51, 3, 2, 1, 0, 9}}
rankings[2] = &Ranking{3, 0, RankingFields{20, 50, 50, 50, 0.51, 3, 2, 1, 0, 10}}
rankings[0] = Ranking{1, 0, 0, RankingFields{10, 25, 25, 25, 0.49, 3, 2, 1, 0, 5}}
rankings[1] = Ranking{2, 0, 0, RankingFields{19, 50, 50, 50, 0.51, 3, 2, 1, 0, 9}}
rankings[2] = Ranking{3, 0, 0, RankingFields{20, 50, 50, 50, 0.51, 3, 2, 1, 0, 10}}
sort.Sort(rankings)
assert.Equal(t, 2, rankings[0].TeamId)
assert.Equal(t, 3, rankings[1].TeamId)

View File

@@ -45,9 +45,9 @@ func TestScore2() *Score {
}
func TestRanking1() *Ranking {
return &Ranking{254, 1, RankingFields{20, 625, 90, 554, 0.254, 3, 2, 1, 0, 10}}
return &Ranking{254, 1, 0, RankingFields{20, 625, 90, 554, 0.254, 3, 2, 1, 0, 10}}
}
func TestRanking2() *Ranking {
return &Ranking{1114, 2, RankingFields{18, 700, 625, 90, 0.1114, 1, 3, 2, 0, 10}}
return &Ranking{1114, 2, 1, RankingFields{18, 700, 625, 90, 0.1114, 1, 3, 2, 0, 10}}
}

View File

@@ -13,6 +13,7 @@ import (
type RankingDb struct {
TeamId int
Rank int
PreviousRank int
RankingFieldsJson string
}
@@ -59,7 +60,7 @@ func (database *Database) TruncateRankings() error {
return database.rankingMap.TruncateTables()
}
func (database *Database) GetAllRankings() ([]game.Ranking, error) {
func (database *Database) GetAllRankings() (game.Rankings, error) {
var rankingDbs []RankingDb
err := database.rankingMap.Select(&rankingDbs, "SELECT * FROM rankings ORDER BY rank")
if err != nil {
@@ -90,7 +91,7 @@ func (database *Database) ReplaceAllRankings(rankings game.Rankings) error {
}
for _, ranking := range rankings {
rankingDb, err := serializeRanking(ranking)
rankingDb, err := serializeRanking(&ranking)
if err != nil {
transaction.Rollback()
return err
@@ -107,7 +108,7 @@ func (database *Database) ReplaceAllRankings(rankings game.Rankings) error {
// Converts the nested struct MatchResult to the DB version that has JSON fields.
func serializeRanking(ranking *game.Ranking) (*RankingDb, error) {
rankingDb := RankingDb{TeamId: ranking.TeamId, Rank: ranking.Rank}
rankingDb := RankingDb{TeamId: ranking.TeamId, Rank: ranking.Rank, PreviousRank: ranking.PreviousRank}
if err := serializeHelper(&rankingDb.RankingFieldsJson, ranking.RankingFields); err != nil {
return nil, err
}
@@ -116,7 +117,7 @@ func serializeRanking(ranking *game.Ranking) (*RankingDb, error) {
// Converts the DB Ranking with JSON fields to the nested struct version.
func (rankingDb *RankingDb) deserialize() (*game.Ranking, error) {
ranking := game.Ranking{TeamId: rankingDb.TeamId, Rank: rankingDb.Rank}
ranking := game.Ranking{TeamId: rankingDb.TeamId, Rank: rankingDb.Rank, PreviousRank: rankingDb.PreviousRank}
if err := json.Unmarshal([]byte(rankingDb.RankingFieldsJson), &ranking.RankingFields); err != nil {
return nil, err
}

View File

@@ -13,10 +13,10 @@ import (
)
// Determines the rankings from the stored match results, and saves them to the database.
func CalculateRankings(database *model.Database) error {
func CalculateRankings(database *model.Database, preservePreviousRank bool) (game.Rankings, error) {
matches, err := database.GetMatchesByType("qualification")
if err != nil {
return err
return nil, err
}
rankings := make(map[int]*game.Ranking)
for _, match := range matches {
@@ -25,7 +25,7 @@ func CalculateRankings(database *model.Database) error {
}
matchResult, err := database.GetMatchResultForMatch(match.Id)
if err != nil {
return err
return nil, err
}
if !match.Red1IsSurrogate {
addMatchResultToRankings(rankings, match.Red1, matchResult, true)
@@ -47,16 +47,33 @@ func CalculateRankings(database *model.Database) error {
}
}
// Retrieve old rankings so that we can display changes in rank as a result of this calculation.
oldRankings, err := database.GetAllRankings()
if err != nil {
return nil, err
}
oldRankingsMap := make(map[int]game.Ranking, len(oldRankings))
for _, ranking := range oldRankings {
oldRankingsMap[ranking.TeamId] = ranking
}
sortedRankings := sortRankings(rankings)
for rank, ranking := range sortedRankings {
ranking.Rank = rank + 1
sortedRankings[rank].Rank = rank + 1
if oldRank, ok := oldRankingsMap[ranking.TeamId]; ok {
if preservePreviousRank {
sortedRankings[rank].PreviousRank = oldRank.PreviousRank
} else {
sortedRankings[rank].PreviousRank = oldRank.Rank
}
}
}
err = database.ReplaceAllRankings(sortedRankings)
if err != nil {
return nil
return nil, err
}
return nil
return sortedRankings, nil
}
// Checks all the match results for yellow and red cards, and updates the team model accordingly.
@@ -140,7 +157,7 @@ func addMatchResultToRankings(rankings map[int]*game.Ranking, teamId int, matchR
func sortRankings(rankings map[int]*game.Ranking) game.Rankings {
var sortedRankings game.Rankings
for _, ranking := range rankings {
sortedRankings = append(sortedRankings, ranking)
sortedRankings = append(sortedRankings, *ranking)
}
sort.Sort(sortedRankings)
return sortedRankings

View File

@@ -13,17 +13,24 @@ func TestCalculateRankings(t *testing.T) {
database := setupTestDb(t)
setupMatchResultsForRankings(database)
err := CalculateRankings(database)
updatedRankings, err := CalculateRankings(database, false)
assert.Nil(t, err)
rankings, err := database.GetAllRankings()
assert.Nil(t, err)
assert.Equal(t, updatedRankings, rankings)
if assert.Equal(t, 6, len(rankings)) {
assert.Equal(t, 4, rankings[0].TeamId)
assert.Equal(t, 0, rankings[0].PreviousRank)
assert.Equal(t, 5, rankings[1].TeamId)
assert.Equal(t, 0, rankings[1].PreviousRank)
assert.Equal(t, 6, rankings[2].TeamId)
assert.Equal(t, 0, rankings[2].PreviousRank)
assert.Equal(t, 1, rankings[3].TeamId)
assert.Equal(t, 0, rankings[3].PreviousRank)
assert.Equal(t, 3, rankings[4].TeamId)
assert.Equal(t, 0, rankings[4].PreviousRank)
assert.Equal(t, 2, rankings[5].TeamId)
assert.Equal(t, 0, rankings[5].PreviousRank)
}
// Test after changing a match result.
@@ -31,17 +38,47 @@ func TestCalculateRankings(t *testing.T) {
matchResult3.RedScore, matchResult3.BlueScore = matchResult3.BlueScore, matchResult3.RedScore
err = database.CreateMatchResult(matchResult3)
assert.Nil(t, err)
err = CalculateRankings(database)
updatedRankings, err = CalculateRankings(database, false)
assert.Nil(t, err)
rankings, err = database.GetAllRankings()
assert.Nil(t, err)
assert.Equal(t, updatedRankings, rankings)
if assert.Equal(t, 6, len(rankings)) {
assert.Equal(t, 6, rankings[0].TeamId)
assert.Equal(t, 3, rankings[0].PreviousRank)
assert.Equal(t, 5, rankings[1].TeamId)
assert.Equal(t, 2, rankings[1].PreviousRank)
assert.Equal(t, 4, rankings[2].TeamId)
assert.Equal(t, 1, rankings[2].PreviousRank)
assert.Equal(t, 1, rankings[3].TeamId)
assert.Equal(t, 4, rankings[3].PreviousRank)
assert.Equal(t, 3, rankings[4].TeamId)
assert.Equal(t, 5, rankings[4].PreviousRank)
assert.Equal(t, 2, rankings[5].TeamId)
assert.Equal(t, 6, rankings[5].PreviousRank)
}
matchResult3 = model.BuildTestMatchResult(3, 4)
err = database.CreateMatchResult(matchResult3)
assert.Nil(t, err)
updatedRankings, err = CalculateRankings(database, true)
assert.Nil(t, err)
rankings, err = database.GetAllRankings()
assert.Nil(t, err)
assert.Equal(t, updatedRankings, rankings)
if assert.Equal(t, 6, len(rankings)) {
assert.Equal(t, 4, rankings[0].TeamId)
assert.Equal(t, 1, rankings[0].PreviousRank)
assert.Equal(t, 5, rankings[1].TeamId)
assert.Equal(t, 2, rankings[1].PreviousRank)
assert.Equal(t, 1, rankings[2].TeamId)
assert.Equal(t, 4, rankings[2].PreviousRank)
assert.Equal(t, 3, rankings[3].TeamId)
assert.Equal(t, 5, rankings[3].PreviousRank)
assert.Equal(t, 6, rankings[4].TeamId)
assert.Equal(t, 3, rankings[4].PreviousRank)
assert.Equal(t, 2, rankings[5].TeamId)
assert.Equal(t, 6, rankings[5].PreviousRank)
}
}

View File

@@ -7,6 +7,7 @@ package web
import (
"fmt"
"github.com/Team254/cheesy-arena/game"
"github.com/Team254/cheesy-arena/model"
"github.com/Team254/cheesy-arena/tournament"
"github.com/Team254/cheesy-arena/websocket"
@@ -144,6 +145,15 @@ func (web *Web) matchPlayShowResultHandler(w http.ResponseWriter, r *http.Reques
handleWebErr(w, fmt.Errorf("No result found for match ID %d.", matchId))
return
}
if match.ShouldUpdateRankings() {
web.arena.SavedRankings, err = web.arena.Database.GetAllRankings()
if err != nil {
handleWebErr(w, err)
return
}
} else {
web.arena.SavedRankings = game.Rankings{}
}
web.arena.SavedMatch = match
web.arena.SavedMatchResult = matchResult
web.arena.ScorePostedNotifier.Notify()
@@ -312,7 +322,9 @@ func (web *Web) matchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request
}
// Saves the given match and result to the database, supplanting any previous result for the match.
func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchResult, loadToShowBuffer bool) error {
func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchResult, isMatchReviewEdit bool) error {
var updatedRankings game.Rankings
if match.Type == "elimination" {
// Adjust the score if necessary for an elimination DQ.
matchResult.CorrectEliminationScore()
@@ -368,10 +380,11 @@ func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchRes
if match.ShouldUpdateRankings() {
// Recalculate all the rankings.
err = tournament.CalculateRankings(web.arena.Database)
rankings, err := tournament.CalculateRankings(web.arena.Database, isMatchReviewEdit)
if err != nil {
return err
}
updatedRankings = rankings
}
if match.ShouldUpdateEliminationMatches() {
@@ -432,10 +445,11 @@ func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchRes
}
}
if loadToShowBuffer {
if !isMatchReviewEdit {
// Store the result in the buffer to be shown in the audience display.
web.arena.SavedMatch = match
web.arena.SavedMatchResult = matchResult
web.arena.SavedRankings = updatedRankings
web.arena.ScorePostedNotifier.Notify()
}
@@ -450,7 +464,7 @@ func (web *Web) getCurrentMatchResult() *model.MatchResult {
// Saves the realtime result as the final score for the match currently loaded into the arena.
func (web *Web) commitCurrentMatchScore() error {
return web.commitMatchScore(web.arena.CurrentMatch, web.getCurrentMatchResult(), true)
return web.commitMatchScore(web.arena.CurrentMatch, web.getCurrentMatchResult(), false)
}
// Helper function to implement the required interface for Sort.

View File

@@ -121,7 +121,7 @@ func TestCommitMatch(t *testing.T) {
// Committing test match should do nothing.
match := &model.Match{Id: 0, Type: "test", Red1: 101, Red2: 102, Red3: 103, Blue1: 104, Blue2: 105, Blue3: 106}
err := web.commitMatchScore(match, &model.MatchResult{MatchId: match.Id}, false)
err := web.commitMatchScore(match, &model.MatchResult{MatchId: match.Id}, true)
assert.Nil(t, err)
matchResult, err := web.arena.Database.GetMatchResultForMatch(match.Id)
assert.Nil(t, err)
@@ -134,7 +134,7 @@ func TestCommitMatch(t *testing.T) {
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
matchResult.BlueScore = &game.Score{ExitedInitiationLine: [3]bool{true, false, false}}
err = web.commitMatchScore(match, matchResult, false)
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
assert.Equal(t, 1, matchResult.PlayNumber)
match, _ = web.arena.Database.GetMatchById(1)
@@ -143,7 +143,7 @@ func TestCommitMatch(t *testing.T) {
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
matchResult.RedScore = &game.Score{ExitedInitiationLine: [3]bool{true, false, true}}
err = web.commitMatchScore(match, matchResult, false)
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
assert.Equal(t, 2, matchResult.PlayNumber)
match, _ = web.arena.Database.GetMatchById(1)
@@ -151,7 +151,7 @@ func TestCommitMatch(t *testing.T) {
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
err = web.commitMatchScore(match, matchResult, false)
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
assert.Equal(t, 3, matchResult.PlayNumber)
match, _ = web.arena.Database.GetMatchById(1)
@@ -162,7 +162,7 @@ func TestCommitMatch(t *testing.T) {
web.arena.EventSettings.TbaPublishingEnabled = true
var writer bytes.Buffer
log.SetOutput(&writer)
err = web.commitMatchScore(match, matchResult, false)
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
time.Sleep(time.Millisecond * 10) // Allow some time for the asynchronous publishing to happen.
assert.Contains(t, writer.String(), "Failed to publish matches")
@@ -181,13 +181,13 @@ func TestCommitEliminationTie(t *testing.T) {
Fouls: []game.Foul{{RuleId: 1}, {RuleId: 2}, {RuleId: 4}}},
BlueScore: &game.Score{},
}
err := web.commitMatchScore(match, matchResult, false)
err := web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
match, _ = web.arena.Database.GetMatchById(1)
assert.Equal(t, "T", match.Winner)
match.Type = "elimination"
web.arena.Database.SaveMatch(match)
web.commitMatchScore(match, matchResult, false)
web.commitMatchScore(match, matchResult, true)
match, _ = web.arena.Database.GetMatchById(1)
assert.Equal(t, "T", match.Winner) // No elimination tiebreakers.
}
@@ -203,7 +203,7 @@ func TestCommitCards(t *testing.T) {
matchResult := model.NewMatchResult()
matchResult.MatchId = match.Id
matchResult.BlueCards = map[string]string{"5": "yellow"}
err := web.commitMatchScore(match, matchResult, false)
err := web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
team, _ = web.arena.Database.GetTeamById(5)
assert.True(t, team.YellowCard)
@@ -211,7 +211,7 @@ func TestCommitCards(t *testing.T) {
// Check that editing a match result removes a yellow card from a team.
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
err = web.commitMatchScore(match, matchResult, false)
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
team, _ = web.arena.Database.GetTeamById(5)
assert.False(t, team.YellowCard)
@@ -220,7 +220,7 @@ func TestCommitCards(t *testing.T) {
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
matchResult.BlueCards = map[string]string{"5": "red"}
err = web.commitMatchScore(match, matchResult, false)
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
team, _ = web.arena.Database.GetTeamById(5)
assert.True(t, team.YellowCard)
@@ -234,7 +234,7 @@ func TestCommitCards(t *testing.T) {
matchResult = model.BuildTestMatchResult(match.Id, 10)
matchResult.MatchType = match.Type
matchResult.RedCards = map[string]string{"1": "red"}
err = web.commitMatchScore(match, matchResult, false)
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
assert.Equal(t, 0, matchResult.RedScoreSummary(true).Score)
assert.NotEqual(t, 0, matchResult.BlueScoreSummary(true).Score)

View File

@@ -134,7 +134,7 @@ func (web *Web) matchReviewEditPostHandler(w http.ResponseWriter, r *http.Reques
http.Redirect(w, r, "/match_play", 303)
} else {
err = web.commitMatchScore(match, matchResult, false)
err = web.commitMatchScore(match, matchResult, true)
if err != nil {
handleWebErr(w, err)
return

View File

@@ -64,7 +64,7 @@ func TestSetupSettingsClearDb(t *testing.T) {
assert.Empty(t, matches)
rankings, _ := web.arena.Database.GetAllRankings()
assert.Empty(t, rankings)
tournament.CalculateRankings(web.arena.Database)
tournament.CalculateRankings(web.arena.Database, false)
assert.Empty(t, rankings)
alliances, _ := web.arena.Database.GetAllAlliances()
assert.Empty(t, alliances)