}
response.sendStatus(200);
+
+ this.send_board_state(session_id);
+ }
+
+ /* Analyze a player's board and send validation state via SSE. */
+ send_board_state(session_id) {
+ const player = this.players_by_session[session_id];
+ if (!player) return;
+
+ const tiles = this.state.player_tiles[session_id];
+ if (!tiles) return;
+
+ const board = this.state.player_boards[session_id] || {};
+ const analysis = LetterRip.analyze_grid(board);
+
+ /* Invalid cells: in an invalid word or isolated (not in any word). */
+ const invalid = new Set();
+ const in_word = new Set();
+ for (const w of analysis.words) {
+ for (const cell of w.cells) {
+ const key = cell.r + "," + cell.c;
+ in_word.add(key);
+ if (!w.valid) invalid.add(key);
+ }
+ }
+ for (const key of Object.keys(board)) {
+ if (!in_word.has(key)) invalid.add(key);
+ }
+
+ /* Unconnected cells (not reachable from the first tile via BFS). */
+ const unconnected = new Set();
+ const occupied = Object.keys(board);
+ if (occupied.length > 1) {
+ const pos_set = new Set(occupied);
+ const visited = new Set([occupied[0]]);
+ const queue = [occupied[0]];
+ while (queue.length > 0) {
+ const cur = queue.shift();
+ const [cr, cc] = cur.split(",").map(Number);
+ for (const n of [(cr-1)+","+cc, (cr+1)+","+cc,
+ cr+","+(cc-1), cr+","+(cc+1)]) {
+ if (pos_set.has(n) && !visited.has(n)) {
+ visited.add(n);
+ queue.push(n);
+ }
+ }
+ }
+ for (const key of occupied) {
+ if (!visited.has(key)) unconnected.add(key);
+ }
+ }
+
+ const all_valid = analysis.words.length > 0
+ && analysis.words.every(w => w.valid);
+ const all_connected = unconnected.size === 0;
+
+ const data = JSON.stringify({
+ invalid_cells: [...invalid],
+ unconnected_cells: [...unconnected],
+ all_valid,
+ all_connected
+ });
+ player.send(`event: board-state\ndata: ${data}\n\n`);
}
handle_events(request, response) {
response.write(`event: board\ndata: ${JSON.stringify(board)}\n\n`);
}
+ /* Send current board validation state. */
+ if (tiles) {
+ this.send_board_state(session_id);
+ }
+
/* Send current bag count. */
response.write(`event: dealt\ndata: ${JSON.stringify({
remaining: this.state.bag.length