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