From f9413da3de03126e5ed99073848ec94a825c65bc Mon Sep 17 00:00:00 2001 From: LucaJakob Date: Mon, 1 Jun 2026 08:40:23 +0200 Subject: [PATCH] add php backend --- README.md | 16 +- backend/data/lobbies.json | 7 + backend/index.php | 238 +++++++++++++++++++++ frontend/game.html | 40 +--- frontend/index.html | 12 +- frontend/leaderboard.html | 3 +- frontend/lobby.html | 3 +- frontend/package-lock.json | 355 ++++++++++++++++---------------- frontend/package.json | 3 +- frontend/results.html | 4 +- frontend/scripts/api.js | 57 +++++ frontend/scripts/countries.js | 246 +++++++++++----------- frontend/scripts/drawing.js | 308 +++++++++++++-------------- frontend/scripts/game.js | 123 +++++++---- frontend/scripts/index.js | 32 ++- frontend/scripts/leaderboard.js | 39 +++- frontend/scripts/lobby.js | 31 ++- frontend/scripts/results.js | 16 +- frontend/scripts/scoring.js | 80 ++++--- frontend/scripts/storage.js | 179 ++++++++-------- router.php | 38 ++++ serve.php | 22 ++ 22 files changed, 1124 insertions(+), 728 deletions(-) create mode 100644 backend/data/lobbies.json create mode 100644 backend/index.php create mode 100644 frontend/scripts/api.js create mode 100644 router.php create mode 100644 serve.php diff --git a/README.md b/README.md index 130a30b..afea277 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,13 @@ The game supports single-player sessions with a local leaderboard tracking top s ## How to start -Open the `frontend/index.html` file in a browser. That's it. +From the project root, run: + +```powershell +php serve.php +``` +Then open `http://localhost:8000/` in a browser. +You can override the port, but the default will be `8000`. --- @@ -95,7 +101,13 @@ The drawing part will likely be handled by WebGL and a canvas. Your browser must support **WebGL**. If you are uncertain, [check this website](https://get.webgl.org/). -Open the `frontend/index.html` file in a browser. That's it. +Start the app from the project root: + +```powershell +php serve.php +``` + +Then open `http://localhost:8000/` in a browser. ### Backend diff --git a/backend/data/lobbies.json b/backend/data/lobbies.json new file mode 100644 index 0000000..472acdf --- /dev/null +++ b/backend/data/lobbies.json @@ -0,0 +1,7 @@ +{ + "Test": { + "name": "Test", + "players": [], + "leaderboard": [] + } +} diff --git a/backend/index.php b/backend/index.php new file mode 100644 index 0000000..3dda880 --- /dev/null +++ b/backend/index.php @@ -0,0 +1,238 @@ + false, 'error' => 'Could not open data file.'], 500); + } + + flock($handle, $write ? LOCK_EX : LOCK_SH); + rewind($handle); + $raw = stream_get_contents($handle); + $lobbies = json_decode($raw ?: '{}', true); + if (!is_array($lobbies)) { + $lobbies = []; + } + + $result = $callback($lobbies); + + if ($write) { + rewind($handle); + ftruncate($handle, 0); + fwrite($handle, json_encode($lobbies, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"); + fflush($handle); + } + + flock($handle, LOCK_UN); + fclose($handle); + + return $result; +} + +function ensure_lobby(array &$lobbies, string $lobbyName): array +{ + if (!isset($lobbies[$lobbyName]) || !is_array($lobbies[$lobbyName])) { + $lobbies[$lobbyName] = [ + 'name' => $lobbyName, + 'players' => [], + 'leaderboard' => [], + ]; + } + + $lobbies[$lobbyName]['players'] = array_values($lobbies[$lobbyName]['players'] ?? []); + $lobbies[$lobbyName]['leaderboard'] = array_values($lobbies[$lobbyName]['leaderboard'] ?? []); + + return $lobbies[$lobbyName]; +} + +$action = $_GET['action'] ?? ''; +$body = read_body(); + +if ($action === 'createLobby') { + $lobbyName = clean_name($body['lobbyName'] ?? ''); + if ($lobbyName === '') { + respond(['ok' => false, 'error' => 'Lobby name is required.'], 400); + } + + $lobby = with_lobbies(static function (array &$lobbies) use ($lobbyName): array { + return ensure_lobby($lobbies, $lobbyName); + }, true); + + respond(['ok' => true, 'lobby' => ['name' => $lobby['name']]]); +} + +if ($action === 'joinLobby') { + $lobbyName = clean_name($body['lobbyName'] ?? ''); + $playerName = clean_name($body['playerName'] ?? '', 24); + if ($lobbyName === '' || $playerName === '') { + respond(['ok' => false, 'error' => 'Lobby name and player name are required.'], 400); + } + + $lobby = with_lobbies(static function (array &$lobbies) use ($lobbyName, $playerName): array { + ensure_lobby($lobbies, $lobbyName); + if (!in_array($playerName, $lobbies[$lobbyName]['players'], true)) { + $lobbies[$lobbyName]['players'][] = $playerName; + } + return $lobbies[$lobbyName]; + }, true); + + respond(['ok' => true, 'lobby' => ['name' => $lobby['name'], 'players' => $lobby['players']]]); +} + +if ($action === 'leaveLobby') { + $lobbyName = clean_name($body['lobbyName'] ?? ''); + $playerName = clean_name($body['playerName'] ?? '', 24); + + with_lobbies(static function (array &$lobbies) use ($lobbyName, $playerName): void { + if ($lobbyName === '' || $playerName === '' || !isset($lobbies[$lobbyName])) { + return; + } + + $lobbies[$lobbyName]['players'] = array_values(array_filter( + $lobbies[$lobbyName]['players'] ?? [], + static fn ($name): bool => $name !== $playerName, + )); + }, true); + + respond(['ok' => true]); +} + +if ($action === 'submitScore') { + $lobbyName = clean_name($body['lobbyName'] ?? ''); + $playerName = clean_name($body['playerName'] ?? '', 24); + if ($lobbyName === '' || $playerName === '') { + respond(['ok' => false, 'error' => 'Lobby name and player name are required.'], 400); + } + + $entry = [ + 'playerName' => $playerName, + 'totalScore' => max(0, min(300, (int) ($body['totalScore'] ?? 0))), + 'scores' => clean_scores($body['scores'] ?? []), + 'countries' => clean_countries($body['countries'] ?? []), + 'date' => gmdate('c'), + ]; + + with_lobbies(static function (array &$lobbies) use ($lobbyName, $playerName, $entry): void { + ensure_lobby($lobbies, $lobbyName); + if (!in_array($playerName, $lobbies[$lobbyName]['players'], true)) { + $lobbies[$lobbyName]['players'][] = $playerName; + } + + $lobbies[$lobbyName]['leaderboard'][] = $entry; + usort( + $lobbies[$lobbyName]['leaderboard'], + static fn (array $a, array $b): int => ($b['totalScore'] ?? 0) <=> ($a['totalScore'] ?? 0), + ); + $lobbies[$lobbyName]['leaderboard'] = array_slice($lobbies[$lobbyName]['leaderboard'], 0, 20); + }, true); + + respond(['ok' => true, 'entry' => $entry]); +} + +if ($action === 'getLeaderboard') { + $lobbyName = clean_name($_GET['lobbyName'] ?? ''); + if ($lobbyName === '') { + respond(['ok' => false, 'error' => 'Lobby name is required.'], 400); + } + + $leaderboard = with_lobbies(static function (array &$lobbies) use ($lobbyName): array { + return $lobbies[$lobbyName]['leaderboard'] ?? []; + }); + + respond(['ok' => true, 'leaderboard' => $leaderboard]); +} + +if ($action === 'getLobby') { + $lobbyName = clean_name($_GET['lobbyName'] ?? ''); + if ($lobbyName === '') { + respond(['ok' => false, 'error' => 'Lobby name is required.'], 400); + } + + $lobby = with_lobbies(static function (array &$lobbies) use ($lobbyName): array { + if (!isset($lobbies[$lobbyName])) { + return ['name' => $lobbyName, 'players' => []]; + } + + return [ + 'name' => $lobbies[$lobbyName]['name'] ?? $lobbyName, + 'players' => array_values($lobbies[$lobbyName]['players'] ?? []), + ]; + }); + + respond(['ok' => true, 'lobby' => $lobby]); +} + +respond(['ok' => false, 'error' => 'Unknown action.'], 404); diff --git a/frontend/game.html b/frontend/game.html index 9aaec39..90f225f 100644 --- a/frontend/game.html +++ b/frontend/game.html @@ -142,44 +142,6 @@ - - - - - - + diff --git a/frontend/index.html b/frontend/index.html index 40a0efc..90b005d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -28,7 +28,7 @@ @@ -165,11 +165,6 @@ -
- - -
-