| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/arni-solutions.de/web/slots/api/ |
Upload File : |
<?php
/**
* Schiffe versenken (bis 4 Spieler) – serverautoritär, Schiffe bleiben geheim.
* Aktionen: create | join | place | shot | state
* View blendet gegnerische Schiffspositionen aus (nur eigene Flotte sichtbar).
*/
require_once __DIR__ . '/../includes/bootstrap.php';
header('Content-Type: application/json; charset=utf-8');
function s_out($d) { echo json_encode($d, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; }
function s_fail($m, $c = 400) { http_response_code($c); s_out(['ok' => false, 'message' => $m]); }
function ships_initial(): array {
return ['phase' => 'setup', 'fleet' => [5, 4, 3, 3, 2], 'boards' => (object)[], 'shots' => (object)[],
'order' => [], 'turnIdx' => 0, 'eliminated' => [], 'winner' => null, 'lastShot' => null];
}
/** Sichtbare Sicht für den Spieler mit $token (gegnerische Schiffe ausgeblendet). */
function ships_view(array $r, ?string $token): array {
$players = json_decode($r['players'], true) ?: [];
$st = json_decode($r['state'], true) ?: [];
$myId = null;
foreach ($players as $p) if ($token && hash_equals($p['token'], $token)) $myId = $p['id'];
$boards = $st['boards'] ?? [];
$shots = $st['shots'] ?? [];
$pubBoards = [];
foreach ($players as $p) {
$id = $p['id'];
$b = $boards[$id] ?? null;
$ready = $b ? !empty($b['ready']) : false;
$row = ['ready' => $ready, 'alive' => !in_array($id, $st['eliminated'] ?? [], true)];
if ($id === $myId && $b) $row['ships'] = $b['ships']; // nur eigene Flotte
if ($b) { // geschlagene Schiffszellen sind ohnehin öffentlich
$hitCells = 0; $total = 0;
foreach ($b['ships'] as $ship) foreach ($ship as $cell) { $total++; if (($shots[$id][$cell[0] . ',' . $cell[1]] ?? '') === 'hit') $hitCells++; }
$row['total'] = $total; $row['hits'] = $hitCells;
}
$pubBoards[$id] = $row;
}
$order = $st['order'] ?? [];
return [
'ok' => true, 'code' => $r['code'], 'version' => (int)$r['version'], 'status' => $r['status'],
'phase' => $st['phase'], 'fleet' => $st['fleet'], 'maxp' => (int)$r['maxp'],
'players' => array_map(function ($p) { return ['id' => $p['id'], 'name' => $p['name']]; }, $players),
'boards' => $pubBoards, 'shots' => $shots, 'order' => $order,
'turn' => $order[$st['turnIdx'] ?? 0] ?? null, 'eliminated' => $st['eliminated'] ?? [],
'winner' => $st['winner'], 'lastShot' => $st['lastShot'], 'your_id' => $myId,
];
}
$payload = json_decode(file_get_contents('php://input'), true) ?: [];
$action = (string)($_GET['action'] ?? $payload['action'] ?? '');
$pdo = db();
$load = function ($code, $lock = false) use ($pdo) {
$code = preg_replace('/[^a-f0-9]/', '', (string)$code);
if ($code === '') return null;
$stmt = $pdo->prepare('SELECT * FROM game_rooms WHERE code=? LIMIT 1' . ($lock ? ' FOR UPDATE' : ''));
$stmt->execute([$code]); return $stmt->fetch() ?: null;
};
try {
if ($action === 'create') {
$user = current_user();
$name = mb_substr(trim((string)($payload['name'] ?? '')) ?: ($user['username'] ?? 'Spieler 1'), 0, 30);
$maxp = max(2, min(4, (int)($payload['maxp'] ?? 4)));
$code = bin2hex(random_bytes(5)); $token = bin2hex(random_bytes(12));
$players = [['token' => $token, 'name' => $name, 'id' => 0]];
$pdo->prepare('INSERT INTO game_rooms (code,game,state,players,status,maxp,version,host_user_id,created_at,updated_at) VALUES (?,?,?,?,?,?,1,?,NOW(),NOW())')
->execute([$code, 'ships', json_encode(ships_initial()), json_encode($players), 'lobby', $maxp, $user['id'] ?? null]);
s_out(array_merge(ships_view($load($code), $token), ['token' => $token, 'your_id' => 0]));
}
if ($action === 'join') {
$pdo->beginTransaction();
$r = $load((string)($payload['code'] ?? ''), true);
if (!$r) { $pdo->rollBack(); s_fail('Raum nicht gefunden.', 404); }
$players = json_decode($r['players'], true) ?: [];
$token = (string)($payload['token'] ?? '');
foreach ($players as $p) if ($token && hash_equals($p['token'], $token)) { $pdo->commit(); s_out(array_merge(ships_view($r, $token), ['token' => $token, 'your_id' => $p['id']])); }
if ($r['status'] !== 'lobby') { $pdo->rollBack(); s_fail('Spiel läuft bereits.', 409); }
if (count($players) >= (int)$r['maxp']) { $pdo->rollBack(); s_fail('Raum ist voll.', 409); }
$user = current_user();
$name = mb_substr(trim((string)($payload['name'] ?? '')) ?: ($user['username'] ?? 'Spieler'), 0, 30);
$nid = count($players); $nt = bin2hex(random_bytes(12));
$players[] = ['token' => $nt, 'name' => $name, 'id' => $nid];
$pdo->prepare('UPDATE game_rooms SET players=?, version=version+1, updated_at=NOW() WHERE code=?')->execute([json_encode($players), $r['code']]);
$pdo->commit();
s_out(array_merge(ships_view($load($r['code']), $nt), ['token' => $nt, 'your_id' => $nid]));
}
if ($action === 'state') {
$r = $load((string)($_GET['code'] ?? $payload['code'] ?? ''));
if (!$r) s_fail('Raum nicht gefunden.', 404);
s_out(ships_view($r, (string)($_GET['token'] ?? $payload['token'] ?? '')));
}
if ($action === 'place') {
$pdo->beginTransaction();
$r = $load((string)($payload['code'] ?? ''), true);
if (!$r) { $pdo->rollBack(); s_fail('Raum nicht gefunden.', 404); }
$players = json_decode($r['players'], true) ?: []; $st = json_decode($r['state'], true);
$token = (string)($payload['token'] ?? ''); $myId = null;
foreach ($players as $p) if (hash_equals($p['token'], $token)) $myId = $p['id'];
if ($myId === null) { $pdo->rollBack(); s_fail('Kein Mitglied.', 403); }
if ($st['phase'] !== 'setup') { $pdo->rollBack(); s_fail('Aufstellung beendet.'); }
$ships = $payload['ships'] ?? null;
if (!is_array($ships)) { $pdo->rollBack(); s_fail('Ungültige Flotte.'); }
$st['boards'] = (array)$st['boards']; $st['shots'] = (array)$st['shots'];
$st['boards'][$myId] = ['ships' => $ships, 'ready' => true];
// Alle bereit (>=2 Spieler) → Spiel starten
$allReady = count($players) >= 2;
foreach ($players as $p) if (empty($st['boards'][$p['id']]['ready'])) $allReady = false;
$status = $r['status'];
if ($allReady) {
$st['phase'] = 'playing'; $status = 'playing';
$st['order'] = array_map(function ($p) { return $p['id']; }, $players);
$st['turnIdx'] = 0;
foreach ($players as $p) $st['shots'][$p['id']] = (object)[];
}
$pdo->prepare('UPDATE game_rooms SET state=?, status=?, version=version+1, updated_at=NOW() WHERE code=?')->execute([json_encode($st), $status, $r['code']]);
$pdo->commit();
s_out(ships_view($load($r['code']), $token));
}
if ($action === 'shot') {
$pdo->beginTransaction();
$r = $load((string)($payload['code'] ?? ''), true);
if (!$r) { $pdo->rollBack(); s_fail('Raum nicht gefunden.', 404); }
$players = json_decode($r['players'], true) ?: []; $st = json_decode($r['state'], true);
$token = (string)($payload['token'] ?? ''); $myId = null;
foreach ($players as $p) if (hash_equals($p['token'], $token)) $myId = $p['id'];
if ($myId === null) { $pdo->rollBack(); s_fail('Kein Mitglied.', 403); }
if ($st['phase'] !== 'playing') { $pdo->rollBack(); s_fail('Spiel läuft nicht.'); }
$order = $st['order'];
if (($order[$st['turnIdx']] ?? null) !== $myId) { $pdo->rollBack(); s_fail('Du bist nicht am Zug.', 403); }
$target = (int)($payload['target'] ?? -1);
$row = (int)($payload['r'] ?? -1); $col = (int)($payload['c'] ?? -1);
if ($target === $myId || !in_array($target, $order, true)) { $pdo->rollBack(); s_fail('Ungültiges Ziel.'); }
if ($row < 0 || $row > 9 || $col < 0 || $col > 9) { $pdo->rollBack(); s_fail('Ungültiges Feld.'); }
$key = $row . ',' . $col;
$st['shots'] = (array)$st['shots'];
$st['shots'][$target] = (array)($st['shots'][$target] ?? []);
if (isset($st['shots'][$target][$key])) { $pdo->rollBack(); s_fail('Feld schon beschossen.'); }
// Treffer prüfen
$hit = false; $sunkShip = null;
foreach ($st['boards'][$target]['ships'] as $ship) {
foreach ($ship as $cell) if ((int)$cell[0] === $row && (int)$cell[1] === $col) { $hit = true; break; }
}
$st['shots'][$target][$key] = $hit ? 'hit' : 'miss';
$result = $hit ? 'hit' : 'miss';
if ($hit) {
// versenkt? (alle Zellen des getroffenen Schiffs)
foreach ($st['boards'][$target]['ships'] as $ship) {
$contains = false; foreach ($ship as $cell) if ((int)$cell[0] === $row && (int)$cell[1] === $col) $contains = true;
if (!$contains) continue;
$sunk = true; foreach ($ship as $cell) if (($st['shots'][$target][$cell[0] . ',' . $cell[1]] ?? '') !== 'hit') $sunk = false;
if ($sunk) { $result = 'sunk'; }
}
// eliminiert? (alle Schiffe versenkt)
$allHit = true;
foreach ($st['boards'][$target]['ships'] as $ship) foreach ($ship as $cell) if (($st['shots'][$target][$cell[0] . ',' . $cell[1]] ?? '') !== 'hit') $allHit = false;
if ($allHit) { $result = 'eliminated'; $st['eliminated'][] = $target; $order = array_values(array_filter($order, function ($x) use ($target) { return $x !== $target; })); }
}
// Sieg / nächster Zug
if (count($order) <= 1) { $st['phase'] = 'done'; $st['winner'] = $order[0] ?? $myId; }
else { $i = array_search($myId, $order, true); $st['turnIdx'] = ($i + 1) % count($order); }
$st['order'] = $order;
$st['lastShot'] = ['by' => $myId, 'target' => $target, 'r' => $row, 'c' => $col, 'result' => $result];
$status = $st['phase'] === 'done' ? 'done' : 'playing';
$pdo->prepare('UPDATE game_rooms SET state=?, status=?, version=version+1, updated_at=NOW() WHERE code=?')->execute([json_encode($st), $status, $r['code']]);
$pdo->commit();
s_out(ships_view($load($r['code']), $token));
}
s_fail('Unbekannte Aktion.');
} catch (Throwable $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
s_fail('Serverfehler: ' . $e->getMessage(), 500);
}