playoff visual fixes

This commit is contained in:
2026-03-09 13:01:28 +11:00
parent 1cab39a4f7
commit ba0844048a
11 changed files with 528 additions and 215 deletions

View File

@@ -336,17 +336,19 @@ func generate7to9Bracket(
// generate10to15Bracket creates a finals bracket for 10-15 teams:
//
// Qualifying Finals (QF1-QF4): Top 4 get second chance
// Qualifying Finals: Top 4 get second chance
// QF1: 1st vs 4th
// QF2: 2nd vs 3rd
// QF3: 5th vs 8th
// QF4: 6th vs 7th
//
// Semi Finals:
// SF1: Loser(QF1) vs Winner(QF4) — loser eliminated
// SF2: Loser(QF2) vs Winner(QF3) — loser eliminated
// Elimination Finals: Single elimination
// EF1: 5th vs 8th
// EF2: 6th vs 7th
//
// Preliminary Finals:
// Semi Finals (same-side: QF loser faces same-side EF winner):
// SF1: Loser(QF1) vs Winner(EF1) — loser eliminated
// SF2: Loser(QF2) vs Winner(EF2) — loser eliminated
//
// Preliminary Finals (QF winner vs opposite SF winner):
// PF1: Winner(QF1) vs Winner(SF2)
// PF2: Winner(QF2) vs Winner(SF1)
//
@@ -412,7 +414,7 @@ func generate10to15Bracket(
// Semi Finals
sf1, err := NewPlayoffSeries(ctx, tx, bracket, 5,
"semi_final", "SF1",
nil, nil, // Loser(QF1) vs Winner(EF2)
nil, nil, // Loser(QF1) vs Winner(EF1)
nil, nil,
getMatchesToWin(roundFormats, "semi_final"), SeriesStatusPending)
if err != nil {
@@ -421,7 +423,7 @@ func generate10to15Bracket(
sf2, err := NewPlayoffSeries(ctx, tx, bracket, 6,
"semi_final", "SF2",
nil, nil, // Loser(QF2) vs Winner(EF1)
nil, nil, // Loser(QF2) vs Winner(EF2)
nil, nil,
getMatchesToWin(roundFormats, "semi_final"), SeriesStatusPending)
if err != nil {
@@ -482,28 +484,28 @@ func generate10to15Bracket(
return errors.Wrap(err, "wire QF2")
}
// EF1 (QF3): Winner -> SF2 (team2), Loser eliminated
// EF1 (QF3): Winner -> SF1 (team2), Loser eliminated
err = SetSeriesAdvancement(ctx, tx, qf3.ID,
&sf2.ID, strPtr("team2"), nil, nil)
&sf1.ID, strPtr("team2"), nil, nil)
if err != nil {
return errors.Wrap(err, "wire EF1")
}
// EF2 (QF4): Winner -> SF1 (team2), Loser eliminated
// EF2 (QF4): Winner -> SF2 (team2), Loser eliminated
err = SetSeriesAdvancement(ctx, tx, qf4.ID,
&sf1.ID, strPtr("team2"), nil, nil)
&sf2.ID, strPtr("team2"), nil, nil)
if err != nil {
return errors.Wrap(err, "wire EF2")
}
// SF1: Winner -> PF2 (team2), Loser eliminated
// SF1: Winner -> PF2 (team2), Loser eliminated (crosses to face QF2 winner)
err = SetSeriesAdvancement(ctx, tx, sf1.ID,
&pf2.ID, strPtr("team2"), nil, nil)
if err != nil {
return errors.Wrap(err, "wire SF1")
}
// SF2: Winner -> PF1 (team2), Loser eliminated
// SF2: Winner -> PF1 (team2), Loser eliminated (crosses to face QF1 winner)
err = SetSeriesAdvancement(ctx, tx, sf2.ID,
&pf1.ID, strPtr("team2"), nil, nil)
if err != nil {

View File

@@ -142,7 +142,12 @@ type LeagueWithTeams struct {
Teams []*Team
}
// GetStatus returns the current status of the season based on dates
// GetStatus returns the current status of the season based on dates.
// Dates are treated as inclusive days:
// - StartDate: season is "in progress" from the start of this day
// - EndDate: season is "in progress" through the end of this day
// - FinalsStartDate: finals are active from the start of this day
// - FinalsEndDate: finals are active through the end of this day
func (s *Season) GetStatus() SeasonStatus {
now := time.Now()
@@ -150,20 +155,32 @@ func (s *Season) GetStatus() SeasonStatus {
return StatusUpcoming
}
// dayPassed returns true if the entire calendar day of t has passed.
// e.g., if t is March 8, this returns true starting March 9 00:00:00.
dayPassed := func(t time.Time) bool {
return now.After(t.Truncate(time.Hour*24).AddDate(0, 0, 1))
}
// dayStarted returns true if the calendar day of t has started.
// e.g., if t is March 8, this returns true starting March 8 00:00:00.
dayStarted := func(t time.Time) bool {
return !now.Before(t.Truncate(time.Hour * 24))
}
if !s.FinalsStartDate.IsZero() {
if !s.FinalsEndDate.IsZero() && now.After(s.FinalsEndDate.Time) {
if !s.FinalsEndDate.IsZero() && dayPassed(s.FinalsEndDate.Time) {
return StatusCompleted
}
if now.After(s.FinalsStartDate.Time) {
if dayStarted(s.FinalsStartDate.Time) {
return StatusFinals
}
if !s.EndDate.IsZero() && now.After(s.EndDate.Time) {
if !s.EndDate.IsZero() && dayPassed(s.EndDate.Time) {
return StatusFinalsSoon
}
return StatusInProgress
}
if !s.EndDate.IsZero() && now.After(s.EndDate.Time) {
if !s.EndDate.IsZero() && dayPassed(s.EndDate.Time) {
return StatusCompleted
}