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

feat: add syntax highlighting to the codeblocks

dunkirk.sh 426b1936 df276ad0

verified
+126 -16
+126 -16
internal/server/web.go
··· 5 5 "fmt" 6 6 "html/template" 7 7 "net/http" 8 - 8 + 9 9 "github.com/go-chi/chi/v5" 10 - 10 + 11 11 "battleship-arena/internal/storage" 12 12 ) 13 13 ··· 256 256 257 257 code { 258 258 background: #0f172a; 259 - padding: 0.375rem 0.75rem; 260 - border-radius: 0.375rem; 259 + padding: 0.5rem 0.875rem; 260 + border-radius: 0.5rem; 261 261 font-family: 'Monaco', 'Courier New', monospace; 262 262 font-size: 0.875rem; 263 - color: #3b82f6; 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; 264 343 } 265 344 266 345 .empty-state { ··· 538 617 window.addEventListener('DOMContentLoaded', () => { 539 618 connectSSE(); 540 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 + } 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 - <p><code>ssh -p 2222 username@{{.ServerURL}}</code></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 + 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 - <p><code>scp -P 2222 memory_functions_yourname.cpp username@{{.ServerURL}}:~/</code></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> 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 - 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 - 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 - 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 - 866 + 756 867 if err != nil { 757 868 http.Error(w, "Player not found", http.StatusNotFound) 758 869 return 759 870 } 760 - 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 - 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 - 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 -