a geicko-2 based round robin ranking system designed to test c++ battleship submissions battleship.dunkirk.sh

feat: add compilation failed status

dunkirk.sh 5ffbdc8c 8124d890

verified
Changed files
+50 -16
internal
runner
server
storage
+1
internal/runner/runner.go
···
#define %s
#include "memory.h"
+
#include "battleship_light.h"
#include <string>
void initMemory%s(ComputerMemory &memory);
+2 -1
internal/runner/worker.go
···
if err := CompileSubmission(sub, uploadDir); err != nil {
log.Printf("❌ Compilation failed for %s: %v", sub.Username, err)
-
storage.UpdateSubmissionStatus(sub.ID, "failed")
+
storage.UpdateSubmissionStatus(sub.ID, "compilation_failed")
+
notifyFunc()
continue
}
+22 -9
internal/server/web.go
···
color: #64748b !important;
}
+
tbody tr.broken {
+
opacity: 0.6;
+
color: #ef4444;
+
}
+
+
tbody tr.broken .player-name {
+
color: #f87171;
+
}
+
+
tbody tr.broken .rank {
+
color: #ef4444 !important;
+
}
+
tbody tr:hover {
background: rgba(59, 130, 246, 0.05);
}
···
<tbody>
{{if .Entries}}
{{range $i, $e := .Entries}}
-
<tr{{if $e.IsPending}} class="pending"{{end}}>
-
<td class="rank rank-{{add $i 1}}">{{if $e.IsPending}}⏳{{else if lt $i 3}}{{medal $i}}{{else}}{{add $i 1}}{{end}}</td>
-
<td class="player-name"><a href="/user/{{$e.Username}}" style="color: inherit; text-decoration: none;">{{$e.Username}}{{if $e.IsPending}} <span style="font-size: 0.8em;">(pending)</span>{{end}}</a></td>
-
<td>{{if $e.IsPending}}-{{else}}<strong>{{$e.Rating}}</strong> <span style="color: #94a3b8; font-size: 0.85em;">±{{$e.RD}}</span>{{end}}</td>
-
<td>{{if $e.IsPending}}-{{else}}{{$e.Wins}}{{end}}</td>
-
<td>{{if $e.IsPending}}-{{else}}{{$e.Losses}}{{end}}</td>
-
<td>{{if $e.IsPending}}-{{else}}<span class="win-rate {{winRateClass $e}}">{{winRate $e}}%</span>{{end}}</td>
-
<td>{{if $e.IsPending}}-{{else}}{{printf "%.1f" $e.AvgMoves}}{{end}}</td>
-
<td style="color: #64748b;">{{if $e.IsPending}}Waiting...{{else}}{{$e.LastPlayed.Format "Jan 2, 3:04 PM"}}{{end}}</td>
+
<tr{{if $e.IsPending}} class="pending"{{else if $e.IsBroken}} class="broken"{{end}}>
+
<td class="rank rank-{{add $i 1}}">{{if $e.IsBroken}}💥{{else if $e.IsPending}}⏳{{else if lt $i 3}}{{medal $i}}{{else}}{{add $i 1}}{{end}}</td>
+
<td class="player-name"><a href="/user/{{$e.Username}}" style="color: inherit; text-decoration: none;">{{$e.Username}}{{if $e.IsPending}} <span style="font-size: 0.8em;">(pending)</span>{{else if $e.IsBroken}} <span style="font-size: 0.8em; color: #ef4444;">(compilation failed)</span>{{end}}</a></td>
+
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}<strong>{{$e.Rating}}</strong> <span style="color: #94a3b8; font-size: 0.85em;">±{{$e.RD}}</span>{{end}}</td>
+
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}{{$e.Wins}}{{end}}</td>
+
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}{{$e.Losses}}{{end}}</td>
+
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}<span class="win-rate {{winRateClass $e}}">{{winRate $e}}%</span>{{end}}</td>
+
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}{{printf "%.1f" $e.AvgMoves}}{{end}}</td>
+
<td style="color: #64748b;">{{if $e.IsPending}}Waiting...{{else if $e.IsBroken}}Failed{{else}}{{$e.LastPlayed.Format "Jan 2, 3:04 PM"}}{{end}}</td>
</tr>
{{end}}
{{else}}
+25 -6
internal/storage/database.go
···
Stage string
LastPlayed time.Time
IsPending bool
+
IsBroken bool
}
type Submission struct {
···
SUM(CASE WHEN m.player1_id = s.id THEN m.player2_wins WHEN m.player2_id = s.id THEN m.player1_wins ELSE 0 END) as total_losses,
AVG(CASE WHEN m.player1_id = s.id THEN m.player1_moves ELSE m.player2_moves END) as avg_moves,
MAX(m.timestamp) as last_played,
-
0 as is_pending
+
0 as is_pending,
+
0 as is_broken
FROM submissions s
LEFT JOIN matches m ON (m.player1_id = s.id OR m.player2_id = s.id) AND m.is_valid = 1
-
WHERE s.is_active = 1
+
WHERE s.is_active = 1 AND s.status NOT IN ('compilation_failed')
GROUP BY s.username, s.glicko_rating, s.glicko_rd
HAVING COUNT(m.id) > 0
···
0 as total_losses,
0.0 as avg_moves,
s.upload_time as last_played,
-
1 as is_pending
+
1 as is_pending,
+
0 as is_broken
FROM submissions s
LEFT JOIN matches m ON (m.player1_id = s.id OR m.player2_id = s.id) AND m.is_valid = 1
WHERE s.is_active = 1 AND s.status IN ('pending', 'testing', 'completed')
GROUP BY s.username, s.upload_time
HAVING COUNT(m.id) = 0
-
ORDER BY is_pending ASC, rating DESC, total_wins DESC
+
UNION ALL
+
+
SELECT
+
s.username,
+
0 as rating,
+
0 as rd,
+
0 as total_wins,
+
0 as total_losses,
+
0.0 as avg_moves,
+
s.upload_time as last_played,
+
0 as is_pending,
+
1 as is_broken
+
FROM submissions s
+
WHERE s.is_active = 1 AND s.status = 'compilation_failed'
+
+
ORDER BY is_broken ASC, is_pending ASC, rating DESC, total_wins DESC
LIMIT ?
`
···
var e LeaderboardEntry
var lastPlayed string
var rating, rd float64
-
var isPending int
-
err := rows.Scan(&e.Username, &rating, &rd, &e.Wins, &e.Losses, &e.AvgMoves, &lastPlayed, &isPending)
+
var isPending, isBroken int
+
err := rows.Scan(&e.Username, &rating, &rd, &e.Wins, &e.Losses, &e.AvgMoves, &lastPlayed, &isPending, &isBroken)
if err != nil {
return nil, err
}
···
e.Rating = int(rating)
e.RD = int(rd)
e.IsPending = isPending == 1
+
e.IsBroken = isBroken == 1
totalGames := e.Wins + e.Losses
if totalGames > 0 {