Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions internal/database/game.sql.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions internal/database/queries/game.sql
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ insert into stage_games (stage_id, game_id)
values (?, ?)
returning *;

-- name: SetStageStatus :exec
update stages
set status = ?
where id = ?
and tournament_id = ?;

-- name: UpdateStage :one
update stages
set name = ?,
Expand Down
15 changes: 15 additions & 0 deletions internal/games/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ func fromGameModel(m database.Game) (Game, error) {
}, nil
}

func fromGameModels(models []database.Game) ([]Game, error) {
var results []Game

for _, model := range models {
game, err := fromGameModel(model)
if err != nil {
return []Game{}, fmt.Errorf("failed to convert game model: %w", err)
}

results = append(results, game)
}

return results, nil
}

func toGameModel(g Game) (database.Game, error) {

resolvedWinner := sql.NullInt64{}
Expand Down
47 changes: 4 additions & 43 deletions internal/games/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,49 +114,10 @@ func (s *Service) GetTeamStatistics(ctx context.Context, teamID int64) (TeamStat
return TeamStatistics{}, fmt.Errorf("listing completed games: %w", err)
}

stats := TeamStatistics{}
for _, model := range models {
if err = s.processGameForStats(&stats, model, teamID); err != nil {
return TeamStatistics{}, err
}
}

if stats.GamesPlayed == 0 {
return stats, nil
}

stats.WinRate = (float64(stats.Wins) / float64(stats.GamesPlayed)) * 100
stats.RoundDifferential = stats.RoundsWon - stats.RoundsLost
stats.AverageRoundsWon = float64(stats.RoundsWon) / float64(stats.GamesPlayed)
stats.AverageRoundsLost = float64(stats.RoundsLost) / float64(stats.GamesPlayed)

return stats, nil
}

func (s *Service) processGameForStats(stats *TeamStatistics, model database.Game, teamID int64) error {
stats.GamesPlayed++

switch {
case !model.Winner.Valid || model.Winner.Int64 == 0:
stats.Draws++
case model.Winner.Int64 == teamID:
stats.Wins++
default:
stats.Losses++
}

var results []RoundResult
if err := json.Unmarshal(model.ResultsLog, &results); err != nil {
return fmt.Errorf("parsing game %d results: %w", model.ID, err)
}

teamRoundsWon := len(filterResultsByTeam(TeamA, results))
teamRoundsLost := len(filterResultsByTeam(TeamB, results))
if model.TeamB.Valid && model.TeamB.Int64 == teamID {
teamRoundsWon, teamRoundsLost = teamRoundsLost, teamRoundsWon
playedGames, err := fromGameModels(models)
if err != nil {
return TeamStatistics{}, fmt.Errorf("failed to convert game models: %w", err)
}

stats.RoundsWon += teamRoundsWon
stats.RoundsLost += teamRoundsLost
return nil
return TeamStatisticsForGames(teamID, playedGames), nil
}
64 changes: 64 additions & 0 deletions internal/games/statistics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package games

func TeamStatisticsForGames(teamID int64, ga []Game) TeamStatistics {
var ts TeamStatistics

for _, g := range ga {
tmp := TeamStatisticsForGame(teamID, g)

ts.Wins += tmp.Wins
ts.Losses += tmp.Losses
ts.Draws += tmp.Draws
ts.RoundsWon += tmp.RoundsWon
ts.RoundsLost += tmp.RoundsLost
}

ts.GamesPlayed = len(ga)

if ts.GamesPlayed == 0 {
return ts
}

ts.WinRate = float64(ts.Wins) / float64(ts.GamesPlayed) * 100
ts.RoundDifferential = ts.RoundsWon - ts.RoundsLost
ts.AverageRoundsWon = float64(ts.RoundsWon) / float64(ts.GamesPlayed)
ts.AverageRoundsLost = float64(ts.RoundsLost) / float64(ts.GamesPlayed)

return ts
}

func TeamStatisticsForGame(teamID int64, ga Game) TeamStatistics {
var ts TeamStatistics

if ga.winner == nil {
ga.winner = new(int64)
}

sideAWins := len(filterResultsByTeam(TeamA, ga.results))
sideBWins := len(filterResultsByTeam(TeamB, ga.results))

switch *ga.winner {
case 0:
ts.Draws += 1
case teamID:
ts.Wins += 1
default:
ts.Losses += 1
}

teamSide := TeamA
if ga.teamB == teamID {
teamSide = TeamB
}

switch teamSide {
case TeamA:
ts.RoundsWon += sideAWins
ts.RoundsLost += sideBWins
default:
ts.RoundsWon += sideBWins
ts.RoundsLost += sideAWins
}

return ts
}
117 changes: 117 additions & 0 deletions internal/games/statistics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package games

import (
"testing"

"github.com/code-gorilla-au/odize"
)

func TestTeamStatisticsForGames(t *testing.T) {
group := odize.NewGroup(t, nil)

teamID := int64(1)
otherTeamID := int64(2)

err := group.
Test("should return zeroed statistics for empty games list", func(t *testing.T) {
stats := TeamStatisticsForGames(teamID, []Game{})
odize.AssertEqual(t, 0, stats.GamesPlayed)
odize.AssertEqual(t, 0, stats.Wins)
odize.AssertEqual(t, 0, stats.Losses)
odize.AssertEqual(t, 0, stats.Draws)
odize.AssertEqual(t, 0.0, stats.WinRate)
odize.AssertEqual(t, 0, stats.RoundsWon)
odize.AssertEqual(t, 0, stats.RoundsLost)
odize.AssertEqual(t, 0, stats.RoundDifferential)
odize.AssertEqual(t, 0.0, stats.AverageRoundsWon)
odize.AssertEqual(t, 0.0, stats.AverageRoundsLost)
}).
Test("should calculate statistics correctly for a win", func(t *testing.T) {
game := Game{
teamA: teamID,
teamB: otherTeamID,
winner: &teamID,
results: []RoundResult{
{Outcome: TeamA, RemainingPlayers: 1},
},
}
stats := TeamStatisticsForGames(teamID, []Game{game})
odize.AssertEqual(t, 1, stats.GamesPlayed)
odize.AssertEqual(t, 1, stats.Wins)
odize.AssertEqual(t, 0, stats.Losses)
odize.AssertEqual(t, 0, stats.Draws)
odize.AssertEqual(t, 100.00, stats.WinRate)
odize.AssertEqual(t, 1, stats.RoundsWon)
odize.AssertEqual(t, 0, stats.RoundsLost)
odize.AssertEqual(t, 1, stats.RoundDifferential)
odize.AssertEqual(t, 1.0, stats.AverageRoundsWon)
odize.AssertEqual(t, 0.0, stats.AverageRoundsLost)
}).
Test("should calculate statistics correctly for a loss", func(t *testing.T) {
game := Game{
teamA: teamID,
teamB: otherTeamID,
winner: &otherTeamID,
results: []RoundResult{
{Outcome: TeamB, RemainingPlayers: 1},
},
}
stats := TeamStatisticsForGames(teamID, []Game{game})
odize.AssertEqual(t, 1, stats.GamesPlayed)
odize.AssertEqual(t, 0, stats.Wins)
odize.AssertEqual(t, 1, stats.Losses)
odize.AssertEqual(t, 0, stats.Draws)
odize.AssertEqual(t, 0.0, stats.WinRate)
odize.AssertEqual(t, 0, stats.RoundsWon)
odize.AssertEqual(t, 1, stats.RoundsLost)
odize.AssertEqual(t, -1, stats.RoundDifferential)
odize.AssertEqual(t, 0.0, stats.AverageRoundsWon)
odize.AssertEqual(t, 1.0, stats.AverageRoundsLost)
}).
Test("should calculate statistics correctly for a draw", func(t *testing.T) {
winner := int64(0)
game := Game{
teamA: teamID,
teamB: otherTeamID,
winner: &winner,
results: []RoundResult{
{Outcome: TeamA, RemainingPlayers: 1},
},
}
stats := TeamStatisticsForGames(teamID, []Game{game})
odize.AssertEqual(t, 1, stats.GamesPlayed)
odize.AssertEqual(t, 0, stats.Wins)
odize.AssertEqual(t, 0, stats.Losses)
odize.AssertEqual(t, 1, stats.Draws)
odize.AssertEqual(t, 0.0, stats.WinRate)
odize.AssertEqual(t, 1, stats.RoundsWon)
odize.AssertEqual(t, 0, stats.RoundsLost)
odize.AssertEqual(t, 1, stats.RoundDifferential)
odize.AssertEqual(t, 1.0, stats.AverageRoundsWon)
odize.AssertEqual(t, 0.0, stats.AverageRoundsLost)
}).
Test("should handle nil winner as draw", func(t *testing.T) {
game := Game{
teamA: teamID,
teamB: otherTeamID,
winner: nil,
results: []RoundResult{
{Outcome: TeamA, RemainingPlayers: 1},
},
}
stats := TeamStatisticsForGames(teamID, []Game{game})
odize.AssertEqual(t, 1, stats.GamesPlayed)
odize.AssertEqual(t, 0, stats.Wins)
odize.AssertEqual(t, 0, stats.Losses)
odize.AssertEqual(t, 1, stats.Draws)
odize.AssertEqual(t, 0.0, stats.WinRate)
odize.AssertEqual(t, 1, stats.RoundsWon)
odize.AssertEqual(t, 0, stats.RoundsLost)
odize.AssertEqual(t, 1, stats.RoundDifferential)
odize.AssertEqual(t, 1.0, stats.AverageRoundsWon)
odize.AssertEqual(t, 0.0, stats.AverageRoundsLost)
}).
Run()

odize.AssertNoError(t, err)
}
1 change: 1 addition & 0 deletions internal/tournaments/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Store interface {
CreateStage(ctx context.Context, arg database.CreateStageParams) (database.Stage, error)
CreateTournament(ctx context.Context, arg database.CreateTournamentParams) (database.Tournament, error)
UpdateStage(ctx context.Context, arg database.UpdateStageParams) (database.Stage, error)
SetStageStatus(ctx context.Context, arg database.SetStageStatusParams) error
}

type TeamsService interface {
Expand Down