diff --git a/public/index.html b/public/index.html index ef147fb..81b2cdc 100644 --- a/public/index.html +++ b/public/index.html @@ -386,9 +386,9 @@ // --- STATE & HISTORY --- let ws, myRole, selected = null; - let state = null; // Authoritative latest state - let history = []; // Array of state snapshots - let historyIndex = -1; // Current view index + let state = null; + let history = []; + let historyIndex = -1; const pos = { 0: [80, 210], 1: [180, 110], 2: [180, 210], 3: [180, 310], 4: [280, 110], 5: [280, 210], 6: [280, 310], 7: [380, 110], 8: [380, 210], 9: [380, 310], 10: [480, 210] }; const edges = [[0, 1], [0, 2], [0, 3], [1, 2], [2, 3], [1, 4], [2, 5], [3, 6], [4, 5], [5, 6], [4, 7], [5, 8], [6, 9], [7, 8], [8, 9], [5, 1], [5, 3], [5, 7], [5, 9], [7, 10], [8, 10], [9, 10]]; @@ -398,7 +398,7 @@ function initLocalState() { const startState = { wolf: 5, sheep: [0, 1, 3], turn: "sheep", moveCount: 0, winner: null, lastMove: null }; history = []; - updateHistory(startState); + updateHistory(startState, true); document.getElementById("lobby").classList.add("hidden"); document.getElementById("game").classList.remove("hidden"); document.getElementById("roomInfo").style.visibility = "hidden"; @@ -406,15 +406,20 @@ document.getElementById("copyBtn").style.opacity = "0.5"; } - function updateHistory(newState) { - // If it's a reset (moveCount 0) or a new move, add to history + function updateHistory(newState, forceJump = false) { + const isAtEnd = historyIndex === history.length - 1; + if (newState.moveCount === 0) { history = [JSON.parse(JSON.stringify(newState))]; } else if (!state || newState.moveCount > state.moveCount) { history.push(JSON.parse(JSON.stringify(newState))); } + state = newState; - historyIndex = history.length - 1; // Snap to newest + // Only jump the view to the latest state if the player was already at the end or if forced (like on restart) + if (isAtEnd || forceJump) { + historyIndex = history.length - 1; + } render(); } @@ -437,24 +442,36 @@ if (msg.type === "state") { myRole = msg.yourRole; document.getElementById("overlay").className = msg.onlineCount < 2 ? "" : "hidden"; - updateHistory(msg.game); + updateHistory(msg.game, msg.game.moveCount === 0); } }; } - // Keyboard controls for back/forward + // Keyboard controls for Navigation window.addEventListener("keydown", (e) => { - if (e.key === "ArrowLeft" && historyIndex > 0) { - historyIndex--; - render(); - } else if (e.key === "ArrowRight" && historyIndex < history.length - 1) { - historyIndex++; - render(); + if (history.length === 0) return; + + switch (e.key) { + case "ArrowLeft": + if (historyIndex > 0) historyIndex--; + break; + case "ArrowRight": + if (historyIndex < history.length - 1) historyIndex++; + break; + case "ArrowUp": // Jump to Present + historyIndex = history.length - 1; + break; + case "ArrowDown": // Jump to Start + historyIndex = 0; + break; + default: + return; // Exit if other key } + selected = null; // Clear selection when moving through time + render(); }); function handleLocalMove(from, to) { - // If moving while viewing history, we branch off const baseState = history[historyIndex]; const allowed = (baseState.turn === "wolf") ? bWolf[from] : bSheep[from]; if (!allowed?.includes(to) || baseState.sheep.includes(to) || baseState.wolf === to) return; @@ -466,9 +483,9 @@ nextState.moveCount++; nextState.winner = checkWinConditions(nextState); - // Truncate history if we were looking at the past + // Branch history history = history.slice(0, historyIndex + 1); - updateHistory(nextState); + updateHistory(nextState, true); } function checkWinConditions(g) { @@ -525,7 +542,6 @@ const type = isWolf ? "wolf" : (isSheep ? "sheep" : "empty"); const isWinnerPiece = (viewState.winner === "wolf" && isWolf) || (viewState.winner === "sheep" && isSheep); - // Logic for possible moves const canInteract = isLocal || (!isViewingHistory && state.turn === myRole); const isPossible = canInteract && selected !== null && (viewState.turn === "wolf" ? bWolf[selected] : bSheep[selected]).includes(i) && !isWolf && !isSheep;