diff --git a/JoshHeaps.Net/Controllers/ChessController.cs b/JoshHeaps.Net/Controllers/ChessController.cs index 5ed577b..74b3b96 100644 --- a/JoshHeaps.Net/Controllers/ChessController.cs +++ b/JoshHeaps.Net/Controllers/ChessController.cs @@ -67,6 +67,8 @@ public class ChessController : ControllerBase isWhite = false; } + ScheduleRemoveGame(gameState.GameId, TimeSpan.FromDays(1)); + return Ok(new { Id = playerId, @@ -146,12 +148,12 @@ public class ChessController : ControllerBase if (result.IsCheckmate || result.IsStalemate) { // queue game removal - _gameRemovalTasks.TryAdd(moveDto.GameId, Task.Run(async () => - { - await Task.Delay(TimeSpan.FromMinutes(1)); - _games.Remove(moveDto.GameId, out _); - _gameRemovalTasks.Remove(moveDto.GameId, out _); - })); + ScheduleRemoveGame(gameState.GameId, TimeSpan.FromMinutes(1)); + } + else + { + // increase timeout if play continues. + ScheduleRemoveGame(gameState.GameId, TimeSpan.FromDays(1)); } return Ok(result); @@ -189,4 +191,26 @@ public class ChessController : ControllerBase return Ok(allMoves); } + + private static void ScheduleRemoveGame(Guid id, TimeSpan delay) + { + if (_gameRemovalTasks.ContainsKey(id)) + { + _gameRemovalTasks[id] = Task.Run(async () => + { + await Task.Delay(delay); + _games.Remove(id, out _); + _gameRemovalTasks.Remove(id, out _); + }); + + return; + } + + _gameRemovalTasks.TryAdd(id, Task.Run(async () => + { + await Task.Delay(delay); + _games.Remove(id, out _); + _gameRemovalTasks.Remove(id, out _); + })); + } } diff --git a/JoshHeaps.Net/Hubs/ChessHub.cs b/JoshHeaps.Net/Hubs/ChessHub.cs index fd177cb..0cb59a1 100644 --- a/JoshHeaps.Net/Hubs/ChessHub.cs +++ b/JoshHeaps.Net/Hubs/ChessHub.cs @@ -11,8 +11,14 @@ public class ChessHub : Hub await Groups.AddToGroupAsync(Context.ConnectionId, gameId); } - public async Task MoveMade(string gameId, MoveResultDto moveResult) + public async Task MoveMade(string gameId, MoveDto moveDto, MoveResultDto moveResult) { - await Clients.OthersInGroup(gameId).SendAsync("ReceiveMoveUpdate", gameId, moveResult); + await Clients.OthersInGroup(gameId).SendAsync("ReceiveMoveUpdate", gameId, moveDto, moveResult); + } + + public async Task LeaveWebsocketGroup(string gameId) + { + Console.WriteLine($"❌ Leaving group {gameId}"); + await Groups.RemoveFromGroupAsync(Context.ConnectionId, gameId); } } diff --git a/JoshHeaps.Net/wwwroot/css/chess/game.css b/JoshHeaps.Net/wwwroot/css/chess/game.css index 87c5b16..f285207 100644 --- a/JoshHeaps.Net/wwwroot/css/chess/game.css +++ b/JoshHeaps.Net/wwwroot/css/chess/game.css @@ -41,4 +41,14 @@ #chessBoard.flipped { transform: rotate(180deg); +} + +.chessSquare.previous-start { + box-sizing: border-box; + border: 4px solid #ffd700; /* gold or whatever you like */ +} + +.chessSquare.previous-end { + box-sizing: border-box; + border: 4px solid #ff8c00; /* orange */ } \ No newline at end of file diff --git a/JoshHeaps.Net/wwwroot/js/ChessScripts/chessLogic.js b/JoshHeaps.Net/wwwroot/js/ChessScripts/chessLogic.js index 6ddaf56..9350141 100644 --- a/JoshHeaps.Net/wwwroot/js/ChessScripts/chessLogic.js +++ b/JoshHeaps.Net/wwwroot/js/ChessScripts/chessLogic.js @@ -4,6 +4,8 @@ let currentPlayerIsWhite = null; let signalRConnection = null; let selectedPiece = null; let legalMoves = []; +let previousMoveStart = null; +let previousMoveEnd = null; async function startNewGame() { // Stop previous SignalR connection if needed @@ -19,35 +21,15 @@ async function startNewGame() { currentGameId = gameData.gameId; currentPlayerId = gameData.id; currentPlayerIsWhite = gameData.isWhite; + previousMoveStart = null; + previousMoveEnd = null; + document.cookie = `chessGameId=${currentGameId}; path=/; max-age=86400`; // expires in 1 day + document.cookie = `chessPlayerId=${currentPlayerId}; path=/; max-age=86400`; + document.cookie = `chessPlayerIsWhite=${currentPlayerIsWhite}; path=/; max-age=86400` console.log("🆕 Game started:", currentGameId); // Build and start SignalR connection - signalRConnection = new signalR.HubConnectionBuilder() - .withUrl("/chessHub") - .configureLogging(signalR.LogLevel.Information) - .build(); - - signalRConnection.onclose(err => { - console.error("❌ SignalR connection closed:", err?.message); - }); - - signalRConnection.on("ReceiveMoveUpdate", async (gameId, moveResultDto) => { - if (gameId !== currentGameId) return; - - const res = await fetch(`/api/chess/${gameId}`); - const data = await res.json(); - renderPieces(data.pieces); - - alertGameStatusChange(moveResultDto); - }); - - try { - await signalRConnection.start(); - console.log("✅ SignalR connected"); - await signalRConnection.invoke("JoinWebsocketGroup", currentGameId); - } catch (err) { - console.error("❌ SignalR failed to start or join:", err); - } + await setupSignalRConnection(); // Render initial state const gameState = await fetch(`/api/chess/${currentGameId}`); @@ -87,11 +69,23 @@ function renderPieces(pieces) { for (let i = 0; i < 64; i++) { const square = document.getElementById(`square-${i}`); + square.classList.remove("previous-start", "previous-end"); if (!square.classList.contains("legal")) { square.onclick = () => clearHighlights(); } } + + if (previousMoveStart && previousMoveEnd) { + const [startRow, startCol] = flipCoordinates(...previousMoveStart); + const [endRow, endCol] = flipCoordinates(...previousMoveEnd); + + const startIndex = startRow * 8 + startCol; + const endIndex = endRow * 8 + endCol; + + document.getElementById(`square-${startIndex}`)?.classList.add("previous-start"); + document.getElementById(`square-${endIndex}`)?.classList.add("previous-end"); + } } function getPieceImageUrl(piece) { @@ -164,6 +158,9 @@ async function handleMove(targetRow, targetCol) { PromotionChoice: null // optional }; + previousMoveStart = [selectedPiece.row, selectedPiece.col]; + previousMoveEnd = [targetRow, targetCol]; + const res = await fetch("/api/chess/move", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -184,7 +181,7 @@ async function handleMove(targetRow, targetCol) { selectedPiece = null; legalMoves = []; clearHighlights(); - await signalRConnection.invoke("MoveMade", currentGameId, moveResultDto); + await signalRConnection.invoke("MoveMade", currentGameId, moveDto, moveResultDto); alertGameStatusChange(moveResultDto); } @@ -208,7 +205,8 @@ function clearHighlights() { } function alertGameStatusChange(moveResultDto) { - setTimeout(() => { + setTimeout(async () => { + let gameOver = false; // 🟡 Optional: show check if (moveResultDto.isCheck) { console.log("🛑 Check!"); @@ -222,6 +220,12 @@ function alertGameStatusChange(moveResultDto) { alert("🤝 Stalemate!"); gameOver = true; } + + if (gameOver) { + await signalRConnection.invoke("LeaveWebsocketGroup", currentGameId); + await signalRConnection.stop(); + signalRConnection = null; + } }, 500); } @@ -232,5 +236,68 @@ function flipCoordinates(row, col) { return [row, col]; } +function getCookie(name) { + const value = document.cookie.split('; ') + .find(row => row.startsWith(name + '=')); + return value ? value.split('=')[1] : null; +} + +async function setupSignalRConnection() { + signalRConnection = new signalR.HubConnectionBuilder() + .withUrl("/chessHub") + .configureLogging(signalR.LogLevel.Information) + .build(); + + signalRConnection.onclose(err => { + console.error("❌ SignalR connection closed:", err?.message); + }); + + signalRConnection.on("ReceiveMoveUpdate", async (gameId, moveDto, moveResultDto) => { + if (gameId !== currentGameId) return; + + const res = await fetch(`/api/chess/${gameId}`); + const data = await res.json(); + previousMoveStart = [moveDto.sourceRow, moveDto.sourceCol]; + previousMoveEnd = [moveDto.targetRow, moveDto.targetCol]; + renderPieces(data.pieces); + + alertGameStatusChange(moveResultDto); + }); + + try { + await signalRConnection.start(); + console.log("✅ SignalR connected"); + await signalRConnection.invoke("JoinWebsocketGroup", currentGameId); + } catch (err) { + console.error("❌ SignalR failed to start or join:", err); + } +} + console.log("chessLogic.js loaded"); -window.startNewGame = startNewGame; \ No newline at end of file +window.startNewGame = startNewGame; + +window.addEventListener('load', async () => { + const savedGameId = getCookie("chessGameId"); + const savedPlayerId = getCookie("chessPlayerId"); + const savedPlayerIsWhite = getCookie("chessPlayerIsWhite"); + + if (savedGameId && savedPlayerId) { + try { + const response = await fetch(`/api/chess/${savedGameId}`); + if (response.ok) { + console.log("🧠 Rejoining saved game..."); + currentGameId = savedGameId; + currentPlayerId = savedPlayerId; + currentPlayerIsWhite = (savedPlayerIsWhite === "true"); + + await setupSignalRConnection(); // use your existing SignalR connect logic + const gameData = await response.json(); + renderPieces(gameData.pieces); + } else { + console.warn("Saved game not found or expired."); + } + } catch (err) { + console.error("Failed to rejoin saved game:", err); + } + } +}); \ No newline at end of file