Show waiting players during practice

This commit is contained in:
2026-05-08 13:29:49 -04:00
parent 4631f9b7c5
commit 48b619e92b
3 changed files with 76 additions and 7 deletions

65
game.js
View File

@@ -35,6 +35,8 @@ let roomId = "";
let gameMode = "practice"; let gameMode = "practice";
let multiplayerState = "local"; let multiplayerState = "local";
let botTimer = 0; let botTimer = 0;
let lobbyTimer = 0;
let waitingPlayers = 0;
function createBoard() { function createBoard() {
return Array.from({ length: size }, () => Array(size).fill(empty)); return Array.from({ length: size }, () => Array(size).fill(empty));
@@ -54,6 +56,7 @@ function setupGame() {
function connectToServer() { function connectToServer() {
gameMode = "online"; gameMode = "online";
stopLobbyPolling();
closeSocket(); closeSocket();
localPlayer = ""; localPlayer = "";
roomId = ""; roomId = "";
@@ -103,7 +106,8 @@ function startPracticeMode() {
roomId = ""; roomId = "";
multiplayerState = "local"; multiplayerState = "local";
setupGame(); setupGame();
setMultiplayerState("local", "BOT practice. You are X. Random O replies after each move."); setPracticeStatus("BOT practice. You are X. Random O replies after each move.");
startLobbyPolling();
updateModeButtons(); updateModeButtons();
} }
@@ -115,6 +119,40 @@ function closeSocket() {
socket = null; socket = null;
} }
function startLobbyPolling() {
stopLobbyPolling();
checkLobby();
lobbyTimer = window.setInterval(checkLobby, 3000);
}
function stopLobbyPolling() {
if (lobbyTimer) {
window.clearInterval(lobbyTimer);
lobbyTimer = 0;
}
}
async function checkLobby() {
if (gameMode !== "practice") {
return;
}
try {
const response = await fetch("/lobby", { cache: "no-store" });
if (!response.ok) {
return;
}
const status = await response.json();
waitingPlayers = Number(status.waitingPlayers || 0);
if (!gameOver && currentPlayer === localPlayer) {
setPracticeStatus("BOT practice. You are X. Random O replies after each move.");
}
} catch {
waitingPlayers = 0;
}
}
function handleServerMessage(payload) { function handleServerMessage(payload) {
if (payload.type === "waiting") { if (payload.type === "waiting") {
localPlayer = ""; localPlayer = "";
@@ -255,7 +293,7 @@ function playLocalMove(row, col) {
} }
if (!gameOver) { if (!gameOver) {
setMultiplayerState("local", "Bot thinking..."); setPracticeStatus("Bot thinking...");
botTimer = window.setTimeout(playBotMove, 360); botTimer = window.setTimeout(playBotMove, 360);
} }
} }
@@ -276,14 +314,14 @@ function applyMove(row, col, player, source) {
if (win.length > 0) { if (win.length > 0) {
winningCells = win; winningCells = win;
gameOver = true; gameOver = true;
setMultiplayerState(activeState(), `${player} wins with four in a row.`); setActiveStatus(`${player} wins with four in a row.`);
render(); render();
return true; return true;
} }
if (isDraw()) { if (isDraw()) {
gameOver = true; gameOver = true;
setMultiplayerState(activeState(), "Draw. The board is full."); setActiveStatus("Draw. The board is full.");
render(); render();
return true; return true;
} }
@@ -291,7 +329,7 @@ function applyMove(row, col, player, source) {
currentPlayer = opponentOf(player); currentPlayer = opponentOf(player);
const captureMessage = captured.length > 0 ? `${player} captured ${captured.length}. ` : ""; const captureMessage = captured.length > 0 ? `${player} captured ${captured.length}. ` : "";
const turnMessage = currentPlayer === localPlayer ? "Your turn." : waitingMessage(); const turnMessage = currentPlayer === localPlayer ? "Your turn." : waitingMessage();
setMultiplayerState(activeState(), `${captureMessage}${turnMessage}`); setActiveStatus(`${captureMessage}${turnMessage}`);
render(); render();
flashCapturedCells(captured); flashCapturedCells(captured);
@@ -306,6 +344,15 @@ function activeState() {
return gameMode === "practice" ? "local" : "paired"; return gameMode === "practice" ? "local" : "paired";
} }
function setActiveStatus(text) {
if (gameMode === "practice") {
setPracticeStatus(text);
return;
}
setMultiplayerState(activeState(), text);
}
function waitingMessage() { function waitingMessage() {
return gameMode === "practice" ? "Bot turn." : `Waiting for ${currentPlayer}.`; return gameMode === "practice" ? "Bot turn." : `Waiting for ${currentPlayer}.`;
} }
@@ -401,6 +448,11 @@ function setMultiplayerState(state, text) {
messageElement.textContent = text; messageElement.textContent = text;
} }
function setPracticeStatus(text) {
const suffix = waitingPlayers > 0 ? " NET: player waiting." : " NET: no one waiting.";
setMultiplayerState("local", `${text}${suffix}`);
}
function playBotMove() { function playBotMove() {
if (gameMode !== "practice" || gameOver || currentPlayer !== "O") { if (gameMode !== "practice" || gameOver || currentPlayer !== "O") {
return; return;
@@ -471,7 +523,7 @@ resetButton.addEventListener("click", () => {
setupGame(); setupGame();
if (gameMode === "practice") { if (gameMode === "practice") {
setMultiplayerState("local", "BOT practice. You are X. Random O replies after each move."); setPracticeStatus("BOT practice. You are X. Random O replies after each move.");
return; return;
} }
@@ -482,6 +534,7 @@ resetButton.addEventListener("click", () => {
practiceButton.addEventListener("click", startPracticeMode); practiceButton.addEventListener("click", startPracticeMode);
onlineButton.addEventListener("click", () => { onlineButton.addEventListener("click", () => {
gameMode = "online"; gameMode = "online";
stopLobbyPolling();
localPlayer = ""; localPlayer = "";
roomId = ""; roomId = "";
multiplayerState = "connecting"; multiplayerState = "connecting";

View File

@@ -58,7 +58,7 @@
<div class="rule-card"> <div class="rule-card">
<h2>MODES</h2> <h2>MODES</h2>
<p>BOT is local practice against random moves. NET pairs the first two connected players automatically.</p> <p>BOT is local practice against random moves and shows if NET has a player waiting. NET pairs the first two connected players automatically.</p>
</div> </div>
<ol id="moveList" class="move-list" aria-label="Move history"></ol> <ol id="moveList" class="move-list" aria-label="Move history"></ol>

View File

@@ -76,6 +76,15 @@ const server = http.createServer((request, response) => {
return; return;
} }
if (requestUrl.pathname === "/lobby") {
response.writeHead(200, {
"Cache-Control": "no-store",
"Content-Type": "application/json; charset=utf-8",
});
response.end(request.method === "HEAD" ? undefined : JSON.stringify(lobbyStatus()));
return;
}
const staticName = staticFiles.get(requestUrl.pathname); const staticName = staticFiles.get(requestUrl.pathname);
if (!staticName) { if (!staticName) {
response.writeHead(404); response.writeHead(404);
@@ -257,6 +266,13 @@ function pairClients(xClient, oClient) {
send(oClient, { type: "paired", player: "O", roomId }); send(oClient, { type: "paired", player: "O", roomId });
} }
function lobbyStatus() {
return {
activeGames: rooms.size,
waitingPlayers: waitingClientId && clients.has(waitingClientId) ? 1 : 0,
};
}
function handleSocketData(client, buffer) { function handleSocketData(client, buffer) {
if (!clients.has(client.id)) { if (!clients.has(client.id)) {
return; return;