tangled
alpha
login
or
join now
dunkirk.sh
/
battleship-arena
1
fork
atom
a geicko-2 based round robin ranking system designed to test c++ battleship submissions
battleship.dunkirk.sh
1
fork
atom
overview
issues
pulls
pipelines
feat: add syntax highlighting to the codeblocks
dunkirk.sh
3 months ago
426b1936
df276ad0
verified
This commit was signed with the committer's
known signature
.
dunkirk.sh
SSH Key Fingerprint:
SHA256:DqcG0RXYExE26KiWo3VxJnsxswN1QNfTBvB+bdSpk80=
+126
-16
1 changed file
expand all
collapse all
unified
split
internal
server
web.go
+126
-16
internal/server/web.go
···
5
5
"fmt"
6
6
"html/template"
7
7
"net/http"
8
8
-
8
8
+
9
9
"github.com/go-chi/chi/v5"
10
10
-
10
10
+
11
11
"battleship-arena/internal/storage"
12
12
)
13
13
···
256
256
257
257
code {
258
258
background: #0f172a;
259
259
-
padding: 0.375rem 0.75rem;
260
260
-
border-radius: 0.375rem;
259
259
+
padding: 0.5rem 0.875rem;
260
260
+
border-radius: 0.5rem;
261
261
font-family: 'Monaco', 'Courier New', monospace;
262
262
font-size: 0.875rem;
263
263
-
color: #3b82f6;
263
263
+
color: #60a5fa;
264
264
+
border: 1px solid #1e3a8a;
265
265
+
display: inline-block;
266
266
+
line-height: 1.5;
267
267
+
}
268
268
+
269
269
+
.code-block {
270
270
+
position: relative;
271
271
+
background: #0f172a;
272
272
+
border: 1px solid #1e3a8a;
273
273
+
border-radius: 0.5rem;
274
274
+
margin: 1rem 0;
275
275
+
overflow: hidden;
276
276
+
}
277
277
+
278
278
+
.code-block-header {
279
279
+
background: #1e3a8a;
280
280
+
padding: 0.5rem 1rem;
281
281
+
display: flex;
282
282
+
justify-content: space-between;
283
283
+
align-items: center;
284
284
+
border-bottom: 1px solid #1e3a8a;
285
285
+
}
286
286
+
287
287
+
.code-block-lang {
288
288
+
color: #94a3b8;
289
289
+
font-size: 0.75rem;
290
290
+
font-weight: 600;
291
291
+
text-transform: uppercase;
292
292
+
letter-spacing: 0.05em;
293
293
+
}
294
294
+
295
295
+
.code-block-copy {
296
296
+
background: #3b82f6;
297
297
+
color: white;
298
298
+
border: none;
299
299
+
padding: 0.25rem 0.75rem;
300
300
+
border-radius: 0.25rem;
301
301
+
font-size: 0.75rem;
302
302
+
cursor: pointer;
303
303
+
transition: background 0.2s;
304
304
+
}
305
305
+
306
306
+
.code-block-copy:hover {
307
307
+
background: #2563eb;
308
308
+
}
309
309
+
310
310
+
.code-block-copy.copied {
311
311
+
background: #10b981;
312
312
+
}
313
313
+
314
314
+
.code-block pre {
315
315
+
margin: 0;
316
316
+
padding: 1rem;
317
317
+
overflow-x: auto;
318
318
+
}
319
319
+
320
320
+
.code-block code {
321
321
+
background: transparent;
322
322
+
border: none;
323
323
+
padding: 0;
324
324
+
display: block;
325
325
+
color: #e2e8f0;
326
326
+
}
327
327
+
328
328
+
.code-block .token-command {
329
329
+
color: #60a5fa;
330
330
+
}
331
331
+
332
332
+
.code-block .token-flag {
333
333
+
color: #a78bfa;
334
334
+
}
335
335
+
336
336
+
.code-block .token-string {
337
337
+
color: #34d399;
338
338
+
}
339
339
+
340
340
+
.code-block .token-comment {
341
341
+
color: #64748b;
342
342
+
font-style: italic;
264
343
}
265
344
266
345
.empty-state {
···
538
617
window.addEventListener('DOMContentLoaded', () => {
539
618
connectSSE();
540
619
});
620
620
+
621
621
+
function copyCode(button, text) {
622
622
+
// Decode HTML entities in template variables
623
623
+
const tempDiv = document.createElement('div');
624
624
+
tempDiv.innerHTML = text;
625
625
+
const decodedText = tempDiv.textContent || tempDiv.innerText;
626
626
+
627
627
+
navigator.clipboard.writeText(decodedText).then(() => {
628
628
+
const originalText = button.textContent;
629
629
+
button.textContent = 'Copied!';
630
630
+
button.classList.add('copied');
631
631
+
setTimeout(() => {
632
632
+
button.textContent = originalText;
633
633
+
button.classList.remove('copied');
634
634
+
}, 2000);
635
635
+
}).catch(err => {
636
636
+
console.error('Failed to copy:', err);
637
637
+
});
638
638
+
}
541
639
</script>
542
640
</head>
543
641
<body>
···
611
709
<div class="info-card">
612
710
<h3>📤 How to Submit</h3>
613
711
<p><strong>First time?</strong> Connect via SSH to create your account:</p>
614
614
-
<p><code>ssh -p 2222 username@{{.ServerURL}}</code></p>
712
712
+
713
713
+
<div class="code-block">
714
714
+
<div class="code-block-header">
715
715
+
<span class="code-block-lang">bash</span>
716
716
+
<button class="code-block-copy" onclick="copyCode(this, 'ssh -p 2222 {{.ServerURL}}')">Copy</button>
717
717
+
</div>
718
718
+
<pre><code><span class="token-command">ssh</span> <span class="token-flag">-p</span> <span class="token-string">2222</span> <span class="token-string">{{.ServerURL}}</span></code></pre>
719
719
+
</div>
720
720
+
615
721
<p style="margin-top: 0.5rem; color: #94a3b8;">You'll be prompted for your name, bio, and link. Your SSH key will be registered.</p>
616
722
617
723
<p style="margin-top: 1rem;"><strong>Upload your AI:</strong></p>
618
618
-
<p><code>scp -P 2222 memory_functions_yourname.cpp username@{{.ServerURL}}:~/</code></p>
724
724
+
725
725
+
<div class="code-block">
726
726
+
<div class="code-block-header">
727
727
+
<span class="code-block-lang">bash</span>
728
728
+
<button class="code-block-copy" onclick="copyCode(this, 'scp -P 2222 memory_functions_yourname.cpp {{.ServerURL}}:~/')">Copy</button>
729
729
+
</div>
730
730
+
<pre><code><span class="token-command">scp</span> <span class="token-flag">-P</span> <span class="token-string">2222</span> <span class="token-string">memory_functions_yourname.cpp</span> <span class="token-string">{{.ServerURL}}:~/</span></code></pre>
731
731
+
</div>
619
732
620
733
<p style="margin-top: 1rem; color: #94a3b8;">
621
734
<a href="/users" style="color: #60a5fa;">View all players →</a>
···
687
800
if entries == nil {
688
801
entries = []storage.LeaderboardEntry{}
689
802
}
690
690
-
803
803
+
691
804
// Get matches for bracket
692
805
matches, err := storage.GetAllMatches()
693
806
if err != nil {
···
729
842
json.NewEncoder(w).Encode(entries)
730
843
}
731
844
732
732
-
733
733
-
734
845
func calculateTotalGames(entries []storage.LeaderboardEntry) int {
735
846
total := 0
736
847
for _, e := range entries {
···
745
856
http.Error(w, "Username required", http.StatusBadRequest)
746
857
return
747
858
}
748
748
-
859
859
+
749
860
// Get submission ID for this username
750
861
var submissionID int
751
862
err := storage.DB.QueryRow(
752
863
"SELECT id FROM submissions WHERE username = ? AND is_active = 1",
753
864
username,
754
865
).Scan(&submissionID)
755
755
-
866
866
+
756
867
if err != nil {
757
868
http.Error(w, "Player not found", http.StatusNotFound)
758
869
return
759
870
}
760
760
-
871
871
+
761
872
// Get rating history
762
873
history, err := storage.GetRatingHistory(submissionID)
763
874
if err != nil {
764
875
http.Error(w, fmt.Sprintf("Failed to get rating history: %v", err), http.StatusInternalServerError)
765
876
return
766
877
}
767
767
-
878
878
+
768
879
w.Header().Set("Content-Type", "application/json")
769
880
json.NewEncoder(w).Encode(history)
770
881
}
···
775
886
http.Redirect(w, r, "/", http.StatusSeeOther)
776
887
return
777
888
}
778
778
-
889
889
+
779
890
tmpl := template.Must(template.New("player").Parse(playerPageHTML))
780
891
tmpl.Execute(w, map[string]string{"Username": username})
781
892
}
···
1036
1147
</body>
1037
1148
</html>
1038
1149
`
1039
1039
-