Add cookies and game deletions

This commit is contained in:
jheaps
2025-03-27 16:15:48 -06:00
parent 79a37c911d
commit 9f6327f5d9
4 changed files with 144 additions and 37 deletions
+30 -6
View File
@@ -67,6 +67,8 @@ public class ChessController : ControllerBase
isWhite = false; isWhite = false;
} }
ScheduleRemoveGame(gameState.GameId, TimeSpan.FromDays(1));
return Ok(new return Ok(new
{ {
Id = playerId, Id = playerId,
@@ -146,12 +148,12 @@ public class ChessController : ControllerBase
if (result.IsCheckmate || result.IsStalemate) if (result.IsCheckmate || result.IsStalemate)
{ {
// queue game removal // queue game removal
_gameRemovalTasks.TryAdd(moveDto.GameId, Task.Run(async () => ScheduleRemoveGame(gameState.GameId, TimeSpan.FromMinutes(1));
{ }
await Task.Delay(TimeSpan.FromMinutes(1)); else
_games.Remove(moveDto.GameId, out _); {
_gameRemovalTasks.Remove(moveDto.GameId, out _); // increase timeout if play continues.
})); ScheduleRemoveGame(gameState.GameId, TimeSpan.FromDays(1));
} }
return Ok(result); return Ok(result);
@@ -189,4 +191,26 @@ public class ChessController : ControllerBase
return Ok(allMoves); 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 _);
}));
}
} }
+8 -2
View File
@@ -11,8 +11,14 @@ public class ChessHub : Hub
await Groups.AddToGroupAsync(Context.ConnectionId, gameId); 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);
} }
} }
+10
View File
@@ -42,3 +42,13 @@
#chessBoard.flipped { #chessBoard.flipped {
transform: rotate(180deg); 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 */
}
@@ -4,6 +4,8 @@ let currentPlayerIsWhite = null;
let signalRConnection = null; let signalRConnection = null;
let selectedPiece = null; let selectedPiece = null;
let legalMoves = []; let legalMoves = [];
let previousMoveStart = null;
let previousMoveEnd = null;
async function startNewGame() { async function startNewGame() {
// Stop previous SignalR connection if needed // Stop previous SignalR connection if needed
@@ -19,35 +21,15 @@ async function startNewGame() {
currentGameId = gameData.gameId; currentGameId = gameData.gameId;
currentPlayerId = gameData.id; currentPlayerId = gameData.id;
currentPlayerIsWhite = gameData.isWhite; 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); console.log("🆕 Game started:", currentGameId);
// Build and start SignalR connection // Build and start SignalR connection
signalRConnection = new signalR.HubConnectionBuilder() await setupSignalRConnection();
.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);
}
// Render initial state // Render initial state
const gameState = await fetch(`/api/chess/${currentGameId}`); const gameState = await fetch(`/api/chess/${currentGameId}`);
@@ -87,11 +69,23 @@ function renderPieces(pieces) {
for (let i = 0; i < 64; i++) { for (let i = 0; i < 64; i++) {
const square = document.getElementById(`square-${i}`); const square = document.getElementById(`square-${i}`);
square.classList.remove("previous-start", "previous-end");
if (!square.classList.contains("legal")) { if (!square.classList.contains("legal")) {
square.onclick = () => clearHighlights(); 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) { function getPieceImageUrl(piece) {
@@ -164,6 +158,9 @@ async function handleMove(targetRow, targetCol) {
PromotionChoice: null // optional PromotionChoice: null // optional
}; };
previousMoveStart = [selectedPiece.row, selectedPiece.col];
previousMoveEnd = [targetRow, targetCol];
const res = await fetch("/api/chess/move", { const res = await fetch("/api/chess/move", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -184,7 +181,7 @@ async function handleMove(targetRow, targetCol) {
selectedPiece = null; selectedPiece = null;
legalMoves = []; legalMoves = [];
clearHighlights(); clearHighlights();
await signalRConnection.invoke("MoveMade", currentGameId, moveResultDto); await signalRConnection.invoke("MoveMade", currentGameId, moveDto, moveResultDto);
alertGameStatusChange(moveResultDto); alertGameStatusChange(moveResultDto);
} }
@@ -208,7 +205,8 @@ function clearHighlights() {
} }
function alertGameStatusChange(moveResultDto) { function alertGameStatusChange(moveResultDto) {
setTimeout(() => { setTimeout(async () => {
let gameOver = false;
// 🟡 Optional: show check // 🟡 Optional: show check
if (moveResultDto.isCheck) { if (moveResultDto.isCheck) {
console.log("🛑 Check!"); console.log("🛑 Check!");
@@ -222,6 +220,12 @@ function alertGameStatusChange(moveResultDto) {
alert("🤝 Stalemate!"); alert("🤝 Stalemate!");
gameOver = true; gameOver = true;
} }
if (gameOver) {
await signalRConnection.invoke("LeaveWebsocketGroup", currentGameId);
await signalRConnection.stop();
signalRConnection = null;
}
}, 500); }, 500);
} }
@@ -232,5 +236,68 @@ function flipCoordinates(row, col) {
return [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"); console.log("chessLogic.js loaded");
window.startNewGame = startNewGame; 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);
}
}
});