update
This commit is contained in:
244
backend/state.go
244
backend/state.go
@@ -8,6 +8,21 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
positionBoardPreliminary = "preliminary"
|
||||
positionBoardFinal = "final"
|
||||
positionBoardPrelimTie = "prelim_tiebreak"
|
||||
positionBoardFinalTie = "final_tiebreak"
|
||||
positionBoardUnassigned = "UNASSIGNED"
|
||||
)
|
||||
|
||||
var allowedPositionBoards = map[string]bool{
|
||||
positionBoardPreliminary: true,
|
||||
positionBoardFinal: true,
|
||||
positionBoardPrelimTie: true,
|
||||
positionBoardFinalTie: true,
|
||||
}
|
||||
|
||||
func (a *App) readState(includeAllProofs bool) (StateResponse, error) {
|
||||
players, err := a.readPlayers()
|
||||
if err != nil {
|
||||
@@ -38,17 +53,32 @@ func (a *App) readState(includeAllProofs bool) (StateResponse, error) {
|
||||
}
|
||||
|
||||
derived := computeDerived(players, scoreMap)
|
||||
positionBoards, err := a.readPositionBoards(players, derived)
|
||||
if err != nil {
|
||||
return StateResponse{}, err
|
||||
}
|
||||
currentStage, err := a.readCurrentStage()
|
||||
if err != nil {
|
||||
return StateResponse{}, err
|
||||
}
|
||||
lastStage, err := a.readLastStage()
|
||||
if err != nil {
|
||||
return StateResponse{}, err
|
||||
}
|
||||
|
||||
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),
|
||||
Players: players,
|
||||
Scores: scoreMapToJSON(scoreMap),
|
||||
PositionBoards: positionBoardMapToJSON(positionBoards),
|
||||
Settings: settings,
|
||||
Derived: derived,
|
||||
CurrentStage: currentStage,
|
||||
LastStage: lastStage,
|
||||
ServerTime: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
if includeScoreProofs {
|
||||
response.ScoreProofs = scoreProofMapToJSON(scoreProofs)
|
||||
@@ -133,6 +163,210 @@ func scoreProofMapToJSON(proofMap map[string]map[int]string) map[string]map[stri
|
||||
return out
|
||||
}
|
||||
|
||||
func positionBoardMapToJSON(positionMap map[string]map[int]PositionSlot) map[string]map[string]PositionSlot {
|
||||
out := map[string]map[string]PositionSlot{}
|
||||
for board := range allowedPositionBoards {
|
||||
out[board] = map[string]PositionSlot{}
|
||||
}
|
||||
for board, boardMap := range positionMap {
|
||||
if _, ok := out[board]; !ok {
|
||||
out[board] = map[string]PositionSlot{}
|
||||
}
|
||||
for playerID, slot := range boardMap {
|
||||
out[board][strconv.Itoa(playerID)] = slot
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (a *App) readPositionBoards(players []Player, derived DerivedState) (map[string]map[int]PositionSlot, error) {
|
||||
defaults := defaultPositionBoards(players, derived)
|
||||
merged := map[string]map[int]PositionSlot{}
|
||||
for board, boardMap := range defaults {
|
||||
merged[board] = map[int]PositionSlot{}
|
||||
for playerID, slot := range boardMap {
|
||||
merged[board][playerID] = slot
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := a.db.Query(`SELECT board, player_id, group_key, position FROM player_positions`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query player positions: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var boardRaw string
|
||||
var playerID int
|
||||
var groupKey string
|
||||
var position int
|
||||
if err := rows.Scan(&boardRaw, &playerID, &groupKey, &position); err != nil {
|
||||
return nil, fmt.Errorf("scan player position: %w", err)
|
||||
}
|
||||
board, ok := normalizePositionBoard(boardRaw)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
boardMap := merged[board]
|
||||
if boardMap == nil {
|
||||
continue
|
||||
}
|
||||
defaultSlot, exists := boardMap[playerID]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
pos := normalizePositionValue(position, defaultSlot.Position)
|
||||
group := normalizePositionGroupKey(board, groupKey, defaultSlot.GroupKey)
|
||||
boardMap[playerID] = PositionSlot{
|
||||
GroupKey: group,
|
||||
Position: pos,
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate player positions: %w", err)
|
||||
}
|
||||
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
func defaultPositionBoards(players []Player, derived DerivedState) map[string]map[int]PositionSlot {
|
||||
out := map[string]map[int]PositionSlot{
|
||||
positionBoardPreliminary: {},
|
||||
positionBoardFinal: {},
|
||||
positionBoardPrelimTie: {},
|
||||
positionBoardFinalTie: {},
|
||||
}
|
||||
|
||||
preRows := make([]Player, 0, len(players))
|
||||
preRows = append(preRows, players...)
|
||||
sort.SliceStable(preRows, func(i, j int) bool {
|
||||
ga := normalizePreliminaryGroupKey(preRows[i].GroupCode)
|
||||
gb := normalizePreliminaryGroupKey(preRows[j].GroupCode)
|
||||
if ga != gb {
|
||||
return ga < gb
|
||||
}
|
||||
return preRows[i].ID < preRows[j].ID
|
||||
})
|
||||
preGroupCounts := map[string]int{}
|
||||
for _, player := range preRows {
|
||||
group := normalizePreliminaryGroupKey(player.GroupCode)
|
||||
preGroupCounts[group]++
|
||||
out[positionBoardPreliminary][player.ID] = PositionSlot{
|
||||
GroupKey: group,
|
||||
Position: preGroupCounts[group],
|
||||
}
|
||||
}
|
||||
|
||||
finalGroupCounts := map[string]int{}
|
||||
for _, row := range derived.FinalGroups.Group1 {
|
||||
group := "1"
|
||||
finalGroupCounts[group]++
|
||||
out[positionBoardFinal][row.PlayerID] = PositionSlot{GroupKey: group, Position: finalGroupCounts[group]}
|
||||
}
|
||||
for _, row := range derived.FinalGroups.Group2 {
|
||||
group := "2"
|
||||
finalGroupCounts[group]++
|
||||
out[positionBoardFinal][row.PlayerID] = PositionSlot{GroupKey: group, Position: finalGroupCounts[group]}
|
||||
}
|
||||
|
||||
preRowsByID := map[int]RankingRow{}
|
||||
for _, row := range derived.PreliminaryRanking.Rows {
|
||||
preRowsByID[row.PlayerID] = row
|
||||
}
|
||||
preTieRows := make([]RankingRow, 0, len(derived.PreliminaryRanking.TieBreak.PlayerIDs))
|
||||
for _, id := range derived.PreliminaryRanking.TieBreak.PlayerIDs {
|
||||
row, ok := preRowsByID[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
preTieRows = append(preTieRows, row)
|
||||
}
|
||||
sort.SliceStable(preTieRows, func(i, j int) bool {
|
||||
if preTieRows[i].TieBreak != preTieRows[j].TieBreak {
|
||||
return preTieRows[i].TieBreak > preTieRows[j].TieBreak
|
||||
}
|
||||
if preTieRows[i].Score != preTieRows[j].Score {
|
||||
return preTieRows[i].Score > preTieRows[j].Score
|
||||
}
|
||||
return preTieRows[i].PlayerID < preTieRows[j].PlayerID
|
||||
})
|
||||
for idx, row := range preTieRows {
|
||||
groupKey := strconv.Itoa((idx / 6) + 1)
|
||||
position := (idx % 6) + 1
|
||||
out[positionBoardPrelimTie][row.PlayerID] = PositionSlot{GroupKey: groupKey, Position: position}
|
||||
}
|
||||
|
||||
finalRowsByID := map[int]RankingRow{}
|
||||
for _, row := range derived.FinalRanking.Rows {
|
||||
finalRowsByID[row.PlayerID] = row
|
||||
}
|
||||
finalTieRows := make([]RankingRow, 0, len(derived.FinalRanking.TieBreak.PlayerIDs))
|
||||
for _, id := range derived.FinalRanking.TieBreak.PlayerIDs {
|
||||
row, ok := finalRowsByID[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
finalTieRows = append(finalTieRows, row)
|
||||
}
|
||||
sort.SliceStable(finalTieRows, func(i, j int) bool {
|
||||
if finalTieRows[i].TieBreak != finalTieRows[j].TieBreak {
|
||||
return finalTieRows[i].TieBreak > finalTieRows[j].TieBreak
|
||||
}
|
||||
if finalTieRows[i].Score != finalTieRows[j].Score {
|
||||
return finalTieRows[i].Score > finalTieRows[j].Score
|
||||
}
|
||||
return finalTieRows[i].PlayerID < finalTieRows[j].PlayerID
|
||||
})
|
||||
for idx, row := range finalTieRows {
|
||||
groupKey := strconv.Itoa((idx / 6) + 1)
|
||||
position := (idx % 6) + 1
|
||||
out[positionBoardFinalTie][row.PlayerID] = PositionSlot{GroupKey: groupKey, Position: position}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizePositionBoard(board string) (string, bool) {
|
||||
normalized := strings.ToLower(strings.TrimSpace(board))
|
||||
if !allowedPositionBoards[normalized] {
|
||||
return "", false
|
||||
}
|
||||
return normalized, true
|
||||
}
|
||||
|
||||
func normalizePreliminaryGroupKey(groupCode string) string {
|
||||
normalized := strings.ToUpper(strings.TrimSpace(groupCode))
|
||||
if normalized == "" {
|
||||
return positionBoardUnassigned
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func normalizePositionGroupKey(board, groupKey, fallback string) string {
|
||||
if board == positionBoardPreliminary {
|
||||
normalized := normalizePreliminaryGroupKey(groupKey)
|
||||
if normalized == "" {
|
||||
return fallback
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
normalized := strings.TrimSpace(groupKey)
|
||||
if normalized == "" {
|
||||
return fallback
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func normalizePositionValue(position int, fallback int) int {
|
||||
if position > 0 {
|
||||
return position
|
||||
}
|
||||
if fallback > 0 {
|
||||
return fallback
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (a *App) ensureScoreRows(players []Player) error {
|
||||
if len(players) == 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user