From 5365fc213cc3aa33c94f1b3c662eabeed6d28818 Mon Sep 17 00:00:00 2001 From: amiskus999 Date: Thu, 9 Apr 2026 15:29:43 -0400 Subject: [PATCH 1/9] began auto join --- server/server.js | 11 +++++++++++ src/routes/+page.svelte | 28 +++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/server/server.js b/server/server.js index bc516a3..8513ef6 100644 --- a/server/server.js +++ b/server/server.js @@ -124,6 +124,17 @@ app.post('/create-lobby', (req, res) => { res.json({ gameId, message: 'Lobby created!' }); }); +/** + * Matchmaking: Find a lobby that is 'waiting' and has exactly 1 player. + */ +app.get('/find-lobby', (req, res) => { + const gameId = Object.keys(lobbies).find(id => { + const lobby = lobbies[id]; + return lobby.status === 'waiting' && Object.keys(lobby.players).length === 1; + }); + res.json({ gameId: gameId || null }); +}); + // --- SOCKET.IO: GAME LOGIC --- diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 77c428a..9c78af2 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -117,10 +117,28 @@ } /** - * Changes the modal to 3 in order to support multiplayer join lobby functionality. + * Automatically finds an available lobby or creates one if none exist. + * This connects two users without requiring manual code entry. */ - function goToJoin() { - modalStep = 3; + async function autoJoin() { + try { + // Attempt to find a lobby with an available slot (e.g., 1/2 players) + // This assumes your backend has a corresponding /find-lobby endpoint + const res = await fetch(`${PUBLIC_SERVER_URL}/find-lobby`); + const data = await res.json(); + + if (data.gameId) { + lobbyCode = data.gameId; + gameId.set(lobbyCode); + $socket.emit('join_game', { gameId: lobbyCode, playerName: nickname }); + modalStep = 2; // Transition to the waiting/ready state + } else { + // No available lobbies found, fallback to creating one + await goToCreate(); + } + } catch (e) { + await goToCreate(); // Fallback to creating a lobby on error + } } /** @@ -350,7 +368,7 @@ {:else if modalStep === 2} -

LOBBY CREATED

+

LOBBY READY

{lobbyCode}

From 7591f1e3863e6ac411d1111dc391de38bc7a1153 Mon Sep 17 00:00:00 2001 From: amiskus999 Date: Thu, 16 Apr 2026 15:29:09 -0400 Subject: [PATCH 2/9] feat: added a button for user to search for an open lobby --- server/server.js | 8 +++++++- src/routes/+page.svelte | 12 ++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/server/server.js b/server/server.js index 8513ef6..e1b5b9b 100644 --- a/server/server.js +++ b/server/server.js @@ -111,6 +111,7 @@ app.post('/save-map', (req, res) => { app.post('/create-lobby', (req, res) => { const gameId = uuidv4().substring(0, 6); // Shorter ID for easier sharing + const mode = req.body.mode || 'multi'; lobbies[gameId] = { players: {}, // Using object keyed by socket.id status: 'waiting', @@ -118,6 +119,7 @@ app.post('/create-lobby', (req, res) => { activePlayer: null, // Tracks whose turn it is fleets: {}, // Secret fleet positions { socketId: { alpha: {q,r}, beta: {q,r} } } assets: {}, // Tracks assets like fuel, special weapons, etc. + mode: mode, history: [], // Stores a log of all moves/strikes for replay or reconnection fleetPlaced: {} }; @@ -130,7 +132,11 @@ app.post('/create-lobby', (req, res) => { app.get('/find-lobby', (req, res) => { const gameId = Object.keys(lobbies).find(id => { const lobby = lobbies[id]; - return lobby.status === 'waiting' && Object.keys(lobby.players).length === 1; + return ( + lobby.status === 'waiting' && + Object.keys(lobby.players).length === 1 && + lobby.mode === 'multi' + ); }); res.json({ gameId: gameId || null }); }); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 9c78af2..20ca176 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -123,7 +123,6 @@ async function autoJoin() { try { // Attempt to find a lobby with an available slot (e.g., 1/2 players) - // This assumes your backend has a corresponding /find-lobby endpoint const res = await fetch(`${PUBLIC_SERVER_URL}/find-lobby`); const data = await res.json(); @@ -368,12 +367,21 @@ + +
{:else if modalStep === 2} -

LOBBY READY

+

{statusMessage || 'LOBBY READY'}

{lobbyCode}

From eb512f88dd4764bcda2f9bb6381284cb7044a762 Mon Sep 17 00:00:00 2001 From: amiskus999 Date: Tue, 21 Apr 2026 15:04:33 -0400 Subject: [PATCH 4/9] fix: fixed find lobby --- package-lock.json | 24 ++++++++++++------------ package.json | 6 +++--- server/server.js | 3 ++- src/routes/+page.svelte | 24 +++++++++++++++++------- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index f59c862..993ba13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "fff", "version": "0.0.1", "dependencies": { - "dotenv": "^17.3.1", + "dotenv": "^17.4.2", "express": "^5.2.1", "express.js": "^1.0.0", "honeycomb-grid": "^4.1.5", @@ -21,10 +21,10 @@ "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/kit": "^2.55.0", "@sveltejs/vite-plugin-svelte": "^6.2.4", - "svelte": "^5.55.1", + "svelte": "^5.55.4", "svelte-check": "^4.3.3", "typescript": "^5.9.3", - "vite": "^7.3.1", + "vite": "^7.3.2", "vitest": "^4.0.15" } }, @@ -1491,9 +1491,9 @@ } }, "node_modules/dotenv": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", - "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -3217,9 +3217,9 @@ } }, "node_modules/svelte": { - "version": "5.55.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.1.tgz", - "integrity": "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==", + "version": "5.55.4", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.4.tgz", + "integrity": "sha512-q8DFohk6vUswSng95IZb9nzWJnbINZsK7OiM1snAa3qCjJBL0ZQpvMyAaVXjUukdM75J/m8UE8xwqat8Ors/zQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3406,9 +3406,9 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index b9fe00f..9340d54 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,14 @@ "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/kit": "^2.55.0", "@sveltejs/vite-plugin-svelte": "^6.2.4", - "svelte": "^5.55.1", + "svelte": "^5.55.4", "svelte-check": "^4.3.3", "typescript": "^5.9.3", - "vite": "^7.3.1", + "vite": "^7.3.2", "vitest": "^4.0.15" }, "dependencies": { - "dotenv": "^17.3.1", + "dotenv": "^17.4.2", "express": "^5.2.1", "express.js": "^1.0.0", "honeycomb-grid": "^4.1.5", diff --git a/server/server.js b/server/server.js index e1b5b9b..6665ade 100644 --- a/server/server.js +++ b/server/server.js @@ -111,7 +111,7 @@ app.post('/save-map', (req, res) => { app.post('/create-lobby', (req, res) => { const gameId = uuidv4().substring(0, 6); // Shorter ID for easier sharing - const mode = req.body.mode || 'multi'; + const mode = (req.body && req.body.mode) ? req.body.mode : 'multi'; lobbies[gameId] = { players: {}, // Using object keyed by socket.id status: 'waiting', @@ -119,6 +119,7 @@ app.post('/create-lobby', (req, res) => { activePlayer: null, // Tracks whose turn it is fleets: {}, // Secret fleet positions { socketId: { alpha: {q,r}, beta: {q,r} } } assets: {}, // Tracks assets like fuel, special weapons, etc. + botMemory: { knownHits: [], firedShots: [] }, mode: mode, history: [], // Stores a log of all moves/strikes for replay or reconnection fleetPlaced: {} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e4277a4..2007cd4 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -100,7 +100,11 @@ try { statusMessage = 'New lobby created. Share this ID:'; // Set message for new lobby modalStep = 2; - const res = await fetch(`${PUBLIC_SERVER_URL}/create-lobby`, { method: 'POST' }); + const res = await fetch(`${PUBLIC_SERVER_URL}/create-lobby`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ mode: 'multi' }) + }); const data = await res.json(); lobbyCode = data.gameId; @@ -127,6 +131,9 @@ */ async function autoJoin() { try { + statusMessage = 'Searching for available lobbies...'; + modalStep = 2; + // Attempt to find a lobby with an available slot (e.g., 1/2 players) const res = await fetch(`${PUBLIC_SERVER_URL}/find-lobby`); const data = await res.json(); @@ -134,17 +141,20 @@ if (data.gameId) { lobbyCode = data.gameId; gameId.set(lobbyCode); + statusMessage = `Match found! Joining lobby: ${lobbyCode}`; $socket.emit('join_game', { gameId: lobbyCode, playerName: nickname }); - statusMessage = 'Joined existing lobby. Your game ID is:'; // Set message for found lobby - modalStep = 2; // Transition to the waiting/ready state } else { - // No available lobbies found, fallback to creating one - await goToCreate(); // goToCreate will set its own statusMessage + statusMessage = 'No active lobbies found.'; + setTimeout(() => { + modalStep = 1; // Redirect back to the selection screen + }, 1500); } } catch (e) { console.error("Failed to find or create lobby", e); statusMessage = 'Failed to connect. Please try again.'; // Generic error message - modalStep = 2; // Still show modal with error + setTimeout(() => { + modalStep = 1; + }, 1500); } } @@ -158,7 +168,7 @@ $socket.emit('join_game', {gameId: lobbyCode, playerName: nickname}); //$socket.onAny((eventName, ...args) => { //alert(`[SOCKET INBOUND] Event: ${eventName} and ${args}`); - statusMessage = 'Attempting to join lobby...'; + statusMessage = `Attempting to join lobby: ${lobbyCode}`; modalStep = 2; // Show the lobby code and status } From e2e0ad6b1b9f9277c3c40fcb5f6c3dcaf67e2cf5 Mon Sep 17 00:00:00 2001 From: amiskus999 Date: Tue, 21 Apr 2026 15:31:03 -0400 Subject: [PATCH 5/9] fix: fixed find lobby --- server/server.js | 4 +++- server/socketHandler.js | 8 ++++++++ src/routes/+page.svelte | 5 ++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/server/server.js b/server/server.js index 6665ade..45af7a2 100644 --- a/server/server.js +++ b/server/server.js @@ -115,6 +115,7 @@ app.post('/create-lobby', (req, res) => { lobbies[gameId] = { players: {}, // Using object keyed by socket.id status: 'waiting', + full: false, turn: 1, // Tracks the current round number (starts at 1) activePlayer: null, // Tracks whose turn it is fleets: {}, // Secret fleet positions { socketId: { alpha: {q,r}, beta: {q,r} } } @@ -134,7 +135,8 @@ app.get('/find-lobby', (req, res) => { const gameId = Object.keys(lobbies).find(id => { const lobby = lobbies[id]; return ( - lobby.status === 'waiting' && + lobby.status === 'waiting' && + !lobby.full && Object.keys(lobby.players).length === 1 && lobby.mode === 'multi' ); diff --git a/server/socketHandler.js b/server/socketHandler.js index 9c202d2..2b85630 100644 --- a/server/socketHandler.js +++ b/server/socketHandler.js @@ -405,6 +405,10 @@ module.exports = (io, lobbies) => { delete lobby.fleets[socketId]; if (lobby.assets) delete lobby.assets[socketId]; + if (Object.keys(lobby.players).length < 2) { + lobby.full = false; + } + if (lobby.status === 'active' && Object.keys(lobby.players).length > 0) { const winnerId = Object.keys(lobby.players)[0]; lobby.status = 'game_over'; @@ -448,6 +452,10 @@ module.exports = (io, lobbies) => { return; } + if (Object.keys(lobby.players).length == 1) { + lobby.full = true; + } + socket.join(gameId); lobby.players[socket.id] = { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 2007cd4..5b0208b 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -140,9 +140,8 @@ if (data.gameId) { lobbyCode = data.gameId; - gameId.set(lobbyCode); - statusMessage = `Match found! Joining lobby: ${lobbyCode}`; - $socket.emit('join_game', { gameId: lobbyCode, playerName: nickname }); + // Transition to the manual join screen with the found code pre-filled + modalStep = 3; } else { statusMessage = 'No active lobbies found.'; setTimeout(() => { From 5b4e67389c5a91c327fb9d5a4bc0309fcf475758 Mon Sep 17 00:00:00 2001 From: amiskus999 Date: Thu, 23 Apr 2026 14:16:35 -0400 Subject: [PATCH 6/9] feat: find-lobby is a feature on its own --- server/server.js | 17 ++++++++++++- src/routes/+page.svelte | 54 +++++++++++++++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/server/server.js b/server/server.js index 45af7a2..584478e 100644 --- a/server/server.js +++ b/server/server.js @@ -111,13 +111,15 @@ app.post('/save-map', (req, res) => { app.post('/create-lobby', (req, res) => { const gameId = uuidv4().substring(0, 6); // Shorter ID for easier sharing - const mode = (req.body && req.body.mode) ? req.body.mode : 'multi'; + const mode = req.body?.mode || 'multi'; + const isPublic = req.body?.isPublic || false; lobbies[gameId] = { players: {}, // Using object keyed by socket.id status: 'waiting', full: false, turn: 1, // Tracks the current round number (starts at 1) activePlayer: null, // Tracks whose turn it is + isPublic: isPublic, fleets: {}, // Secret fleet positions { socketId: { alpha: {q,r}, beta: {q,r} } } assets: {}, // Tracks assets like fuel, special weapons, etc. botMemory: { knownHits: [], firedShots: [] }, @@ -135,6 +137,7 @@ app.get('/find-lobby', (req, res) => { const gameId = Object.keys(lobbies).find(id => { const lobby = lobbies[id]; return ( + lobby.isPublic && lobby.status === 'waiting' && !lobby.full && Object.keys(lobby.players).length === 1 && @@ -144,6 +147,18 @@ app.get('/find-lobby', (req, res) => { res.json({ gameId: gameId || null }); }); +/** + * Dequeue/Delete: Remove a lobby if a player cancels matchmaking or leaves. + */ +app.post('/delete-lobby', (req, res) => { + const { gameId } = req.body; + if (lobbies[gameId]) { + delete lobbies[gameId]; + return res.json({ success: true }); + } + res.status(404).json({ success: false, error: 'Lobby not found' }); +}); + // --- SOCKET.IO: GAME LOGIC --- diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5b0208b..de9560a 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,6 +12,7 @@ let nickname = $state(''); let lobbyCode = $state(''); let statusMessage = $state(''); // New state variable for messages + let isMatchmaking = $state(false); // Track if we are in the find-lobby queue /** 0 = Name Input, 1 = Selection, 2 = Create Lobby, 3 = Join Lobby; multiplayer * 0 = Name Input, 1 = Start Game; Singleplayer */ @@ -47,6 +48,7 @@ modalStep = 0; nickname = ''; lobbyCode = ''; + isMatchmaking = false; statusMessage = ''; // Reset status message }, 200); return @@ -96,14 +98,14 @@ /** * Sends a POST request to the server to create a multiplayer lobby. */ - async function goToCreate() { + async function goToCreate(publicMode = false) { try { - statusMessage = 'New lobby created. Share this ID:'; // Set message for new lobby + statusMessage = publicMode ? 'WAITING FOR OPPONENT...' : 'New lobby created. Share this ID:'; modalStep = 2; const res = await fetch(`${PUBLIC_SERVER_URL}/create-lobby`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ mode: 'multi' }) + body: JSON.stringify({ mode: 'multi', isPublic: publicMode }) }); const data = await res.json(); lobbyCode = data.gameId; @@ -131,7 +133,8 @@ */ async function autoJoin() { try { - statusMessage = 'Searching for available lobbies...'; + isMatchmaking = true; + statusMessage = 'ESTABLISHING SECURE CONNECTION...'; modalStep = 2; // Attempt to find a lobby with an available slot (e.g., 1/2 players) @@ -140,13 +143,11 @@ if (data.gameId) { lobbyCode = data.gameId; - // Transition to the manual join screen with the found code pre-filled - modalStep = 3; + connect(); // Autojoin the found lobby } else { - statusMessage = 'No active lobbies found.'; - setTimeout(() => { - modalStep = 1; // Redirect back to the selection screen - }, 1500); + // No lobby found, we become the first person in the "queue" + statusMessage = 'SEARCHING FOR COMMANDERS...'; + await goToCreate(true); } } catch (e) { console.error("Failed to find or create lobby", e); @@ -177,6 +178,15 @@ * Revert Modal Step by one. */ function goBack() { + if (modalStep === 2 && isMatchmaking) { + // Dequeue: remove the lobby we created for matchmaking + fetch(`${PUBLIC_SERVER_URL}/delete-lobby`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ gameId: lobbyCode }) + }); + isMatchmaking = false; + } if (modalStep > 1) modalStep = 1; else modalStep = 0; } @@ -414,6 +424,10 @@ {:else if modalStep === 2}

{statusMessage || 'LOBBY READY'}

+ + {#if isMatchmaking} +
+ {/if}

{lobbyCode}

@@ -561,6 +575,26 @@ ); } + /* Waiting cursor/spinner for matchmaking */ + .tactical-spinner { + width: 50px; + height: 50px; + border: 3px solid rgba(59, 130, 246, 0.3); + border-radius: 50%; + border-top-color: #3b82f6; + animation: spin 1s ease-in-out infinite; + margin: 1rem auto; + } + + @keyframes spin { + to { transform: rotate(360deg); } + } + + .modal-content h2 { + 0 100%, 0 20px + ); + } + .modal-content h2 { font-size: clamp(1.5rem, 5vw, 2.5rem); margin-bottom: 2rem; From a39fa6a0a91a37b6f6cfb3b4286ab07442f06ec7 Mon Sep 17 00:00:00 2001 From: amiskus999 Date: Thu, 23 Apr 2026 15:18:44 -0400 Subject: [PATCH 7/9] feat: added bg music --- src/routes/+page.svelte | 62 +++++++++++++++++- ...Pirates of the Caribbean Online Theme .mp3 | Bin 0 -> 1657792 bytes 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 static/01. Pirates of the Caribbean Online Theme .mp3 diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index de9560a..8f203bc 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,6 +12,33 @@ let nickname = $state(''); let lobbyCode = $state(''); let statusMessage = $state(''); // New state variable for messages + + // --- AUDIO STATE --- + let bgAudio = $state(null); + let isMuted = $state(false); + + function initBackgroundMusic() { + if (!bgAudio) { + bgAudio = new Audio('/01. Pirates of the Caribbean Online Theme.mp3'); // Path to your file in the static/ folder + bgAudio.loop = true; + bgAudio.volume = 0.4; + } + if (!isMuted) bgAudio.play().catch(err => console.log("Autoplay blocked until interaction", err)); + } + + function stopBackgroundMusic() { + if (bgAudio) { + bgAudio.pause(); + bgAudio.currentTime = 0; + bgAudio = null; + } + } + + function toggleMute() { + isMuted = !isMuted; + if (bgAudio) bgAudio.muted = isMuted; + } + let isMatchmaking = $state(false); // Track if we are in the find-lobby queue /** 0 = Name Input, 1 = Selection, 2 = Create Lobby, 3 = Join Lobby; multiplayer * 0 = Name Input, 1 = Start Game; Singleplayer @@ -59,6 +86,7 @@ if ($socket) { const handleRoomUpdate = ({ players }) => { if (Object.keys(players).length === 2) { + stopBackgroundMusic(); goto("/multiplayer"); } }; @@ -91,7 +119,10 @@ /** Confirms the nickname for the player*/ function confirmName() { - if (nickname.trim().length > 0) modalStep = 1; + if (nickname.trim().length > 0) { + modalStep = 1; + initBackgroundMusic(); + } } /*****************BACKEND METHODS******************/ @@ -124,6 +155,7 @@ * ease of singleplayer testing. However server may not be needed for singleplayer. */ function goToGame() { + stopBackgroundMusic(); goto('/singleplayer'); } @@ -496,11 +528,39 @@ cna CS map + + +