package main import ( "fmt" "sort" "strconv" "strings" "time" ) func (a *App) readState(includeAllProofs bool) (StateResponse, error) { players, err := a.readPlayers() if err != nil { return StateResponse{}, err } if err := a.ensureScoreRows(players); err != nil { return StateResponse{}, err } settings, err := a.readSettings() if err != nil { return StateResponse{}, err } scoreMap, err := a.readScores(players) if err != nil { return StateResponse{}, err } includeScoreProofs := includeAllProofs || settings.ViewProofInView var scoreProofs map[string]map[int]string if includeScoreProofs { scoreProofs, err = a.readScoreProofs(players) if err != nil { return StateResponse{}, err } } derived := computeDerived(players, scoreMap) response := StateResponse{ Competition: CompetitionMeta{ TitleAr: "بطولة دويتوايلر للرماية", TitleEn: "Datwyler Shooting Event", }, Players: players, Scores: scoreMapToJSON(scoreMap), Settings: settings, Derived: derived, ServerTime: time.Now().UTC().Format(time.RFC3339), } if includeScoreProofs { response.ScoreProofs = scoreProofMapToJSON(scoreProofs) } return response, nil } func (a *App) readPlayers() ([]Player, error) { rows, err := a.db.Query(` SELECT id, name_ar, name_en, group_code, image_data FROM players ORDER BY id ASC `) if err != nil { return nil, fmt.Errorf("query players: %w", err) } defer rows.Close() players := []Player{} for rows.Next() { var p Player if err := rows.Scan(&p.ID, &p.NameAr, &p.NameEn, &p.GroupCode, &p.ImageData); err != nil { return nil, fmt.Errorf("scan player: %w", err) } players = append(players, p) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate players: %w", err) } return players, nil } func (a *App) readScoreProofs(players []Player) (map[string]map[int]string, error) { proofs := map[string]map[int]string{} for _, stage := range scoreStages { proofs[stage] = map[int]string{} } for _, p := range players { for _, stage := range scoreStages { proofs[stage][p.ID] = "" } } rows, err := a.db.Query(`SELECT stage, player_id, image_data FROM score_attachments`) if err != nil { return nil, fmt.Errorf("query score attachments: %w", err) } defer rows.Close() for rows.Next() { var stage string var playerID int var imageData string if err := rows.Scan(&stage, &playerID, &imageData); err != nil { return nil, fmt.Errorf("scan score attachment: %w", err) } stage = strings.ToLower(strings.TrimSpace(stage)) stageMap, ok := proofs[stage] if !ok { continue } stageMap[playerID] = imageData } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate score attachments: %w", err) } return proofs, nil } func scoreProofMapToJSON(proofMap map[string]map[int]string) map[string]map[string]string { out := map[string]map[string]string{} for stage, stageMap := range proofMap { out[stage] = map[string]string{} for playerID, imageData := range stageMap { if strings.TrimSpace(imageData) == "" { continue } out[stage][strconv.Itoa(playerID)] = imageData } } return out } func (a *App) ensureScoreRows(players []Player) error { if len(players) == 0 { return nil } tx, err := a.db.Begin() if err != nil { return fmt.Errorf("begin score row ensure tx: %w", err) } defer tx.Rollback() for _, p := range players { for _, stage := range scoreStages { if _, err := tx.Exec(`INSERT OR IGNORE INTO scores(stage, player_id, score) VALUES(?, ?, 0)`, stage, p.ID); err != nil { return fmt.Errorf("ensure score row (%s,%d): %w", stage, p.ID, err) } } } if err := tx.Commit(); err != nil { return fmt.Errorf("commit score row ensure tx: %w", err) } return nil } func (a *App) readScores(players []Player) (map[string]map[int]int, error) { scores := map[string]map[int]int{} for _, stage := range scoreStages { scores[stage] = map[int]int{} } for _, p := range players { for _, stage := range scoreStages { scores[stage][p.ID] = 0 } } rows, err := a.db.Query(`SELECT stage, player_id, score FROM scores`) if err != nil { return nil, fmt.Errorf("query scores: %w", err) } defer rows.Close() for rows.Next() { var stage string var playerID int var score int if err := rows.Scan(&stage, &playerID, &score); err != nil { return nil, fmt.Errorf("scan score: %w", err) } stage = strings.ToLower(stage) if _, ok := scores[stage]; !ok { continue } scores[stage][playerID] = score } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate scores: %w", err) } return scores, nil } func scoreMapToJSON(scoreMap map[string]map[int]int) map[string]map[string]int { out := map[string]map[string]int{} for stage, stageMap := range scoreMap { out[stage] = map[string]int{} for playerID, value := range stageMap { out[stage][strconv.Itoa(playerID)] = value } } return out } func computeDerived(players []Player, scores map[string]map[int]int) DerivedState { playerByID := map[int]Player{} for _, p := range players { playerByID[p.ID] = p } preRows := []RankingRow{} for _, p := range players { preRows = append(preRows, RankingRow{ PlayerID: p.ID, NameAr: p.NameAr, NameEn: p.NameEn, GroupCode: p.GroupCode, ImageData: p.ImageData, Score: scores["preliminary"][p.ID], TieBreak: scores["prelim_tiebreak"][p.ID], }) } sort.SliceStable(preRows, func(i, j int) bool { if preRows[i].Score != preRows[j].Score { return preRows[i].Score > preRows[j].Score } return preRows[i].PlayerID < preRows[j].PlayerID }) assignDenseRankByScore(preRows) preTie := TieBreakInfo{Required: false, Resolved: true, Slots: 0, PlayerIDs: []int{}} finalists := []RankingRow{} if len(preRows) <= 12 { for i := range preRows { row := preRows[i] row.Seed = i + 1 finalists = append(finalists, row) } } else { cutoff := preRows[11].Score above := []RankingRow{} atCutoff := []RankingRow{} for _, row := range preRows { if row.Score > cutoff { above = append(above, row) } else if row.Score == cutoff { atCutoff = append(atCutoff, row) } } slots := 12 - len(above) if slots < 0 { slots = 0 } if len(atCutoff) <= slots { finalists = append(finalists, above...) finalists = append(finalists, atCutoff...) } else { preTie.Required = true preTie.Slots = slots preTie.Resolved = true for _, row := range atCutoff { preTie.PlayerIDs = append(preTie.PlayerIDs, row.PlayerID) } sort.Ints(preTie.PlayerIDs) sort.SliceStable(atCutoff, func(i, j int) bool { if atCutoff[i].TieBreak != atCutoff[j].TieBreak { return atCutoff[i].TieBreak > atCutoff[j].TieBreak } return atCutoff[i].PlayerID < atCutoff[j].PlayerID }) if slots > 0 { boundary := atCutoff[slots-1].TieBreak greater := 0 equal := 0 for _, row := range atCutoff { if row.TieBreak > boundary { greater++ } else if row.TieBreak == boundary { equal++ } } if greater < slots && greater+equal > slots { preTie.Resolved = false } } finalists = append(finalists, above...) if slots > len(atCutoff) { slots = len(atCutoff) } finalists = append(finalists, atCutoff[:slots]...) } sort.SliceStable(finalists, func(i, j int) bool { if finalists[i].Score != finalists[j].Score { return finalists[i].Score > finalists[j].Score } if preTie.Required { if finalists[i].TieBreak != finalists[j].TieBreak { return finalists[i].TieBreak > finalists[j].TieBreak } } return finalists[i].PlayerID < finalists[j].PlayerID }) assignDenseRankBy(finalists, func(a, b RankingRow) bool { if a.Score != b.Score { return false } if preTie.Required { return a.TieBreak == b.TieBreak } return true }) } for i := range finalists { finalists[i].Seed = i + 1 if i < 6 { finalists[i].FinalGroup = 1 } else { finalists[i].FinalGroup = 2 } } finalGroup1 := []RankingRow{} finalGroup2 := []RankingRow{} for _, row := range finalists { if row.FinalGroup == 1 { finalGroup1 = append(finalGroup1, row) } else { finalGroup2 = append(finalGroup2, row) } } finalRows := []RankingRow{} for _, finalist := range finalists { p := playerByID[finalist.PlayerID] finalRows = append(finalRows, RankingRow{ PlayerID: finalist.PlayerID, NameAr: p.NameAr, NameEn: p.NameEn, GroupCode: p.GroupCode, ImageData: p.ImageData, Score: scores["final"][finalist.PlayerID], TieBreak: scores["final_tiebreak"][finalist.PlayerID], Seed: finalist.Seed, FinalGroup: finalist.FinalGroup, }) } sort.SliceStable(finalRows, func(i, j int) bool { if finalRows[i].Score != finalRows[j].Score { return finalRows[i].Score > finalRows[j].Score } return finalRows[i].Seed < finalRows[j].Seed }) finalTie := TieBreakInfo{Required: false, Resolved: true, Slots: 0, PlayerIDs: []int{}} tiedTop := map[int]bool{} if len(finalRows) > 0 { i := 0 for i < len(finalRows) { j := i + 1 for j < len(finalRows) && finalRows[j].Score == finalRows[i].Score { j++ } if j-i > 1 && finalRows[i].Score > 0 { startPos := i + 1 endPos := j if startPos <= 3 || endPos <= 3 || (startPos < 3 && endPos > 3) { for k := i; k < j; k++ { tiedTop[finalRows[k].PlayerID] = true } } } i = j } if len(tiedTop) > 0 { finalTie.Required = true for id := range tiedTop { finalTie.PlayerIDs = append(finalTie.PlayerIDs, id) } sort.Ints(finalTie.PlayerIDs) sort.SliceStable(finalRows, func(i, j int) bool { if finalRows[i].Score != finalRows[j].Score { return finalRows[i].Score > finalRows[j].Score } iti := tiedTop[finalRows[i].PlayerID] itj := tiedTop[finalRows[j].PlayerID] if iti && itj && finalRows[i].TieBreak != finalRows[j].TieBreak { return finalRows[i].TieBreak > finalRows[j].TieBreak } return finalRows[i].Seed < finalRows[j].Seed }) } } if finalTie.Required { assignDenseRankBy(finalRows, func(a, b RankingRow) bool { if a.Score != b.Score { return false } if tiedTop[a.PlayerID] && tiedTop[b.PlayerID] { return a.TieBreak == b.TieBreak } return true }) if len(finalRows) >= 3 { third := finalRows[2] greater := 0 equal := 0 for _, row := range finalRows { if row.Score > third.Score { greater++ continue } if row.Score == third.Score { itied := tiedTop[row.PlayerID] ttied := tiedTop[third.PlayerID] if itied && ttied { if row.TieBreak > third.TieBreak { greater++ } else if row.TieBreak == third.TieBreak { equal++ } } else { equal++ } } } if greater < 3 && greater+equal > 3 { finalTie.Resolved = false } } } else { assignDenseRankByScore(finalRows) } podium := []RankingRow{} for i := 0; i < len(finalRows) && i < 3; i++ { podium = append(podium, finalRows[i]) } return DerivedState{ PreliminaryRanking: RankingBundle{Rows: preRows, TieBreak: preTie, Unresolved: preTie.Required && !preTie.Resolved}, Finalists: finalists, FinalGroups: FinalGroups{Group1: finalGroup1, Group2: finalGroup2}, FinalRanking: RankingBundle{Rows: finalRows, TieBreak: finalTie, Unresolved: finalTie.Required && !finalTie.Resolved}, Podium: podium, } } func assignDenseRankByScore(rows []RankingRow) { assignDenseRankBy(rows, func(a, b RankingRow) bool { return a.Score == b.Score }) } func assignDenseRankBy(rows []RankingRow, isEqual func(a, b RankingRow) bool) { if len(rows) == 0 { return } currentRank := 1 rows[0].Rank = currentRank for i := 1; i < len(rows); i++ { if !isEqual(rows[i], rows[i-1]) { currentRank = i + 1 } rows[i].Rank = currentRank } }