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; }