39315-vm/tetris_data.php
2026-03-25 15:17:29 +00:00

120 lines
3.8 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/db/config.php';
function tetrisEnsureSchema(): void
{
static $ready = false;
if ($ready) {
return;
}
db()->exec(
"CREATE TABLE IF NOT EXISTS tetris_scores (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
player_name VARCHAR(24) NOT NULL,
score INT UNSIGNED NOT NULL DEFAULT 0,
lines_cleared INT UNSIGNED NOT NULL DEFAULT 0,
level_reached INT UNSIGNED NOT NULL DEFAULT 1,
duration_seconds INT UNSIGNED NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_score (score DESC, lines_cleared DESC),
INDEX idx_created_at (created_at DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$ready = true;
}
function tetrisFetchLeaderboard(int $limit = 10): array
{
tetrisEnsureSchema();
$stmt = db()->prepare(
'SELECT id, player_name, score, lines_cleared, level_reached, duration_seconds, created_at
FROM tetris_scores
ORDER BY score DESC, lines_cleared DESC, duration_seconds ASC, id ASC
LIMIT :limit'
);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
function tetrisFetchRecent(int $limit = 8): array
{
tetrisEnsureSchema();
$stmt = db()->prepare(
'SELECT id, player_name, score, lines_cleared, level_reached, duration_seconds, created_at
FROM tetris_scores
ORDER BY created_at DESC, id DESC
LIMIT :limit'
);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
function tetrisFetchScoreById(int $id): ?array
{
tetrisEnsureSchema();
$stmt = db()->prepare(
'SELECT id, player_name, score, lines_cleared, level_reached, duration_seconds, created_at
FROM tetris_scores
WHERE id = :id
LIMIT 1'
);
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$score = $stmt->fetch();
return $score ?: null;
}
function tetrisFetchBestScore(): ?array
{
$scores = tetrisFetchLeaderboard(1);
return $scores[0] ?? null;
}
function tetrisSaveScore(array $input): int
{
tetrisEnsureSchema();
$name = trim((string) ($input['player_name'] ?? ''));
if ($name === '') {
throw new InvalidArgumentException('Enter your name to save the run.');
}
$nameLength = function_exists('mb_strlen') ? mb_strlen($name) : strlen($name);
if ($nameLength > 24) {
throw new InvalidArgumentException('Name must be 24 characters or fewer.');
}
if (!preg_match('/^[\p{L}\p{N} _.-]+$/u', $name)) {
throw new InvalidArgumentException('Use letters, numbers, spaces, dots, dashes, or underscores only.');
}
$score = max(0, min(999999, (int) ($input['score'] ?? 0)));
$lines = max(0, min(9999, (int) ($input['lines_cleared'] ?? 0)));
$level = max(1, min(999, (int) ($input['level_reached'] ?? 1)));
$duration = max(0, min(86400, (int) ($input['duration_seconds'] ?? 0)));
if ($score === 0 && $lines === 0) {
throw new InvalidArgumentException('Finish a run before saving to the leaderboard.');
}
$stmt = db()->prepare(
'INSERT INTO tetris_scores (player_name, score, lines_cleared, level_reached, duration_seconds)
VALUES (:player_name, :score, :lines_cleared, :level_reached, :duration_seconds)'
);
$stmt->bindValue(':player_name', $name, PDO::PARAM_STR);
$stmt->bindValue(':score', $score, PDO::PARAM_INT);
$stmt->bindValue(':lines_cleared', $lines, PDO::PARAM_INT);
$stmt->bindValue(':level_reached', $level, PDO::PARAM_INT);
$stmt->bindValue(':duration_seconds', $duration, PDO::PARAM_INT);
$stmt->execute();
return (int) db()->lastInsertId();
}