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 multiplayerState = "local";
let botTimer = 0;
let lobbyTimer = 0;
let waitingPlayers = 0;
function createBoard() {
return Array.from({ length: size }, () => Array(size).fill(empty));
@@ -54,6 +56,7 @@ function setupGame() {
function connectToServer() {
gameMode = "online";
stopLobbyPolling();
closeSocket();
localPlayer = "";
roomId = "";
@@ -103,7 +106,8 @@ function startPracticeMode() {
roomId = "";
multiplayerState = "local";
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();
}
@@ -115,6 +119,40 @@ function closeSocket() {
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) {
if (payload.type === "waiting") {
localPlayer = "";
@@ -255,7 +293,7 @@ function playLocalMove(row, col) {
}
if (!gameOver) {
setMultiplayerState("local", "Bot thinking...");
setPracticeStatus("Bot thinking...");
botTimer = window.setTimeout(playBotMove, 360);
}
}
@@ -276,14 +314,14 @@ function applyMove(row, col, player, source) {
if (win.length > 0) {
winningCells = win;
gameOver = true;
setMultiplayerState(activeState(), `${player} wins with four in a row.`);
setActiveStatus(`${player} wins with four in a row.`);
render();
return true;
}
if (isDraw()) {
gameOver = true;
setMultiplayerState(activeState(), "Draw. The board is full.");
setActiveStatus("Draw. The board is full.");
render();
return true;
}
@@ -291,7 +329,7 @@ function applyMove(row, col, player, source) {
currentPlayer = opponentOf(player);
const captureMessage = captured.length > 0 ? `${player} captured ${captured.length}. ` : "";
const turnMessage = currentPlayer === localPlayer ? "Your turn." : waitingMessage();
setMultiplayerState(activeState(), `${captureMessage}${turnMessage}`);
setActiveStatus(`${captureMessage}${turnMessage}`);
render();
flashCapturedCells(captured);
@@ -306,6 +344,15 @@ function activeState() {
return gameMode === "practice" ? "local" : "paired";
}
function setActiveStatus(text) {
if (gameMode === "practice") {
setPracticeStatus(text);
return;
}
setMultiplayerState(activeState(), text);
}
function waitingMessage() {
return gameMode === "practice" ? "Bot turn." : `Waiting for ${currentPlayer}.`;
}
@@ -401,6 +448,11 @@ function setMultiplayerState(state, text) {
messageElement.textContent = text;
}
function setPracticeStatus(text) {
const suffix = waitingPlayers > 0 ? " NET: player waiting." : " NET: no one waiting.";
setMultiplayerState("local", `${text}${suffix}`);
}
function playBotMove() {
if (gameMode !== "practice" || gameOver || currentPlayer !== "O") {
return;
@@ -471,7 +523,7 @@ resetButton.addEventListener("click", () => {
setupGame();
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;
}
@@ -482,6 +534,7 @@ resetButton.addEventListener("click", () => {
practiceButton.addEventListener("click", startPracticeMode);
onlineButton.addEventListener("click", () => {
gameMode = "online";
stopLobbyPolling();
localPlayer = "";
roomId = "";
multiplayerState = "connecting";

View File

@@ -58,7 +58,7 @@
<div class="rule-card">
<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>
<ol id="moveList" class="move-list" aria-label="Move history"></ol>

View File

@@ -76,6 +76,15 @@ const server = http.createServer((request, response) => {
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);
if (!staticName) {
response.writeHead(404);
@@ -257,6 +266,13 @@ function pairClients(xClient, oClient) {
send(oClient, { type: "paired", player: "O", roomId });
}
function lobbyStatus() {
return {
activeGames: rooms.size,
waitingPlayers: waitingClientId && clients.has(waitingClientId) ? 1 : 0,
};
}
function handleSocketData(client, buffer) {
if (!clients.has(client.id)) {
return;