120 lines
3.8 KiB
PHP
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();
|
|
}
|