SyntaxSquad/DADProject#16 fix: Cancle and Return is working and some other fixes involving game duration

This commit is contained in:
AfonsoCMSousa
2026-01-03 02:19:00 +00:00
parent 7e6766c726
commit 3dcd8df4c7
9 changed files with 227 additions and 143 deletions
@@ -244,4 +244,19 @@ class GameController extends Controller
'game' => $game->fresh(), 'game' => $game->fresh(),
]); ]);
} }
public function destroy(Game $game)
{
// Ensure only the host can delete, and ONLY if it's still Pending
if ($game->player1_user_id !== auth()->id()) {
return response()->json(['message' => 'Unauthorized'], 403);
}
if ($game->status !== 'Pending') {
return response()->json(['message' => 'Cannot delete a game in progress'], 400);
}
$game->delete();
return response()->noContent();
}
} }
+73 -19
View File
@@ -291,18 +291,88 @@ const handleBeforeUnload = (event) => {
} }
}; };
onBeforeRouteLeave((to, from, next) => { // --- FIX: SURRENDER LOGIC ---
if (gameOver.value || !shouldShowBoard.value) { const handleSurrender = () => {
if(confirm('Are you sure you want to surrender? You will lose.')) {
wsStore.surrender(gameId.value)
}
}
const handleCloseModal = async () => {
// 1. If game is still in Lobby (Pending) and I am the Host, DELETE it.
if (!shouldShowBoard.value && amIHost.value && !gameOver.value) {
try {
// This calls Route::delete('/{game}', ...) in your api.php
await axios.delete(`${API_BASE_URL}/games/${gameId.value}`);
console.log("Lobby cancelled and deleted.");
} catch (e) {
console.error("Failed to delete pending game:", e);
}
}
// 2. Notify server of voluntary exit (to skip the 10s reconnect timer)
if (wsStore.socket) {
wsStore.socket.emit('notify_disconnect');
}
// 3. Disconnect and go home
wsStore.disconnect();
router.push({ name: 'home' });
}
// --- LIFECYCLE ---
onBeforeRouteLeave(async (to, from, next) => {
// CASE A: Game is Pending (Waiting Room)
if (!shouldShowBoard.value && !gameOver.value) {
if (amIHost.value) {
// Ask for confirmation to close the lobby
const confirmClose = window.confirm("This will cancel the lobby. Are you sure?");
if (confirmClose) {
try {
// Delete the game from DB so it doesn't get stuck as "Pending"
await axios.delete(`${API_BASE_URL}/games/${gameId.value}`);
} catch(e) { console.error(e) }
if(wsStore.socket) wsStore.socket.emit('notify_disconnect');
wsStore.disconnect();
next();
} else {
next(false); // Stay on page
}
return;
} else {
// If I am NOT host (just a guest waiting), just leave
if(wsStore.socket) wsStore.socket.emit('notify_disconnect');
wsStore.disconnect();
next();
return;
}
}
// CASE B: Game is Over (Standard exit)
if (gameOver.value) {
next(); next();
return; return;
} }
// CASE C: Game is Active/Playing (The Surrender Logic we built before)
const answer = window.confirm( const answer = window.confirm(
"If you leave, you will be register has 'losing' or 'surrendered', Are you sure you want to leave?" "If you leave, you will be register has 'losing' or 'surrendered', Are you sure you want to leave?"
); );
if (answer) { if (answer) {
wsStore.surrender(gameId.value); // ... (Your existing surrender logic) ...
try { await wsStore.surrender(gameId.value); } catch (e) {}
if (wsStore.socket) {
wsStore.socket.emit('notify_disconnect', () => {
wsStore.disconnect();
next();
});
setTimeout(() => { if (wsStore.connected) { wsStore.disconnect(); next(); } }, 500);
return;
}
wsStore.disconnect(); wsStore.disconnect();
next(); next();
} else { } else {
@@ -310,22 +380,6 @@ onBeforeRouteLeave((to, from, next) => {
} }
}); });
// --- FIX: SURRENDER LOGIC ---
const handleSurrender = () => {
if(confirm('Are you sure you want to surrender? You will lose.')) {
// Send event to server. Do NOT disconnect locally yet.
// Wait for the server to send "Game Over" event back.
wsStore.surrender(gameId.value)
}
}
const handleCloseModal = () => {
wsStore.disconnect()
router.push({ name: 'home' })
}
// --- LIFECYCLE ---
onMounted(async () => { onMounted(async () => {
await fetchGameMetadata() await fetchGameMetadata()
wsStore.connect() wsStore.connect()
+2
View File
@@ -97,6 +97,8 @@ const getPendingMatches = async (mode = selectedMode.value) => {
mLoading.value = true; mLoading.value = true;
try { try {
// FIX: Incorrect endpoint used previously
// TAG: 404
const response = await axios.get(`${API_BASE_URL}/matches/open`, { const response = await axios.get(`${API_BASE_URL}/matches/open`, {
params: { params: {
type: mode, type: mode,
+7 -1
View File
@@ -621,7 +621,8 @@
</div> </div>
<div class="text-right"> <div class="text-right">
<div class="font-bold text-sm">{{ game.amount }} pts</div>
<div class="font-bold text-sm">{{ game.points }} pts</div>
<div class="text-[10px] text-muted-foreground"> <div class="text-[10px] text-muted-foreground">
{{ new Date(game.began_at).toLocaleDateString() }} {{ new Date(game.began_at).toLocaleDateString() }}
{{ {{
@@ -1185,10 +1186,15 @@ const fetchData = async () => {
return { return {
id: game.id, id: game.id,
player1_id: game.player1.id,
player2_id: game.player2.id,
variant: `Type ${game.type}`, variant: `Type ${game.type}`,
outcome: outcome, outcome: outcome,
opponent: null, opponent: null,
amount: game.total_points || 0, amount: game.total_points || 0,
points: game.player1_user_id === authStore.currentUser.id
? game.player1_points
: game.player2_points,
duration: `${Math.floor(game.total_time / 60)}m ${game.total_time % 60}s`, duration: `${Math.floor(game.total_time / 60)}m ${game.total_time % 60}s`,
began_at: game.began_at, began_at: game.began_at,
details: { details: {
-1
View File
@@ -92,7 +92,6 @@ export const useSocketStore = defineStore('websocket', () => {
socket.value.emit('play-card', { gameId, cardId }) socket.value.emit('play-card', { gameId, cardId })
} }
// --- NEW: Surrender Action ---
const surrender = (gameId) => { const surrender = (gameId) => {
if (!socket.value?.connected) return if (!socket.value?.connected) return
console.log('[Game] Surrendering:', gameId) console.log('[Game] Surrendering:', gameId)
+1
View File
@@ -96,6 +96,7 @@ const performGameEnd = (io, game, gameId, winnerId, loserId, reason) => {
action: "game_ended", action: "game_ended",
winnerId: parseInt(winnerId), winnerId: parseInt(winnerId),
loserId: parseInt(loserId), loserId: parseInt(loserId),
totalTime: game.startTime ? Math.floor((Date.now() - game.startTime) / 1000) : 0,
isDraw: false, isDraw: false,
player1_points: game.players[Object.keys(game.players)[0]]?.points || 0, player1_points: game.players[Object.keys(game.players)[0]]?.points || 0,
player2_points: game.players[Object.keys(game.players)[1]]?.points || 0, player2_points: game.players[Object.keys(game.players)[1]]?.points || 0,
+12 -3
View File
@@ -12,7 +12,9 @@ export const server = {
const API_URL = "http://localhost:8000/api/v1"; const API_URL = "http://localhost:8000/api/v1";
const disconnectTimers = new Map(); const disconnectTimers = new Map();
const DISCONNECT_TIMEOUT = 1 * 60 * 1000; // 1 minutes const RECONNECT_GRACE_PERIOD = 1 * 60 * 1000; // 1 minutes
const userId = (socket) => socket.user && socket.user.id;
export const serverStart = (port) => { export const serverStart = (port) => {
server.io = new Server(port, { server.io = new Server(port, {
@@ -75,16 +77,23 @@ export const serverStart = (port) => {
server.io.on("connection", (socket) => { server.io.on("connection", (socket) => {
gameEvents(server.io, socket); gameEvents(server.io, socket);
console.log(`[Connection] User ${userId} connected. Socket ID: ${socket.id}`);
matchEvents(server.io, socket); matchEvents(server.io, socket);
handleConnectionEvents(server.io, socket); handleConnectionEvents(server.io, socket);
socket.on("notify_disconnect", () => {
socket.isVoluntaryDisconnect = true;
});
socket.on("disconnect", () => { socket.on("disconnect", () => {
// Remove from active socket list // Remove from active socket list
removeUser(socket.id); removeUser(socket.id);
if (socket.isVoluntaryDisconnect) {
console.log(`[Disconnect] User ${userId} disconnected voluntarily. No surrender needed.`);
return;
}
if (userId) { if (userId) {
console.log(`[Disconnect] User ${userId} disconnected. Waiting ${RECONNECT_GRACE_PERIOD / 1000}s...`); console.log(`[Disconnect] User ${userId} disconnected. Waiting ${RECONNECT_GRACE_PERIOD / 1000}s...`);
-2
View File
@@ -33,5 +33,3 @@ export const removeGame = (id) => {
} }
}; };
export { createGame, getGame, removeGame };