jah242 commited on
Commit
134659f
·
verified ·
1 Parent(s): 88fee7d

Create templates/index.html

Browse files
Files changed (1) hide show
  1. 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>