239 lines
7.0 KiB
PHP
239 lines
7.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Access-Control-Allow-Origin: *');
|
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
|
header('Access-Control-Allow-Headers: Content-Type');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
http_response_code(204);
|
|
exit;
|
|
}
|
|
|
|
$dataDir = __DIR__ . DIRECTORY_SEPARATOR . 'data';
|
|
$dataFile = $dataDir . DIRECTORY_SEPARATOR . 'lobbies.json';
|
|
|
|
if (!is_dir($dataDir)) {
|
|
mkdir($dataDir, 0777, true);
|
|
}
|
|
|
|
if (!file_exists($dataFile)) {
|
|
file_put_contents($dataFile, "{}\n");
|
|
}
|
|
|
|
function respond(array $payload, int $status = 200): void
|
|
{
|
|
http_response_code($status);
|
|
echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
function read_body(): array
|
|
{
|
|
$raw = file_get_contents('php://input');
|
|
if ($raw === false || trim($raw) === '') {
|
|
return [];
|
|
}
|
|
|
|
$decoded = json_decode($raw, true);
|
|
return is_array($decoded) ? $decoded : [];
|
|
}
|
|
|
|
function clean_name(mixed $value, int $maxLength = 40): string
|
|
{
|
|
$name = trim((string) $value);
|
|
$name = preg_replace('/\s+/', ' ', $name) ?? '';
|
|
return substr($name, 0, $maxLength);
|
|
}
|
|
|
|
function clean_scores(mixed $value): array
|
|
{
|
|
if (!is_array($value)) {
|
|
return [];
|
|
}
|
|
|
|
return array_slice(array_map(static function ($score): int {
|
|
return max(0, min(100, (int) $score));
|
|
}, $value), 0, 3);
|
|
}
|
|
|
|
function clean_countries(mixed $value): array
|
|
{
|
|
if (!is_array($value)) {
|
|
return [];
|
|
}
|
|
|
|
return array_slice(array_map(static function ($country): string {
|
|
return clean_name($country, 80);
|
|
}, $value), 0, 3);
|
|
}
|
|
|
|
function with_lobbies(callable $callback, bool $write = false): mixed
|
|
{
|
|
global $dataFile;
|
|
|
|
$handle = fopen($dataFile, 'c+');
|
|
if ($handle === false) {
|
|
respond(['ok' => 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);
|