Create templates/index.html
Browse files- templates/index.html +527 -0
templates/index.html
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 6 |
+
<title>Beautiful Go Game</title>
|
| 7 |
+
<style>
|
| 8 |
+
* { margin:0; padding:0; box-sizing:border-box; font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
|
| 9 |
+
:root { --cell-size: 40px; --board-size: 13; }
|
| 10 |
+
|
| 11 |
+
body { background:linear-gradient(135deg,#1a2a6c,#b21f1f,#1a2a6c); min-height:100vh; display:flex; flex-direction:column; align-items:center; padding:20px; color:#fff; }
|
| 12 |
+
.container { max-width:1200px; width:100%; display:flex; flex-direction:column; align-items:center; gap:20px; }
|
| 13 |
+
|
| 14 |
+
header { text-align:center; padding:20px; width:100%; background:rgba(0,0,0,0.3); border-radius:15px; box-shadow:0 8px 32px rgba(0,0,0,0.3); backdrop-filter:blur(10px); border:1px solid rgba(255,255,255,0.1); }
|
| 15 |
+
h1 { font-size:2.8rem; margin-bottom:10px; text-shadow:0 0 10px rgba(255,255,255,0.5); background:linear-gradient(to right,#ffd700,#ffffff); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
|
| 16 |
+
.subtitle { font-size:1.2rem; opacity:0.9; margin-bottom:15px; }
|
| 17 |
+
|
| 18 |
+
.game-info { display:flex; justify-content:space-between; width:100%; gap:20px; margin:10px 0; }
|
| 19 |
+
.player-info { background:rgba(0,0,0,0.4); padding:15px; border-radius:10px; flex:1; text-align:center; box-shadow:0 4px 15px rgba(0,0,0,0.2); }
|
| 20 |
+
.player-info.active { background:rgba(46,204,113,0.3); border:2px solid #2ecc71; transform:scale(1.05); transition:all .3s ease; }
|
| 21 |
+
.player-name { font-size:1.5rem; font-weight:bold; margin-bottom:8px; word-break:break-word; }
|
| 22 |
+
.player-stats { display:flex; justify-content:space-around; margin-top:10px; }
|
| 23 |
+
.stat { text-align:center; }
|
| 24 |
+
.stat-value { font-size:1.8rem; font-weight:bold; }
|
| 25 |
+
.stat-label { font-size:0.9rem; opacity:0.8; }
|
| 26 |
+
|
| 27 |
+
.game-controls { display:flex; gap:15px; margin:15px 0; flex-wrap:wrap; justify-content:center; }
|
| 28 |
+
button { padding:12px 25px; font-size:1rem; font-weight:bold; border:none; border-radius:50px; cursor:pointer; background:linear-gradient(to right,#3498db,#2980b9); color:#fff; box-shadow:0 4px 10px rgba(0,0,0,0.3); transition:all .3s ease; }
|
| 29 |
+
button:hover { transform:translateY(-3px); box-shadow:0 6px 15px rgba(0,0,0,0.4); }
|
| 30 |
+
button:active { transform:translateY(1px); }
|
| 31 |
+
#new-game { background:linear-gradient(to right,#2ecc71,#27ae60); }
|
| 32 |
+
#pass-turn { background:linear-gradient(to right,#f39c12,#d35400); }
|
| 33 |
+
#resign { background:linear-gradient(to right,#e74c3c,#c0392b); }
|
| 34 |
+
|
| 35 |
+
.board-container { position:relative; background:#dcb35c; padding:20px; border-radius:10px; box-shadow:0 15px 35px rgba(0,0,0,0.5); border:8px solid #8b4513; }
|
| 36 |
+
#go-board { --w: calc(var(--board-size) * var(--cell-size)); display:grid; grid-template-columns:repeat(var(--board-size),var(--cell-size)); grid-template-rows:repeat(var(--board-size),var(--cell-size)); width:var(--w); height:var(--w); gap:0; position:relative; }
|
| 37 |
+
.cell { width:var(--cell-size); height:var(--cell-size); position:relative; display:flex; justify-content:center; align-items:center; }
|
| 38 |
+
.grid-line { position:absolute; background:#000; pointer-events:none; }
|
| 39 |
+
.horizontal { width:100%; height:1px; top:50%; }
|
| 40 |
+
.vertical { width:1px; height:100%; left:50%; }
|
| 41 |
+
|
| 42 |
+
.star-point { position:absolute; width:8px; height:8px; background:#000; border-radius:50%; z-index:1; }
|
| 43 |
+
|
| 44 |
+
.stone { width:36px; height:36px; border-radius:50%; position:absolute; z-index:2; box-shadow:0 3px 5px rgba(0,0,0,0.5); transition:transform .2s ease; }
|
| 45 |
+
.stone:hover { transform:scale(1.1); }
|
| 46 |
+
.black { background:radial-gradient(circle at 30% 30%, #555, #000); border:1px solid #222; }
|
| 47 |
+
.white { background:radial-gradient(circle at 30% 30%, #fff, #ddd); border:1px solid #999; }
|
| 48 |
+
.last-move { box-shadow:0 0 0 3px #ff5722; }
|
| 49 |
+
|
| 50 |
+
.game-status { background:rgba(0,0,0,0.4); padding:15px; border-radius:10px; text-align:center; font-size:1.2rem; width:100%; box-shadow:0 4px 15px rgba(0,0,0,0.2); }
|
| 51 |
+
.score-display { display:flex; justify-content:center; gap:30px; margin-top:10px; }
|
| 52 |
+
.score-item { display:flex; align-items:center; gap:8px; }
|
| 53 |
+
.score-stone { width:20px; height:20px; border-radius:50%; }
|
| 54 |
+
.score-stone.black { background:radial-gradient(circle at 30% 30%, #555, #000); }
|
| 55 |
+
.score-stone.white { background:radial-gradient(circle at 30% 30%, #fff, #ddd); border:1px solid #999; }
|
| 56 |
+
|
| 57 |
+
.instructions { background:rgba(0,0,0,0.3); padding:20px; border-radius:10px; max-width:800px; margin-top:20px; box-shadow:0 4px 15px rgba(0,0,0,0.2); }
|
| 58 |
+
.instructions h2 { margin-bottom:15px; color:#ffd700; text-align:center; }
|
| 59 |
+
.instructions ul { padding-left:20px; }
|
| 60 |
+
.instructions li { margin-bottom:10px; line-height:1.5; }
|
| 61 |
+
|
| 62 |
+
.winner-message { position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); display:flex; justify-content:center; align-items:center; z-index:100; opacity:0; pointer-events:none; transition:opacity .5s ease; }
|
| 63 |
+
.winner-message.show { opacity:1; pointer-events:all; }
|
| 64 |
+
.winner-content { background:linear-gradient(135deg,#1a2a6c,#b21f1f); padding:40px; border-radius:20px; text-align:center; max-width:500px; width:90%; box-shadow:0 0 50px rgba(255,215,0,0.5); border:3px solid gold; }
|
| 65 |
+
.winner-content h2 { font-size:2.5rem; margin-bottom:20px; color:gold; }
|
| 66 |
+
.winner-content p { font-size:1.5rem; margin-bottom:30px; }
|
| 67 |
+
|
| 68 |
+
.signin-overlay { position:fixed; inset:0; background:rgba(0,0,0,0.85); display:flex; align-items:center; justify-content:center; z-index:200; }
|
| 69 |
+
.card { background:linear-gradient(135deg,#1a2a6c,#b21f1f); padding:28px; border-radius:16px; width:90%; max-width:520px; border:2px solid rgba(255,255,255,0.2); box-shadow:0 10px 30px rgba(0,0,0,0.5); }
|
| 70 |
+
.card h3 { margin-bottom:12px; text-align:center; }
|
| 71 |
+
.row { display:flex; gap:10px; margin:10px 0 18px; flex-wrap:wrap; }
|
| 72 |
+
.row input { flex:1; min-width:160px; padding:12px 14px; border-radius:8px; border:1px solid rgba(255,255,255,0.3); background:rgba(0,0,0,0.3); color:#fff; outline:none; }
|
| 73 |
+
.row button { flex:0 0 auto; }
|
| 74 |
+
|
| 75 |
+
/* Inline color picker (replaces popup) */
|
| 76 |
+
.color-inline { display:none; align-items:center; justify-content:center; gap:12px; background:rgba(0,0,0,0.3); padding:10px 14px; border-radius:12px; }
|
| 77 |
+
.color-inline h3 { margin-right:10px; font-size:1rem; font-weight:600; }
|
| 78 |
+
.colors { display:flex; gap:12px; }
|
| 79 |
+
.color-btn { flex:1; padding:12px; border-radius:10px; border:none; cursor:pointer; font-weight:700; }
|
| 80 |
+
.color-black { background:#222; color:#fff; }
|
| 81 |
+
.color-white { background:#eee; color:#111; }
|
| 82 |
+
.disabled { opacity:0.5; pointer-events:none; }
|
| 83 |
+
|
| 84 |
+
@media (max-width:768px) {
|
| 85 |
+
.game-info { flex-direction:column; }
|
| 86 |
+
:root { --cell-size:30px; }
|
| 87 |
+
.stone { width:26px; height:26px; }
|
| 88 |
+
h1 { font-size:2rem; }
|
| 89 |
+
.color-inline { flex-direction:column; align-items:stretch; }
|
| 90 |
+
}
|
| 91 |
+
</style>
|
| 92 |
+
</head>
|
| 93 |
+
<body>
|
| 94 |
+
<div class="container">
|
| 95 |
+
<header>
|
| 96 |
+
<h1>Beautiful Go Game</h1>
|
| 97 |
+
<p class="subtitle">A strategic board game for two players</p>
|
| 98 |
+
</header>
|
| 99 |
+
|
| 100 |
+
<div class="game-info">
|
| 101 |
+
<div class="player-info" id="pBlack">
|
| 102 |
+
<div class="player-name" id="name-black">Black</div>
|
| 103 |
+
<div class="player-stats">
|
| 104 |
+
<div class="stat"><div class="stat-value" id="wins-black">0</div><div class="stat-label">Wins</div></div>
|
| 105 |
+
<div class="stat"><div class="stat-value" id="caps-black">0</div><div class="stat-label">Captures</div></div>
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
<div class="player-info active" id="pWhite">
|
| 109 |
+
<div class="player-name" id="name-white">White</div>
|
| 110 |
+
<div class="player-stats">
|
| 111 |
+
<div class="stat"><div class="stat-value" id="wins-white">0</div><div class="stat-label">Wins</div></div>
|
| 112 |
+
<div class="stat"><div class="stat-value" id="caps-white">0</div><div class="stat-label">Captures</div></div>
|
| 113 |
+
</div>
|
| 114 |
+
</div>
|
| 115 |
+
</div>
|
| 116 |
+
|
| 117 |
+
<div class="game-controls">
|
| 118 |
+
<button id="new-game">New Game</button>
|
| 119 |
+
<button id="pass-turn">Pass Turn</button>
|
| 120 |
+
<button id="resign">Resign</button>
|
| 121 |
+
<select id="board-size" title="Board size is controlled by the server">
|
| 122 |
+
<option value="9">9x9</option>
|
| 123 |
+
<option value="13" selected>13x13</option>
|
| 124 |
+
<option value="19">19x19</option>
|
| 125 |
+
</select>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
<!-- Inline color picker (no popup) -->
|
| 129 |
+
<div id="color-picker" class="color-inline" aria-live="polite">
|
| 130 |
+
<h3>Pick your color</h3>
|
| 131 |
+
<div class="colors">
|
| 132 |
+
<button id="pick-black" class="color-btn color-black">Play as Black</button>
|
| 133 |
+
<button id="pick-white" class="color-btn color-white">Play as White</button>
|
| 134 |
+
</div>
|
| 135 |
+
</div>
|
| 136 |
+
|
| 137 |
+
<div class="board-container"><div id="go-board"></div></div>
|
| 138 |
+
|
| 139 |
+
<div class="game-status">
|
| 140 |
+
<div>Current Player: <span id="player-turn">Black</span></div>
|
| 141 |
+
<div class="score-display">
|
| 142 |
+
<div class="score-item"><div class="score-stone black"></div><span id="score-black">0</span></div>
|
| 143 |
+
<div class="score-item"><div class="score-stone white"></div><span id="score-white">0</span></div>
|
| 144 |
+
</div>
|
| 145 |
+
</div>
|
| 146 |
+
|
| 147 |
+
<div class="instructions">
|
| 148 |
+
<h2>How to Play Go</h2>
|
| 149 |
+
<ul>
|
| 150 |
+
<li><strong>Objective:</strong> Surround more territory than your opponent</li>
|
| 151 |
+
<li><strong>Players:</strong> Black moves first, then White</li>
|
| 152 |
+
<li><strong>Moves:</strong> Place stones on empty intersections</li>
|
| 153 |
+
<li><strong>Capturing:</strong> Stones with no liberties are captured</li>
|
| 154 |
+
<li><strong>Pass:</strong> Skip your turn when you have no beneficial moves</li>
|
| 155 |
+
<li><strong>End:</strong> Game ends when both players pass consecutively</li>
|
| 156 |
+
</ul>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
|
| 160 |
+
<div class="winner-message" id="winner-overlay">
|
| 161 |
+
<div class="winner-content">
|
| 162 |
+
<h2>Game Over!</h2>
|
| 163 |
+
<p id="winner-text">Player wins by resignation!</p>
|
| 164 |
+
<button id="play-again">Play Again</button>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
|
| 168 |
+
<!-- Sign-in -->
|
| 169 |
+
<div class="signin-overlay" id="signin">
|
| 170 |
+
<div class="card">
|
| 171 |
+
<h3>Sign in</h3>
|
| 172 |
+
<div class="row">
|
| 173 |
+
<input id="username" placeholder="Enter your username" maxlength="24" />
|
| 174 |
+
<input id="password" type="password" placeholder="Enter the password" />
|
| 175 |
+
<button id="btn-signin">Continue</button>
|
| 176 |
+
</div>
|
| 177 |
+
<p style="opacity:.8;font-size:.9rem;text-align:center">Enter your username and the shared password to continue. After you click “New Game”, you’ll be asked to pick a color.</p>
|
| 178 |
+
</div>
|
| 179 |
+
</div>
|
| 180 |
+
|
| 181 |
+
<script src="https://cdn.socket.io/3.1.3/socket.io.min.js"></script>
|
| 182 |
+
<script>
|
| 183 |
+
var socket = io('/', { path:'/socket.io', transports:['polling'], upgrade:false });
|
| 184 |
+
|
| 185 |
+
// DOM
|
| 186 |
+
var boardEl = document.getElementById('go-board');
|
| 187 |
+
var pBlackEl = document.getElementById('pBlack');
|
| 188 |
+
var pWhiteEl = document.getElementById('pWhite');
|
| 189 |
+
var nameBlackEl = document.getElementById('name-black');
|
| 190 |
+
var nameWhiteEl = document.getElementById('name-white');
|
| 191 |
+
var winsBlackEl = document.getElementById('wins-black');
|
| 192 |
+
var winsWhiteEl = document.getElementById('wins-white');
|
| 193 |
+
var capsBlackEl = document.getElementById('caps-black');
|
| 194 |
+
var capsWhiteEl = document.getElementById('caps-white');
|
| 195 |
+
var scoreBlackEl = document.getElementById('score-black');
|
| 196 |
+
var scoreWhiteEl = document.getElementById('score-white');
|
| 197 |
+
var playerTurnEl = document.getElementById('player-turn');
|
| 198 |
+
var winnerOverlay = document.getElementById('winner-overlay');
|
| 199 |
+
var winnerText = document.getElementById('winner-text');
|
| 200 |
+
var playAgainBtn = document.getElementById('play-again');
|
| 201 |
+
|
| 202 |
+
var boardSizeSelect = document.getElementById('board-size');
|
| 203 |
+
var btnNew = document.getElementById('new-game');
|
| 204 |
+
var btnPass = document.getElementById('pass-turn');
|
| 205 |
+
var btnResign = document.getElementById('resign');
|
| 206 |
+
|
| 207 |
+
var signin = document.getElementById('signin');
|
| 208 |
+
var usernameInput = document.getElementById('username');
|
| 209 |
+
var passwordInput = document.getElementById('password');
|
| 210 |
+
var btnSignin = document.getElementById('btn-signin');
|
| 211 |
+
|
| 212 |
+
// Inline color picker
|
| 213 |
+
var colorPicker = document.getElementById('color-picker');
|
| 214 |
+
var pickBlack = document.getElementById('pick-black');
|
| 215 |
+
var pickWhite = document.getElementById('pick-white');
|
| 216 |
+
|
| 217 |
+
// Persistence
|
| 218 |
+
var LS_USER = 'go_username';
|
| 219 |
+
var LS_COLOR = 'go_my_color';
|
| 220 |
+
function save(k,v){ try{ localStorage.setItem(k,v); }catch(e){} }
|
| 221 |
+
function load(k){ try{ return localStorage.getItem(k); }catch(e){ return null; } }
|
| 222 |
+
|
| 223 |
+
// State
|
| 224 |
+
var username = '';
|
| 225 |
+
var colorsMap = { black:null, white:null };
|
| 226 |
+
var myColor = null;
|
| 227 |
+
|
| 228 |
+
var boardSize = 13;
|
| 229 |
+
boardEl.style.setProperty('--board-size', String(boardSize));
|
| 230 |
+
var board = Array(boardSize).fill().map(()=>Array(boardSize).fill(null));
|
| 231 |
+
var currentColor = 'black';
|
| 232 |
+
var lastMove = null;
|
| 233 |
+
var gameOver = false;
|
| 234 |
+
|
| 235 |
+
// server convention: captured[color] = stones of that color captured
|
| 236 |
+
var captured = { black:0, white:0 };
|
| 237 |
+
var winsByUser = {}; // username -> wins
|
| 238 |
+
var scores = { black:0, white:0 };
|
| 239 |
+
|
| 240 |
+
// Sign in
|
| 241 |
+
(function(){ var u = load(LS_USER); if (u) usernameInput.value = u; })();
|
| 242 |
+
btnSignin.onclick = function(){
|
| 243 |
+
var u = (usernameInput.value||'').trim();
|
| 244 |
+
var p = (passwordInput.value||'').trim();
|
| 245 |
+
if (!u){ alert('Enter a username'); return; }
|
| 246 |
+
if (!p){ alert('Enter the password'); return; }
|
| 247 |
+
username = u; save(LS_USER,u);
|
| 248 |
+
socket.emit('join', { username: u, password: p });
|
| 249 |
+
};
|
| 250 |
+
|
| 251 |
+
function showColorPicker(){
|
| 252 |
+
pickBlack.classList.toggle('disabled', !!colorsMap.black);
|
| 253 |
+
pickWhite.classList.toggle('disabled', !!colorsMap.white);
|
| 254 |
+
colorPicker.style.display = 'flex';
|
| 255 |
+
}
|
| 256 |
+
function hideColorPicker(){ colorPicker.style.display = 'none'; }
|
| 257 |
+
pickBlack.onclick = function(){
|
| 258 |
+
if (!colorsMap.black) { socket.emit('claim_color', { username, color:'black' }); pickBlack.classList.add('disabled'); pickWhite.classList.add('disabled'); }
|
| 259 |
+
};
|
| 260 |
+
pickWhite.onclick = function(){
|
| 261 |
+
if (!colorsMap.white) { socket.emit('claim_color', { username, color:'white' }); pickBlack.classList.add('disabled'); pickWhite.classList.add('disabled'); }
|
| 262 |
+
};
|
| 263 |
+
|
| 264 |
+
// Socket events
|
| 265 |
+
socket.on('init', function(data){
|
| 266 |
+
var size = parseInt(data.board_size,10);
|
| 267 |
+
if (!isNaN(size) && size>0){
|
| 268 |
+
boardSize = size;
|
| 269 |
+
boardEl.style.setProperty('--board-size', String(boardSize));
|
| 270 |
+
board = Array(boardSize).fill().map(()=>Array(boardSize).fill(null));
|
| 271 |
+
}
|
| 272 |
+
if (Array.isArray(data.board)){
|
| 273 |
+
for (var r=0;r<Math.min(boardSize,data.board.length);r++){
|
| 274 |
+
for (var c=0;c<Math.min(boardSize,data.board[r].length);c++){
|
| 275 |
+
var v = data.board[r][c];
|
| 276 |
+
board[r][c] = (v==='black'||v==='white')?v:null;
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
currentColor = data.current_player || 'black';
|
| 281 |
+
captured = data.captured || captured;
|
| 282 |
+
scores = data.scores || scores;
|
| 283 |
+
lastMove = data.last_move || null;
|
| 284 |
+
gameOver = !!data.game_over;
|
| 285 |
+
if (data.wins_by_user) winsByUser = data.wins_by_user;
|
| 286 |
+
|
| 287 |
+
boardSizeSelect.value = String(boardSize);
|
| 288 |
+
boardSizeSelect.disabled = true;
|
| 289 |
+
|
| 290 |
+
refreshNamesUI();
|
| 291 |
+
refreshCapturesUI();
|
| 292 |
+
computeLiveScores();
|
| 293 |
+
updateWinsUI();
|
| 294 |
+
updateTurnUI();
|
| 295 |
+
renderBoard();
|
| 296 |
+
winnerOverlay.classList.remove('show');
|
| 297 |
+
|
| 298 |
+
// hide sign-in only after successful init (i.e., password accepted)
|
| 299 |
+
signin.style.display = 'none';
|
| 300 |
+
});
|
| 301 |
+
|
| 302 |
+
socket.on('colors', function(map){
|
| 303 |
+
colorsMap = { black: map.black || null, white: map.white || null };
|
| 304 |
+
myColor = (colorsMap.black===username)?'black':(colorsMap.white===username)?'white':null;
|
| 305 |
+
if (myColor) save(LS_COLOR,myColor); else save(LS_COLOR,'');
|
| 306 |
+
|
| 307 |
+
if (!colorsMap.black && !colorsMap.white) showColorPicker();
|
| 308 |
+
else if (!myColor) showColorPicker();
|
| 309 |
+
else hideColorPicker();
|
| 310 |
+
|
| 311 |
+
refreshNamesUI();
|
| 312 |
+
updateWinsUI();
|
| 313 |
+
updateTurnUI();
|
| 314 |
+
});
|
| 315 |
+
|
| 316 |
+
socket.on('move', function(data){
|
| 317 |
+
var x = data.x, y = data.y;
|
| 318 |
+
var placed = (data.player==='black'||data.player==='white') ? data.player
|
| 319 |
+
: (data.next_player==='black'?'white':'black');
|
| 320 |
+
|
| 321 |
+
// Place locally
|
| 322 |
+
board[x][y] = placed; lastMove = {row:x,col:y};
|
| 323 |
+
|
| 324 |
+
// ALWAYS remove captured stones locally so the board visuals match
|
| 325 |
+
var removed = localCapture(x,y,placed);
|
| 326 |
+
|
| 327 |
+
// Then sync capture COUNTS from server if provided
|
| 328 |
+
if (data.captured) {
|
| 329 |
+
captured = data.captured;
|
| 330 |
+
} else if (removed > 0) {
|
| 331 |
+
if (placed === 'black') captured.white = (captured.white||0) + removed;
|
| 332 |
+
else captured.black = (captured.black||0) + removed;
|
| 333 |
+
}
|
| 334 |
+
refreshCapturesUI();
|
| 335 |
+
|
| 336 |
+
currentColor = data.next_player || (placed==='black'?'white':'black');
|
| 337 |
+
computeLiveScores();
|
| 338 |
+
updateTurnUI();
|
| 339 |
+
renderBoard();
|
| 340 |
+
});
|
| 341 |
+
|
| 342 |
+
socket.on('pass', function(data){
|
| 343 |
+
currentColor = data.next_player || (currentColor==='black'?'white':'black');
|
| 344 |
+
updateTurnUI();
|
| 345 |
+
});
|
| 346 |
+
|
| 347 |
+
socket.on('resign', function(data){
|
| 348 |
+
if (data.wins_by_user) winsByUser = data.wins_by_user;
|
| 349 |
+
scores = data.scores || scores;
|
| 350 |
+
updateWinsUI();
|
| 351 |
+
gameOver = true;
|
| 352 |
+
var winner = data.winner;
|
| 353 |
+
var winnerName = (winner==='black') ? (colorsMap.black||'Black') : (colorsMap.white||'White');
|
| 354 |
+
winnerText.textContent = winnerName + ' wins by resignation!';
|
| 355 |
+
winnerOverlay.classList.add('show');
|
| 356 |
+
});
|
| 357 |
+
|
| 358 |
+
socket.on('game_over', function(data){
|
| 359 |
+
scores = data.scores || scores;
|
| 360 |
+
if (data.wins_by_user) winsByUser = data.wins_by_user;
|
| 361 |
+
updateWinsUI();
|
| 362 |
+
updateScoresUI();
|
| 363 |
+
gameOver = true;
|
| 364 |
+
var b=scores.black|0, w=scores.white|0;
|
| 365 |
+
var winnerName = (b===w) ? "It's a draw!" : ((b>w)? (colorsMap.black||'Black')+' wins!' : (colorsMap.white||'White')+' wins!');
|
| 366 |
+
winnerText.textContent = winnerName;
|
| 367 |
+
winnerOverlay.classList.add('show');
|
| 368 |
+
});
|
| 369 |
+
|
| 370 |
+
socket.on('error', function(err){
|
| 371 |
+
alert((err && err.message)? err.message : 'Server error.');
|
| 372 |
+
});
|
| 373 |
+
|
| 374 |
+
// Controls
|
| 375 |
+
document.getElementById('new-game').onclick = function(){
|
| 376 |
+
if (!username){ alert('Sign in first.'); return; }
|
| 377 |
+
showColorPicker();
|
| 378 |
+
socket.emit('new_game', { player: username });
|
| 379 |
+
};
|
| 380 |
+
document.getElementById('pass-turn').onclick = function(){
|
| 381 |
+
if (!username) return;
|
| 382 |
+
if (!canPlayNow()){ alert('Not your turn.'); return; }
|
| 383 |
+
socket.emit('pass', { player: username });
|
| 384 |
+
};
|
| 385 |
+
document.getElementById('resign').onclick = function(){
|
| 386 |
+
if (!username) return;
|
| 387 |
+
socket.emit('resign', { player: username });
|
| 388 |
+
};
|
| 389 |
+
boardSizeSelect.onchange = function(e){
|
| 390 |
+
e.target.value = String(boardSize);
|
| 391 |
+
alert('Board size is controlled by the server.');
|
| 392 |
+
};
|
| 393 |
+
playAgainBtn.onclick = function(){ if (username) { showColorPicker(); socket.emit('new_game', { player: username }); } };
|
| 394 |
+
|
| 395 |
+
// Helpers
|
| 396 |
+
function canPlayNow(){
|
| 397 |
+
if (gameOver) return false;
|
| 398 |
+
if (!username) return false;
|
| 399 |
+
if (currentColor === 'black') return colorsMap.black === username;
|
| 400 |
+
if (currentColor === 'white') return colorsMap.white === username;
|
| 401 |
+
return false;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
function refreshNamesUI(){
|
| 405 |
+
nameBlackEl.textContent = colorsMap.black ? (colorsMap.black + (colorsMap.black===username?' (You)':'')) : 'Black';
|
| 406 |
+
nameWhiteEl.textContent = colorsMap.white ? (colorsMap.white + (colorsMap.white===username?' (You)':'')) : 'White';
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
function updateTurnUI(){
|
| 410 |
+
var label = (currentColor==='black') ? (colorsMap.black||'Black') : (colorsMap.white||'White');
|
| 411 |
+
playerTurnEl.textContent = label;
|
| 412 |
+
if (currentColor==='black'){ pBlackEl.classList.add('active'); pWhiteEl.classList.remove('active'); }
|
| 413 |
+
else { pBlackEl.classList.remove('active'); pWhiteEl.classList.add('active'); }
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
function updateWinsUI(){
|
| 417 |
+
var bname = colorsMap.black, wname = colorsMap.white;
|
| 418 |
+
winsBlackEl.textContent = bname && winsByUser[bname] != null ? winsByUser[bname] : 0;
|
| 419 |
+
winsWhiteEl.textContent = wname && winsByUser[wname] != null ? winsByUser[wname] : 0;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
function refreshCapturesUI(){
|
| 423 |
+
// black player's captures = captured.white; white player's captures = captured.black
|
| 424 |
+
capsBlackEl.textContent = captured.white||0;
|
| 425 |
+
capsWhiteEl.textContent = captured.black||0;
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
function computeLiveScores(){
|
| 429 |
+
var bs=0, ws=0;
|
| 430 |
+
for (var r=0;r<boardSize;r++) for (var c=0;c<boardSize;c++){
|
| 431 |
+
if (board[r][c]==='black') bs++; else if (board[r][c]==='white') ws++;
|
| 432 |
+
}
|
| 433 |
+
scores.black = bs + (captured.white||0);
|
| 434 |
+
scores.white = ws + (captured.black||0);
|
| 435 |
+
updateScoresUI();
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
function updateScoresUI(){
|
| 439 |
+
scoreBlackEl.textContent = scores.black||0;
|
| 440 |
+
scoreWhiteEl.textContent = scores.white||0;
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
// Board rendering (draw grid in every cell => inner edges present)
|
| 444 |
+
function renderBoard(){
|
| 445 |
+
boardEl.innerHTML = '';
|
| 446 |
+
for (var r = 0; r < boardSize; r++) {
|
| 447 |
+
for (var c = 0; c < boardSize; c++) {
|
| 448 |
+
(function(row, col){
|
| 449 |
+
var cell = document.createElement('div');
|
| 450 |
+
cell.className = 'cell';
|
| 451 |
+
|
| 452 |
+
var h = document.createElement('div'); h.className='grid-line horizontal'; cell.appendChild(h);
|
| 453 |
+
var v = document.createElement('div'); v.className='grid-line vertical'; cell.appendChild(v);
|
| 454 |
+
|
| 455 |
+
if (boardSize >= 13 &&
|
| 456 |
+
((row===3&&col===3) || (row===3&&col===boardSize-4) ||
|
| 457 |
+
(row===boardSize-4&&col===3) || (row===boardSize-4&&col===boardSize-4) ||
|
| 458 |
+
(row===Math.floor(boardSize/2)&&col===Math.floor(boardSize/2)) ||
|
| 459 |
+
(row===3&&col===Math.floor(boardSize/2)) || (row===boardSize-4&&col===Math.floor(boardSize/2)) ||
|
| 460 |
+
(row===Math.floor(boardSize/2)&&col===3) || (row===Math.floor(boardSize/2)&&col===boardSize-4))) {
|
| 461 |
+
var star = document.createElement('div'); star.className='star-point'; cell.appendChild(star);
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
if (board[row][col]) {
|
| 465 |
+
var stone = document.createElement('div');
|
| 466 |
+
stone.className = 'stone ' + board[row][col];
|
| 467 |
+
if (lastMove && lastMove.row===row && lastMove.col===col) stone.classList.add('last-move');
|
| 468 |
+
cell.appendChild(stone);
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
cell.addEventListener('click', function(){
|
| 472 |
+
if (!canPlayNow()) { alert('Not your turn / pick a color first.'); return; }
|
| 473 |
+
if (board[row][col]) return;
|
| 474 |
+
socket.emit('move', { x: row, y: col, player: username });
|
| 475 |
+
});
|
| 476 |
+
|
| 477 |
+
boardEl.appendChild(cell);
|
| 478 |
+
})(r, c);
|
| 479 |
+
}
|
| 480 |
+
}
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
// Local capture helpers (visual update)
|
| 484 |
+
function hasLibertiesAt(row,col,color){
|
| 485 |
+
var vis = Array(boardSize).fill().map(()=>Array(boardSize).fill(false));
|
| 486 |
+
return dfs(row,col,color,vis);
|
| 487 |
+
}
|
| 488 |
+
function dfs(r,c,color,vis){
|
| 489 |
+
if (r<0||r>=boardSize||c<0||c>=boardSize) return false;
|
| 490 |
+
if (vis[r][c]) return false;
|
| 491 |
+
if (board[r][c]===null) return true;
|
| 492 |
+
if (board[r][c]!==color) return false;
|
| 493 |
+
vis[r][c]=true;
|
| 494 |
+
return dfs(r-1,c,color,vis) || dfs(r+1,c,color,vis) || dfs(r,c-1,color,vis) || dfs(r,c+1,color,vis);
|
| 495 |
+
}
|
| 496 |
+
function floodRemove(r,c,color,vis){
|
| 497 |
+
if (r<0||r>=boardSize||c<0||c>=boardSize) return 0;
|
| 498 |
+
if (vis[r][c] || board[r][c]!==color) return 0;
|
| 499 |
+
vis[r][c]=true; board[r][c]=null;
|
| 500 |
+
return 1 + floodRemove(r-1,c,color,vis) + floodRemove(r+1,c,color,vis) + floodRemove(r,c-1,color,vis) + floodRemove(r,c+1,color,vis);
|
| 501 |
+
}
|
| 502 |
+
function localCapture(r,c,placed){
|
| 503 |
+
var opp=(placed==='black')?'white':'black';
|
| 504 |
+
var removed=0;
|
| 505 |
+
var dirs=[[-1,0],[1,0],[0,-1],[0,1]];
|
| 506 |
+
for (var i=0;i<dirs.length;i++){
|
| 507 |
+
var nr=r+dirs[i][0], nc=c+dirs[i][1];
|
| 508 |
+
if (nr>=0&&nr<boardSize&&nc>=0&&nc<boardSize&&board[nr][nc]===opp){
|
| 509 |
+
if (!hasLibertiesAt(nr,nc,opp)){
|
| 510 |
+
var vis=Array(boardSize).fill().map(()=>Array(boardSize).fill(false));
|
| 511 |
+
removed += floodRemove(nr,nc,opp,vis);
|
| 512 |
+
}
|
| 513 |
+
}
|
| 514 |
+
}
|
| 515 |
+
return removed;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
// Boot
|
| 519 |
+
(function(){
|
| 520 |
+
renderBoard();
|
| 521 |
+
updateTurnUI();
|
| 522 |
+
updateWinsUI();
|
| 523 |
+
updateScoresUI();
|
| 524 |
+
})();
|
| 525 |
+
</script>
|
| 526 |
+
</body>
|
| 527 |
+
</html>
|