423 lines
14 KiB
PHP
423 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* DisciplineSettingsModel.php
|
|
*
|
|
* Model dla ustawień dyscyplin (Ping-Pong, Papier-Kamień-Nożyce, Piłkarzyki)
|
|
* Obsługuje versioning, validację i trwałość danych
|
|
*/
|
|
|
|
class DisciplineSettingsModel
|
|
{
|
|
private $pdo;
|
|
|
|
public function __construct(PDO $pdo)
|
|
{
|
|
$this->pdo = $pdo;
|
|
$this->ensureTableExists();
|
|
}
|
|
|
|
/**
|
|
* Upewnia się, że tabela settings_disciplines istnieje
|
|
*/
|
|
private function ensureTableExists()
|
|
{
|
|
$this->pdo->exec("
|
|
CREATE TABLE IF NOT EXISTS settings_disciplines (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
discipline VARCHAR(50) NOT NULL UNIQUE,
|
|
|
|
-- Reguły gry (logika)
|
|
pointsToWin INT NOT NULL DEFAULT 10,
|
|
setsToWin INT NOT NULL DEFAULT 2,
|
|
serveRotation INT NOT NULL DEFAULT 2,
|
|
specialRules TEXT,
|
|
|
|
-- Personalizacja UI (nie wpływa na logiką gry)
|
|
customization JSON,
|
|
|
|
-- Versioning ustawień
|
|
settingsVersion INT NOT NULL DEFAULT 1,
|
|
|
|
-- Metadane
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
updated_by INT,
|
|
|
|
INDEX idx_discipline (discipline),
|
|
INDEX idx_version (settingsVersion)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
");
|
|
}
|
|
|
|
/**
|
|
* Pobiera ustawienia dla dyscypliny
|
|
*
|
|
* @param string $discipline Nazwa dyscypliny (np. 'ping-pong')
|
|
* @return array|null Ustawienia lub null jeśli nie istnieją
|
|
*/
|
|
public function getSettings($discipline)
|
|
{
|
|
$stmt = $this->pdo->prepare("
|
|
SELECT * FROM settings_disciplines
|
|
WHERE discipline = :discipline
|
|
LIMIT 1
|
|
");
|
|
$stmt->execute([':discipline' => $discipline]);
|
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$row) {
|
|
return null;
|
|
}
|
|
|
|
// Rzutuj INT kolumny
|
|
$row['pointsToWin'] = (int)$row['pointsToWin'];
|
|
$row['setsToWin'] = (int)$row['setsToWin'];
|
|
$row['serveRotation'] = (int)$row['serveRotation'];
|
|
$row['settingsVersion'] = (int)$row['settingsVersion'];
|
|
$row['updated_by'] = $row['updated_by'] ? (int)$row['updated_by'] : null;
|
|
|
|
// Dekoduj JSON fields
|
|
if (!empty($row['customization'])) {
|
|
$row['customization'] = json_decode($row['customization'], true);
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
/**
|
|
* Pobiera ustawienia z określonej wersji
|
|
* (do snapshot'ów przy starcie meczu)
|
|
*
|
|
* @param string $discipline Nazwa dyscypliny
|
|
* @param int $version Numer wersji
|
|
* @return array|null Ustawienia danej wersji
|
|
*/
|
|
public function getSettingsByVersion($discipline, $version)
|
|
{
|
|
// TODO: W przyszłości można dodać tabelę settings_disciplines_history
|
|
// dla pełnej historii zmian
|
|
$stmt = $this->pdo->prepare("
|
|
SELECT * FROM settings_disciplines
|
|
WHERE discipline = :discipline
|
|
AND settingsVersion = :version
|
|
LIMIT 1
|
|
");
|
|
$stmt->execute([
|
|
':discipline' => $discipline,
|
|
':version' => (int)$version
|
|
]);
|
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$row) {
|
|
return null;
|
|
}
|
|
|
|
// Rzutuj INT kolumny
|
|
$row['pointsToWin'] = (int)$row['pointsToWin'];
|
|
$row['setsToWin'] = (int)$row['setsToWin'];
|
|
$row['serveRotation'] = (int)$row['serveRotation'];
|
|
$row['settingsVersion'] = (int)$row['settingsVersion'];
|
|
$row['updated_by'] = $row['updated_by'] ? (int)$row['updated_by'] : null;
|
|
|
|
if (!empty($row['customization'])) {
|
|
$row['customization'] = json_decode($row['customization'], true);
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
/**
|
|
* Aktualizuje ustawienia dla dyscypliny
|
|
* Automatycznie zwiększa versioning
|
|
*
|
|
* @param string $discipline Nazwa dyscypliny
|
|
* @param array $settings Nowe ustawienia
|
|
* @param int $userId ID użytkownika wykonującego zmianę
|
|
* @return array Zaktualizowane ustawienia
|
|
* @throws Exception
|
|
*/
|
|
public function updateSettings($discipline, array $settings, $userId)
|
|
{
|
|
// Waliduj dane
|
|
$this->validateSettingsInput($settings);
|
|
|
|
// Pobierz obecne ustawienia, aby zwiększyć versioning
|
|
$current = $this->getSettings($discipline);
|
|
$newVersion = ($current ? (int)$current['settingsVersion'] + 1 : 1);
|
|
|
|
// Przygotuj dane do insertu/update
|
|
$data = [
|
|
':discipline' => $discipline,
|
|
':pointsToWin' => (int)$settings['pointsToWin'],
|
|
':setsToWin' => (int)$settings['setsToWin'],
|
|
':serveRotation' => (int)$settings['serveRotation'],
|
|
':specialRules' => $settings['specialRules'] ?? null,
|
|
':customization' => !empty($settings['customization'])
|
|
? json_encode($settings['customization'], JSON_UNESCAPED_UNICODE)
|
|
: null,
|
|
':settingsVersion' => $newVersion,
|
|
':updated_by' => $userId
|
|
];
|
|
|
|
$this->pdo->beginTransaction();
|
|
try {
|
|
if ($current) {
|
|
// UPDATE
|
|
$stmt = $this->pdo->prepare("
|
|
UPDATE settings_disciplines SET
|
|
pointsToWin = :pointsToWin,
|
|
setsToWin = :setsToWin,
|
|
serveRotation = :serveRotation,
|
|
specialRules = :specialRules,
|
|
customization = :customization,
|
|
settingsVersion = :settingsVersion,
|
|
updated_by = :updated_by,
|
|
updated_at = NOW()
|
|
WHERE discipline = :discipline
|
|
");
|
|
} else {
|
|
// INSERT (nowa dyscyplina)
|
|
$stmt = $this->pdo->prepare("
|
|
INSERT INTO settings_disciplines (
|
|
discipline,
|
|
pointsToWin,
|
|
setsToWin,
|
|
serveRotation,
|
|
specialRules,
|
|
customization,
|
|
settingsVersion,
|
|
updated_by
|
|
) VALUES (
|
|
:discipline,
|
|
:pointsToWin,
|
|
:setsToWin,
|
|
:serveRotation,
|
|
:specialRules,
|
|
:customization,
|
|
:settingsVersion,
|
|
:updated_by
|
|
)
|
|
");
|
|
}
|
|
|
|
$stmt->execute($data);
|
|
|
|
$result = $this->getSettings($discipline);
|
|
$this->pdo->commit();
|
|
|
|
return $result;
|
|
} catch (Exception $e) {
|
|
$this->pdo->rollBack();
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Waliduje dane wejściowe ustawień
|
|
*
|
|
* @param array $settings Ustawienia do walidacji
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
private function validateSettingsInput(array $settings)
|
|
{
|
|
$errors = [];
|
|
|
|
// Walidacja pointsToWin
|
|
if (!isset($settings['pointsToWin'])) {
|
|
$errors[] = 'pointsToWin is required';
|
|
} else {
|
|
$ptw = (int)$settings['pointsToWin'];
|
|
if ($ptw < 1 || $ptw > 100) {
|
|
$errors[] = 'pointsToWin must be between 1 and 100';
|
|
}
|
|
}
|
|
|
|
// Walidacja setsToWin
|
|
if (!isset($settings['setsToWin'])) {
|
|
$errors[] = 'setsToWin is required';
|
|
} else {
|
|
$stw = (int)$settings['setsToWin'];
|
|
if ($stw < 1 || $stw > 100) {
|
|
$errors[] = 'setsToWin must be between 1 and 100';
|
|
}
|
|
}
|
|
|
|
// Walidacja serveRotation
|
|
if (!isset($settings['serveRotation'])) {
|
|
$errors[] = 'serveRotation is required';
|
|
} else {
|
|
$sr = (int)$settings['serveRotation'];
|
|
if ($sr < 1 || $sr > 50) {
|
|
$errors[] = 'serveRotation must be between 1 and 50';
|
|
}
|
|
}
|
|
|
|
// Walidacja specialRules (opcjonalne, ale jeśli podane to string)
|
|
if (isset($settings['specialRules']) && !is_string($settings['specialRules'])) {
|
|
$errors[] = 'specialRules must be a string';
|
|
}
|
|
|
|
// Walidacja customization (opcjonalne, ale jeśli podane to musi być array/object)
|
|
if (isset($settings['customization'])) {
|
|
if (!is_array($settings['customization']) && !is_object($settings['customization'])) {
|
|
$errors[] = 'customization must be an object/array';
|
|
}
|
|
}
|
|
|
|
// Logika biznesowa
|
|
// Remis - wymuszenie override reguł
|
|
if (isset($settings['pointsToWin']) && isset($settings['setsToWin'])) {
|
|
$ptw = (int)$settings['pointsToWin'];
|
|
$stw = (int)$settings['setsToWin'];
|
|
|
|
// Jeśli oba są parzyste, możliwy jest remis - lepiej wymusić nieparzyste
|
|
// W przypadku remisu w ostatnim secie, gracze muszą grać dalej
|
|
if ($ptw % 2 === 0 || $stw % 2 === 0) {
|
|
$errors[] = 'pointsToWin and setsToWin should be odd numbers to avoid draws in final set';
|
|
}
|
|
}
|
|
|
|
if (!empty($errors)) {
|
|
throw new InvalidArgumentException(implode('; ', $errors));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Zwraca domyślne ustawienia dla dyscypliny
|
|
*
|
|
* @param string $discipline Nazwa dyscypliny
|
|
* @return array Domyślne ustawienia
|
|
*/
|
|
public static function getDefaults($discipline = 'ping-pong')
|
|
{
|
|
$defaults = [
|
|
'ping-pong' => [
|
|
'pointsToWin' => 11,
|
|
'setsToWin' => 3,
|
|
'serveRotation' => 2,
|
|
'specialRules' => 'Deuce at 10-10 (play until 2 points ahead)',
|
|
'customization' => [
|
|
'tableColor' => '#2d5016',
|
|
'ballColor' => '#ff6600',
|
|
'paddleColor' => '#000000',
|
|
'uiTheme' => 'dark'
|
|
]
|
|
],
|
|
'rock-paper-scissors' => [
|
|
'pointsToWin' => 5,
|
|
'setsToWin' => 1,
|
|
'serveRotation' => 1,
|
|
'specialRules' => 'Best of 1, instant rounds',
|
|
'customization' => [
|
|
'animationSpeed' => 'fast',
|
|
'uiTheme' => 'light'
|
|
]
|
|
],
|
|
'table-football' => [
|
|
'pointsToWin' => 5,
|
|
'setsToWin' => 1,
|
|
'serveRotation' => 3,
|
|
'specialRules' => 'Standard foosball rules, auto-restart after goal',
|
|
'customization' => [
|
|
'tableColor' => '#000000',
|
|
'figureColor' => '#ffffff',
|
|
'uiTheme' => 'dark'
|
|
]
|
|
]
|
|
];
|
|
|
|
return $defaults[$discipline] ?? $defaults['ping-pong'];
|
|
}
|
|
|
|
/**
|
|
* Inicjalizuje ustawienia dla dyscypliny (jeśli nie istnieją)
|
|
*
|
|
* @param string $discipline Nazwa dyscypliny
|
|
* @param int $userId ID administratora
|
|
*/
|
|
public function initializeIfNotExists($discipline, $userId)
|
|
{
|
|
if (!$this->getSettings($discipline)) {
|
|
$defaults = self::getDefaults($discipline);
|
|
$this->updateSettings($discipline, $defaults, $userId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pobiera snapshot ustawień dla meczu
|
|
* (snapshot to kopia ustawień w momencie startu meczu)
|
|
*
|
|
* @param string $discipline Nazwa dyscypliny
|
|
* @param int $version Opcjonalnie: wersja ustawień. Jeśli null, bierze najnowsze.
|
|
* @return array Snapshot do zapisania w meczu
|
|
*/
|
|
public function getSnapshot($discipline, $version = null)
|
|
{
|
|
if ($version !== null) {
|
|
$settings = $this->getSettingsByVersion($discipline, (int)$version);
|
|
} else {
|
|
$settings = $this->getSettings($discipline);
|
|
}
|
|
|
|
if (!$settings) {
|
|
throw new RuntimeException("Settings not found for discipline: $discipline");
|
|
}
|
|
|
|
return [
|
|
'discipline' => $discipline,
|
|
'settingsVersion' => (int)$settings['settingsVersion'],
|
|
'rules' => [
|
|
'pointsToWin' => (int)$settings['pointsToWin'],
|
|
'setsToWin' => (int)$settings['setsToWin'],
|
|
'serveRotation' => (int)$settings['serveRotation'],
|
|
'specialRules' => $settings['specialRules']
|
|
],
|
|
'snapshotTimestamp' => $settings['updated_at']
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Pobiera historię zmian dla dyscypliny
|
|
* (przydatne do debuggowania i audytu)
|
|
*
|
|
* @param string $discipline Nazwa dyscypliny
|
|
* @return array Historia
|
|
*/
|
|
public function getHistory($discipline)
|
|
{
|
|
// TODO: W przyszłości należy dodać tabelę settings_disciplines_history
|
|
// Po prostu zwracamy obecne dane z metadata
|
|
$current = $this->getSettings($discipline);
|
|
|
|
if (!$current) {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
[
|
|
'version' => $current['settingsVersion'],
|
|
'updated_at' => $current['updated_at'],
|
|
'updated_by' => $current['updated_by'],
|
|
'changes' => 'Latest version'
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Czyści ustawienia dyscypliny z bazy (dla testów)
|
|
*
|
|
* @param string $discipline Nazwa dyscypliny do usunięcia
|
|
* @return bool True jeśli usunięto
|
|
*/
|
|
public function deleteSettings($discipline)
|
|
{
|
|
try {
|
|
$stmt = $this->pdo->prepare("DELETE FROM settings_disciplines WHERE discipline = ?");
|
|
return $stmt->execute([$discipline]);
|
|
} catch (Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
?>
|