a geicko-2 based round robin ranking system designed to test c++ battleship submissions
battleship.dunkirk.sh
1package main
2
3import (
4 "database/sql"
5 "time"
6
7 _ "github.com/mattn/go-sqlite3"
8)
9
10var globalDB *sql.DB
11
12type LeaderboardEntry struct {
13 Username string
14 Wins int
15 Losses int
16 AvgMoves float64
17 LastPlayed time.Time
18}
19
20type Submission struct {
21 ID int
22 Username string
23 Filename string
24 UploadTime time.Time
25 Status string // pending, testing, completed, failed
26}
27
28func initDB(path string) (*sql.DB, error) {
29 db, err := sql.Open("sqlite3", path+"?parseTime=true")
30 if err != nil {
31 return nil, err
32 }
33
34 schema := `
35 CREATE TABLE IF NOT EXISTS submissions (
36 id INTEGER PRIMARY KEY AUTOINCREMENT,
37 username TEXT NOT NULL,
38 filename TEXT NOT NULL,
39 upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
40 status TEXT DEFAULT 'pending',
41 is_active BOOLEAN DEFAULT 1
42 );
43
44 CREATE TABLE IF NOT EXISTS matches (
45 id INTEGER PRIMARY KEY AUTOINCREMENT,
46 player1_id INTEGER,
47 player2_id INTEGER,
48 winner_id INTEGER,
49 player1_moves INTEGER,
50 player2_moves INTEGER,
51 timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
52 FOREIGN KEY (player1_id) REFERENCES submissions(id),
53 FOREIGN KEY (player2_id) REFERENCES submissions(id),
54 FOREIGN KEY (winner_id) REFERENCES submissions(id)
55 );
56
57 CREATE INDEX IF NOT EXISTS idx_matches_player1 ON matches(player1_id);
58 CREATE INDEX IF NOT EXISTS idx_matches_player2 ON matches(player2_id);
59 CREATE INDEX IF NOT EXISTS idx_submissions_username ON submissions(username);
60 CREATE INDEX IF NOT EXISTS idx_submissions_status ON submissions(status);
61 CREATE INDEX IF NOT EXISTS idx_submissions_active ON submissions(is_active);
62 `
63
64 _, err = db.Exec(schema)
65 return db, err
66}
67
68func getLeaderboard(limit int) ([]LeaderboardEntry, error) {
69 query := `
70 SELECT
71 s.username,
72 COUNT(CASE WHEN m.winner_id = s.id THEN 1 END) as wins,
73 COUNT(CASE WHEN (m.player1_id = s.id OR m.player2_id = s.id) AND m.winner_id != s.id THEN 1 END) as losses,
74 AVG(CASE WHEN m.player1_id = s.id THEN m.player1_moves ELSE m.player2_moves END) as avg_moves,
75 MAX(m.timestamp) as last_played
76 FROM submissions s
77 LEFT JOIN matches m ON (m.player1_id = s.id OR m.player2_id = s.id)
78 WHERE s.is_active = 1
79 GROUP BY s.username
80 HAVING COUNT(m.id) > 0
81 ORDER BY wins DESC, losses ASC, avg_moves ASC
82 LIMIT ?
83 `
84
85 rows, err := globalDB.Query(query, limit)
86 if err != nil {
87 return nil, err
88 }
89 defer rows.Close()
90
91 var entries []LeaderboardEntry
92 for rows.Next() {
93 var e LeaderboardEntry
94 var lastPlayed string
95 err := rows.Scan(&e.Username, &e.Wins, &e.Losses, &e.AvgMoves, &lastPlayed)
96 if err != nil {
97 return nil, err
98 }
99
100 // Parse the timestamp string
101 e.LastPlayed, _ = time.Parse("2006-01-02 15:04:05", lastPlayed)
102
103 entries = append(entries, e)
104 }
105
106 return entries, rows.Err()
107}
108
109func addSubmission(username, filename string) (int64, error) {
110 // Mark old submission as inactive
111 _, err := globalDB.Exec(
112 "UPDATE submissions SET is_active = 0 WHERE username = ?",
113 username,
114 )
115 if err != nil {
116 return 0, err
117 }
118
119 // Insert new submission
120 result, err := globalDB.Exec(
121 "INSERT INTO submissions (username, filename, is_active) VALUES (?, ?, 1)",
122 username, filename,
123 )
124 if err != nil {
125 return 0, err
126 }
127 return result.LastInsertId()
128}
129
130func addMatch(player1ID, player2ID, winnerID, player1Moves, player2Moves int) error {
131 _, err := globalDB.Exec(
132 "INSERT INTO matches (player1_id, player2_id, winner_id, player1_moves, player2_moves) VALUES (?, ?, ?, ?, ?)",
133 player1ID, player2ID, winnerID, player1Moves, player2Moves,
134 )
135 return err
136}
137
138func updateSubmissionStatus(id int, status string) error {
139 _, err := globalDB.Exec("UPDATE submissions SET status = ? WHERE id = ?", status, id)
140 return err
141}
142
143func getPendingSubmissions() ([]Submission, error) {
144 rows, err := globalDB.Query(
145 "SELECT id, username, filename, upload_time, status FROM submissions WHERE status = 'pending' AND is_active = 1 ORDER BY upload_time",
146 )
147 if err != nil {
148 return nil, err
149 }
150 defer rows.Close()
151
152 var submissions []Submission
153 for rows.Next() {
154 var s Submission
155 err := rows.Scan(&s.ID, &s.Username, &s.Filename, &s.UploadTime, &s.Status)
156 if err != nil {
157 return nil, err
158 }
159 submissions = append(submissions, s)
160 }
161
162 return submissions, rows.Err()
163}
164
165func getActiveSubmissions() ([]Submission, error) {
166 rows, err := globalDB.Query(
167 "SELECT id, username, filename, upload_time, status FROM submissions WHERE is_active = 1 AND status = 'completed' ORDER BY username",
168 )
169 if err != nil {
170 return nil, err
171 }
172 defer rows.Close()
173
174 var submissions []Submission
175 for rows.Next() {
176 var s Submission
177 err := rows.Scan(&s.ID, &s.Username, &s.Filename, &s.UploadTime, &s.Status)
178 if err != nil {
179 return nil, err
180 }
181 submissions = append(submissions, s)
182 }
183
184 return submissions, rows.Err()
185}
186
187func getUserSubmissions(username string) ([]Submission, error) {
188 rows, err := globalDB.Query(
189 "SELECT id, username, filename, upload_time, status FROM submissions WHERE username = ? ORDER BY upload_time DESC LIMIT 10",
190 username,
191 )
192 if err != nil {
193 return nil, err
194 }
195 defer rows.Close()
196
197 var submissions []Submission
198 for rows.Next() {
199 var s Submission
200 err := rows.Scan(&s.ID, &s.Username, &s.Filename, &s.UploadTime, &s.Status)
201 if err != nil {
202 return nil, err
203 }
204 submissions = append(submissions, s)
205 }
206
207 return submissions, rows.Err()
208}
209
210
211type MatchResult struct {
212 Player1Username string
213 Player2Username string
214 WinnerUsername string
215 AvgMoves int
216}
217
218func getAllMatches() ([]MatchResult, error) {
219 query := `
220 SELECT
221 s1.username as player1,
222 s2.username as player2,
223 sw.username as winner,
224 m.player1_moves as avg_moves
225 FROM matches m
226 JOIN submissions s1 ON m.player1_id = s1.id
227 JOIN submissions s2 ON m.player2_id = s2.id
228 JOIN submissions sw ON m.winner_id = sw.id
229 WHERE s1.is_active = 1 AND s2.is_active = 1
230 ORDER BY m.timestamp DESC
231 `
232
233 rows, err := globalDB.Query(query)
234 if err != nil {
235 return nil, err
236 }
237 defer rows.Close()
238
239 var matches []MatchResult
240 for rows.Next() {
241 var m MatchResult
242 err := rows.Scan(&m.Player1Username, &m.Player2Username, &m.WinnerUsername, &m.AvgMoves)
243 if err != nil {
244 return nil, err
245 }
246 matches = append(matches, m)
247 }
248
249 return matches, rows.Err()
250}