a geicko-2 based round robin ranking system designed to test c++ battleship submissions battleship.dunkirk.sh
at main 3.6 kB view raw
1package server 2 3import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "time" 8 9 "github.com/alexandrevicenzi/go-sse" 10 11 "battleship-arena/internal/storage" 12) 13 14var SSEServer *sse.Server 15 16type ProgressUpdate struct { 17 Type string `json:"type"` 18 Player string `json:"player,omitempty"` 19 Opponent string `json:"opponent,omitempty"` 20 CurrentMatch int `json:"current_match,omitempty"` 21 TotalMatches int `json:"total_matches,omitempty"` 22 EstimatedTimeLeft string `json:"estimated_time_left,omitempty"` 23 PercentComplete float64 `json:"percent_complete,omitempty"` 24 QueuedPlayers []string `json:"queued_players,omitempty"` 25 Status string `json:"status,omitempty"` 26 FailureMessage string `json:"failure_message,omitempty"` 27} 28 29func InitSSE() { 30 // Disable verbose SSE library logging 31 SSEServer = sse.NewServer(nil) 32} 33 34func NotifyLeaderboardUpdate() { 35 entries, err := storage.GetLeaderboard(50) 36 if err != nil { 37 log.Printf("SSE: failed to get leaderboard: %v", err) 38 return 39 } 40 41 data, err := json.Marshal(entries) 42 if err != nil { 43 log.Printf("SSE: failed to marshal leaderboard: %v", err) 44 return 45 } 46 47 SSEServer.SendMessage("/events/updates", sse.SimpleMessage(string(data))) 48} 49 50func BroadcastProgress(player string, currentMatch, totalMatches int, startTime time.Time, queuedPlayers []string) { 51 elapsed := time.Since(startTime) 52 avgTimePerMatch := elapsed / time.Duration(currentMatch) 53 remainingMatches := totalMatches - currentMatch 54 estimatedTimeLeft := avgTimePerMatch * time.Duration(remainingMatches) 55 56 percentComplete := float64(currentMatch) / float64(totalMatches) * 100.0 57 timeLeftStr := formatDuration(estimatedTimeLeft) 58 59 filteredQueue := make([]string, 0) 60 for _, p := range queuedPlayers { 61 if p != player { 62 filteredQueue = append(filteredQueue, p) 63 } 64 } 65 66 progress := ProgressUpdate{ 67 Type: "progress", 68 Player: player, 69 CurrentMatch: currentMatch, 70 TotalMatches: totalMatches, 71 EstimatedTimeLeft: timeLeftStr, 72 PercentComplete: percentComplete, 73 QueuedPlayers: filteredQueue, 74 } 75 76 data, err := json.Marshal(progress) 77 if err != nil { 78 log.Printf("Failed to marshal progress: %v", err) 79 return 80 } 81 82 // Only log every 10th match to reduce noise 83 if currentMatch%10 == 0 || currentMatch == totalMatches { 84 log.Printf("Progress: %s [%d/%d] %.0f%%", player, currentMatch, totalMatches, percentComplete) 85 } 86 87 SSEServer.SendMessage("/events/updates", sse.SimpleMessage(string(data))) 88} 89 90func formatDuration(d time.Duration) string { 91 if d < time.Minute { 92 return "< 1 min" 93 } 94 minutes := int(d.Minutes()) 95 if minutes < 60 { 96 return fmt.Sprintf("%d min", minutes) 97 } 98 hours := minutes / 60 99 mins := minutes % 60 100 if mins > 0 { 101 return fmt.Sprintf("%dh %dm", hours, mins) 102 } 103 return fmt.Sprintf("%dh", hours) 104} 105 106func BroadcastProgressComplete() { 107 complete := ProgressUpdate{ 108 Type: "complete", 109 } 110 111 data, err := json.Marshal(complete) 112 if err != nil { 113 return 114 } 115 116 // Silent - no log needed for routine completion 117 SSEServer.SendMessage("/events/updates", sse.SimpleMessage(string(data))) 118} 119 120func BroadcastStatusUpdate(player, status, failureMessage string) { 121 update := ProgressUpdate{ 122 Type: "status", 123 Player: player, 124 Status: status, 125 FailureMessage: failureMessage, 126 } 127 128 data, err := json.Marshal(update) 129 if err != nil { 130 log.Printf("Failed to marshal status update: %v", err) 131 return 132 } 133 134 SSEServer.SendMessage("/events/updates", sse.SimpleMessage(string(data))) 135}