39001-vm/index.php
2026-03-05 10:39:31 +00:00

241 lines
10 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
session_start();
require_once __DIR__ . '/includes/rooms.php';
ensure_rooms_schema();
cleanup_empty_rooms();
$errors = [];
$flash = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
$playerName = trim($_POST['player_name'] ?? '');
$roomName = trim($_POST['room_name'] ?? '');
if ($action === 'create') {
if ($playerName === '') {
$errors[] = 'Enter a player name to create a room.';
}
if ($roomName === '') {
$roomName = 'Retro Arena';
}
if (!$errors) {
$token = random_token();
$roomId = create_room($roomName, $playerName, $token, 4);
set_session_player($roomId, $token, $playerName);
header('Location: room.php?id=' . $roomId);
exit;
}
}
if ($action === 'join') {
$roomId = (int) ($_POST['room_id'] ?? 0);
if ($roomId <= 0) {
$errors[] = 'Select a room to join.';
}
if ($playerName === '') {
$errors[] = 'Enter a player name to join.';
}
if (!$errors) {
$room = get_room($roomId);
if (!$room) {
$errors[] = 'Room not found.';
} else {
try {
$token = random_token();
$state = add_player_to_room($room, $playerName, $token);
save_room_state($roomId, $state, $room['status']);
set_session_player($roomId, $token, $playerName);
header('Location: room.php?id=' . $roomId);
exit;
} catch (RuntimeException $e) {
$errors[] = $e->getMessage();
}
}
}
}
}
$stmt = db()->prepare('SELECT * FROM rooms ORDER BY updated_at DESC LIMIT 20');
$stmt->execute();
$rooms = $stmt->fetchAll();
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bomber Rooms — Online Multiplayer Lobby</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php else: ?>
<meta name="description" content="Лобби и комнаты для классического Bomberman: создавайте матч, подключайтесь к друзьям и запускайте игру." />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time(); ?>" />
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom small shadow-sm sticky-top">
<div class="container">
<a class="navbar-brand fw-semibold text-dark" href="/">Bomber Rooms</a>
<div class="d-flex gap-2">
<a class="btn btn-outline-dark btn-sm" href="#rooms">Комнаты</a>
<a class="btn btn-dark btn-sm" href="#create">Создать матч</a>
</div>
</div>
</nav>
<header class="py-5 border-bottom bg-body">
<div class="container">
<div class="row align-items-center g-4">
<div class="col-lg-7">
<p class="text-uppercase text-muted small mb-2">Retro multiplayer MVP</p>
<h1 class="display-6 fw-semibold">Классический Bomberman онлайн — лобби, комнаты, матч</h1>
<p class="text-muted mt-3 mb-4">Создайте комнату, позовите друзей и запустите быстрый матч. MVP синхронизирует движение и бомбы через частый опрос — в следующем шаге добавим WebSocket для настоящего realtime.</p>
<div class="d-flex flex-wrap gap-2">
<a class="btn btn-dark" href="#create">Запустить комнату</a>
<a class="btn btn-outline-dark" href="#rooms">Смотреть лобби</a>
</div>
</div>
<div class="col-lg-5">
<div class="panel p-4">
<h2 class="h6 text-uppercase text-muted">Статус MVP</h2>
<ul class="list-unstyled mb-0 small">
<li class="d-flex justify-content-between py-2 border-bottom"><span>Комнаты</span><span><?= count($rooms) ?></span></li>
<li class="d-flex justify-content-between py-2 border-bottom"><span>Сетка</span><span>13 × 11</span></li>
<li class="d-flex justify-content-between py-2"><span>Текущее время</span><span><?= htmlspecialchars($now) ?> UTC</span></li>
</ul>
</div>
</div>
</div>
</div>
</header>
<main class="py-5">
<div class="container">
<?php if ($flash): ?>
<div class="alert alert-info"><?= htmlspecialchars($flash) ?></div>
<?php endif; ?>
<?php if ($errors): ?>
<div class="alert alert-danger">
<?= htmlspecialchars(implode(' ', $errors)) ?>
</div>
<?php endif; ?>
<div id="create" class="row g-4 mb-5">
<div class="col-lg-6">
<div class="panel p-4 h-100">
<h2 class="h5 fw-semibold mb-3">Создать комнату</h2>
<form method="post" class="vstack gap-3">
<input type="hidden" name="action" value="create">
<div>
<label class="form-label small text-muted">Название комнаты</label>
<input type="text" name="room_name" class="form-control" placeholder="Например: Retro Arena">
</div>
<div>
<label class="form-label small text-muted">Ваш ник</label>
<input type="text" name="player_name" class="form-control" placeholder="Player 1" required>
</div>
<button class="btn btn-dark w-100">Создать и войти</button>
</form>
</div>
</div>
<div class="col-lg-6">
<div class="panel p-4 h-100">
<h2 class="h5 fw-semibold mb-3">Как работает матч</h2>
<ol class="small text-muted mb-0">
<li class="mb-2">Создайте комнату и дождитесь 24 игроков.</li>
<li class="mb-2">Хост запускает матч — генерируется карта.</li>
<li class="mb-2">Двигайтесь WASD/стрелками, ставьте бомбы (Space).</li>
<li>Выигрывает последний выживший.</li>
</ol>
</div>
</div>
</div>
<section id="rooms" class="mb-5">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="h5 fw-semibold mb-0">Активные комнаты</h2>
<span class="text-muted small"><?= count($rooms) ?> комнат</span>
</div>
<div class="panel p-0 overflow-hidden">
<div class="table-responsive">
<table class="table table-sm align-middle mb-0">
<thead class="table-light">
<tr>
<th>Комната</th>
<th>Статус</th>
<th>Игроки</th>
<th>Действие</th>
</tr>
</thead>
<tbody>
<?php if (!$rooms): ?>
<tr>
<td colspan="4" class="text-center text-muted py-4">Пока нет комнат — создайте первую выше.</td>
</tr>
<?php else: ?>
<?php foreach ($rooms as $room): ?>
<?php
$state = json_decode($room['state_json'], true) ?: [];
$playerCount = count($state['players'] ?? []);
?>
<tr>
<td>
<div class="fw-semibold"><?= htmlspecialchars($room['name']) ?></div>
<div class="text-muted small">#<?= (int) $room['id'] ?></div>
</td>
<td><span class="badge text-bg-light border"><?= htmlspecialchars($room['status']) ?></span></td>
<td><?= $playerCount ?> / <?= (int) $room['max_players'] ?></td>
<td>
<form method="post" class="d-flex gap-2 align-items-center">
<input type="hidden" name="action" value="join">
<input type="hidden" name="room_id" value="<?= (int) $room['id'] ?>">
<input type="text" name="player_name" class="form-control form-control-sm" placeholder="Ник" required>
<button class="btn btn-outline-dark btn-sm">Войти</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</section>
</div>
</main>
<footer class="border-top py-4">
<div class="container small text-muted d-flex justify-content-between flex-wrap gap-2">
<span>PHP <?= htmlspecialchars($phpVersion) ?></span>
<span>Обновлено <?= htmlspecialchars($now) ?> UTC</span>
<a class="text-muted" href="/healthz">/healthz</a>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?= time(); ?>"></script>
</body>
</html>