- php/: MySQL-backed (via config.php, gitignored) top-5 solutions per level, cookie identity with rename, server-side progress for "Продолжить" - Menu screens: Играть / Продолжить / Предыстория / Настройки, plus placeholder start/end screens for a future intro/outro - Lore screen: EXO:GRID backstory text (6 epochs, no gameplay changes yet) - levels.js/solver.js copied in as-is
52 lines
2.5 KiB
PHP
52 lines
2.5 KiB
PHP
<?php
|
|
// db.php — MySQL, БД уже создана на сервере (панель Тенетты), не SQLite.
|
|
// Схема создаётся сама при первом обращении, руками ничего накатывать не
|
|
// нужно — залил файлы и работает.
|
|
|
|
function get_db(): PDO {
|
|
$cfg = require __DIR__ . '/config.php';
|
|
$dsn = "mysql:host={$cfg['host']};dbname={$cfg['db']};charset=utf8mb4";
|
|
$pdo = new PDO($dsn, $cfg['user'], $cfg['pass']);
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
$pdo->exec('CREATE TABLE IF NOT EXISTS users (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
token VARCHAR(64) UNIQUE NOT NULL,
|
|
name VARCHAR(40) NOT NULL,
|
|
last_level INT NOT NULL DEFAULT 0,
|
|
created_at INT NOT NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
|
|
|
|
// move_key хранится как md5-хеш последовательности ходов (фикс. длина
|
|
// 32 символа) — а не сырой склеенной строкой, у которой длина растёт
|
|
// с числом ходов и может упереться в лимит длины ключа InnoDB-индекса.
|
|
$pdo->exec('CREATE TABLE IF NOT EXISTS solutions (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
level_name VARCHAR(64) NOT NULL,
|
|
user_id INT NOT NULL,
|
|
moves MEDIUMTEXT NOT NULL,
|
|
move_count INT NOT NULL,
|
|
move_key CHAR(32) NOT NULL,
|
|
source VARCHAR(16) NOT NULL DEFAULT "player",
|
|
created_at INT NOT NULL,
|
|
UNIQUE KEY uniq_level_movekey (level_name, move_key)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
|
|
|
|
// MySQL/MariaDB не везде понимают "CREATE INDEX IF NOT EXISTS" — просто
|
|
// глотаем "уже есть" при повторных запусках.
|
|
try { $pdo->exec('CREATE INDEX idx_level_count ON solutions(level_name, move_count)'); }
|
|
catch (PDOException $e) {}
|
|
|
|
return $pdo;
|
|
}
|
|
|
|
// Идентичность — долгий cookie (год) с токеном, не PHP-сессия: имя должно
|
|
// помниться неделями, а не до закрытия вкладки.
|
|
function current_user(PDO $pdo): ?array {
|
|
if (empty($_COOKIE['sokoban_token'])) return null;
|
|
$stmt = $pdo->prepare('SELECT * FROM users WHERE token = ?');
|
|
$stmt->execute([$_COOKIE['sokoban_token']]);
|
|
$u = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
return $u ?: null;
|
|
}
|