From e848fef6aacddd91ec26f698d10f6793d80765ae Mon Sep 17 00:00:00 2001 From: Enzo HAMID Date: Wed, 29 Jan 2025 09:37:55 +0100 Subject: [PATCH 1/4] =?UTF-8?q?:construction:=20Ajout=20d'instructions=20p?= =?UTF-8?q?our=20g=C3=A9rer=20les=20joueurs=20invit=C3=A9s=20(donn=C3=A9es?= =?UTF-8?q?=20stock=C3=A9es=20en=20session)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controllers/controllerGame.class.php | 51 +++++++++++++++++++++--- src/Models/player.dao.php | 31 ++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/Controllers/controllerGame.class.php b/src/Controllers/controllerGame.class.php index 8b24ca7c0..aa52929c9 100644 --- a/src/Controllers/controllerGame.class.php +++ b/src/Controllers/controllerGame.class.php @@ -30,6 +30,7 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; +use Ramsey\Uuid\Uuid; /** * @brief Classe ControllerGame @@ -261,6 +262,23 @@ public function showGame(string $code): void (new GameRecordDAO($this->getPdo()))->delete($gameRecord->getCode()); throw new NotFoundException("La partie n'existe pas"); } + + if (!isset($_SESSION['uuid']) || $_SESSION['uuid'] === null) { + // Crée un invité + // Genération des informations de l'invité + $guestUuid = Uuid::uuid4()->toString(); + $guestUsername = "Guest_" . rand(1000, 9999); + + // Stock les informations dans la session + $_SESSION['uuid'] = $guestUuid; + $_SESSION['username'] = $guestUsername; + $_SESSION['comusCoin'] = 0; + $_SESSION['elo'] = 0; + $_SESSION['xp'] = 0; + $_SESSION['basket'] = null; + $_SESSION['pfpPath'] = 'default-pfp.jpg'; + $_SESSION['is_guest'] = true; + } if ($gameRecord->getState() == GameRecordState::WAITING) { $this->showGameSettings($gameRecord); @@ -296,18 +314,37 @@ private function showGameSettings(GameRecord $gameRecord): void } else { $settings = []; } - $template = $this->getTwig()->load('player/game-settings.twig'); - echo $template->render([ + + // récupération des joueurs de la base de données + $players = array_map(fn($player) => $player['player'], $gameRecord->getPlayers()); + + // si le joueur est un guest, créer un player guest avec les informations en session et l'ajouter au tableau des joueurs + if (isset($_SESSION['is_guest']) && $_SESSION['is_guest'] === true) { + $guestPlayer = [ + 'uuid' => $_SESSION['uuid'], + 'username' => $_SESSION['username'], + 'comusCoin' => $_SESSION['comusCoin'], + 'elo' => $_SESSION['elo'], + 'xp' => $_SESSION['xp'], + 'pfpPath' => $_SESSION['pfpPath'] + ]; + $players[] = (object) $guestPlayer; + } + + // créer le gameState avec toutes les informations + $gameState = [ "code" => $gameRecord->getCode(), "isHost" => $gameRecord->getHostedBy()->getUuid() == $_SESSION['uuid'], - "players" => array_map(fn($player) => $player['player'], $gameRecord->getPlayers()), + "players" => $players, "game" => $gameRecord->getGame(), "chat" => $gameSettings["settings"]["allowChat"], "gameFileInfos" => $gameSettings["game"], "settings" => $settings, "isPrivate" => $gameRecord->isPrivate(), - ]); + ]; + + echo $template->render($gameState); } /** @@ -365,8 +402,10 @@ private function joinGame(string $code, ?string $playerUuid = null): string } if (!in_array($playerUuid, array_map(fn($player) => $player['player']->getUuid(), $gameRecord->getPlayers()))) { - $gameRecordManager->addPlayer($gameRecord, $player); - } + // empêche la récupération d'informations en base de données si le joueur est un guest + if(!isset($_SESSION['is_guest']) || $_SESSION['is_guest'] === false) { + $gameRecordManager->addPlayer($gameRecord, $player);} + } return json_encode([ "success" => true, diff --git a/src/Models/player.dao.php b/src/Models/player.dao.php index ea59ee03f..ac771f735 100644 --- a/src/Models/player.dao.php +++ b/src/Models/player.dao.php @@ -290,6 +290,37 @@ public function createPlayer(string $username, string $email): bool return $stmtPlayer->execute(); } + /** + * @brief Crée un nouveau joueur invité dans la base de données + * + * @details Cette méthode génère un UUID unique pour le joueur, et insère un nouvel enregistrement dans la table des joueurs avec l'UUID et le nom d'utilisateur. + * + * @param string $username Le nom d'utilisateur du joueur + * @return bool Retourne true si le joueur a été créé avec succès, false sinon + * @throws DateMalformedStringException Exception levée dans le cas d'une date malformée + */ + public function createGuestPlayer(string $username): bool + { + // Récupère l'ID du dernier joueur enregistré + $stmt = $this->pdo->query('SELECT MAX(id) as max_id FROM ' . DB_PREFIX . 'user'); + $stmt->setFetchMode(PDO::FETCH_ASSOC); + $result = $stmt->fetch(); + + $userId = $result['max_id'] + 1; + + + // Genération de l'uuid du joueur guest + $uuid = "guest" . Uuid::uuid4()->toString(); + + $stmtPlayer = $this->pdo->prepare("INSERT INTO " . DB_PREFIX . "player (uuid, username, user_id) VALUES (:uuid, :username, :user_id)"); + + $stmtPlayer->bindParam(':uuid', $uuid); + $stmtPlayer->bindParam(':username', $username); + $stmtPlayer->bindParam(':user_id', $userId); + + return $stmtPlayer->execute(); + } + /** * @brief Retourne un objet Player (ou null) à partir du nom d'utilisateur passé en paramètre * @param string|null $username Le nom d'utilisateur du joueur à retrouver From 55e1aa3ef7c0e5b3402110c6f2e5c83f1c35da3d Mon Sep 17 00:00:00 2001 From: Enzo HAMID Date: Wed, 29 Jan 2025 09:39:26 +0100 Subject: [PATCH 2/4] =?UTF-8?q?:construction:=20Ajout=20d'un=20websocket?= =?UTF-8?q?=20pour=20les=20jeux=20(pour=20que=20les=20navigateurs=20de=20t?= =?UTF-8?q?ous=20les=20utilisateurs=20prennent=20en=20compte=20les=20joueu?= =?UTF-8?q?rs=20invit=C3=A9s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/assets/js/game.js | 1 + src/App/Sockets/Game.php | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/public/assets/js/game.js b/public/assets/js/game.js index c4beb70f8..77376ea3f 100644 --- a/public/assets/js/game.js +++ b/public/assets/js/game.js @@ -116,6 +116,7 @@ function closeModal() { // WebSocket const conn = new WebSocket('ws://localhost:8315/chat/' + gameCode); +const connGame = new WebSocket('ws://localhost:8080/game/' + gameCode); conn.onopen = function (e) { console.log("Connexion établie !"); }; diff --git a/src/App/Sockets/Game.php b/src/App/Sockets/Game.php index 218d25c18..2869a3098 100644 --- a/src/App/Sockets/Game.php +++ b/src/App/Sockets/Game.php @@ -16,13 +16,20 @@ class Game implements MessageComponentInterface { + protected $clients; + + public function __construct() + { + $this->clients = new \SplObjectStorage; + } /** * @inheritDoc */ function onOpen(ConnectionInterface $conn) { - // TODO: Implement onOpen() method. + $this->clients->attach($conn); + echo "new connection: ({$conn->resourceId})\n"; } /** @@ -30,7 +37,8 @@ function onOpen(ConnectionInterface $conn) */ function onClose(ConnectionInterface $conn) { - // TODO: Implement onClose() method. + $this->clients->detach($conn); + echo "connection {$conn->resourceId} has disconnected\n"; } /** @@ -38,7 +46,8 @@ function onClose(ConnectionInterface $conn) */ function onError(ConnectionInterface $conn, Exception $e) { - // TODO: Implement onError() method. + echo "error occured: {$e->getMessage()}\n"; + $conn->close(); } /** @@ -46,6 +55,12 @@ function onError(ConnectionInterface $conn, Exception $e) */ function onMessage(ConnectionInterface $from, $msg) { - // TODO: Implement onMessage() method. + $data = json_decode($msg, true); + // handle incomming messages and brodcast to all clients + foreach ($this->clients as $client) { + if($from !== $client) { + $client->send(json_encode($data)); + } + } } } \ No newline at end of file From 3f9b0d03acae0f5dec5b19c36922327355769de4 Mon Sep 17 00:00:00 2001 From: Enzo HAMID Date: Wed, 29 Jan 2025 09:46:29 +0100 Subject: [PATCH 3/4] =?UTF-8?q?:construction:=20Ajout=20du=20broadcast=20a?= =?UTF-8?q?u=20moment=20du=20param=C3=A9trage=20de=20la=20partie=20(pour?= =?UTF-8?q?=20envoyer=20tous=20les=20joueurs=20aux=20joueurs=20de=20la=20p?= =?UTF-8?q?artie)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controllers/controllerGame.class.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Controllers/controllerGame.class.php b/src/Controllers/controllerGame.class.php index aa52929c9..b462014a5 100644 --- a/src/Controllers/controllerGame.class.php +++ b/src/Controllers/controllerGame.class.php @@ -343,10 +343,20 @@ private function showGameSettings(GameRecord $gameRecord): void "settings" => $settings, "isPrivate" => $gameRecord->isPrivate(), ]; + + // broadcast le gameState à tous les joueurs avec un websocket + $this->broadcastGameState($gameState); echo $template->render($gameState); } + private function broadcastGameState(array $gameState): void + { + $wsClient = new WebSocket\Client("ws://localhost:8080/game"); // TODO change class or remove? + $wsClient->send(json_encode($gameState)); + $wsClient->close(); + } + /** * @brief Rejoint une partie avec un code (Méthode GET ou POST autorisée) * @param string $method Méthode HTTP utilisée From feea4ceaf6e5becf39d9df0766f86b1902fba008 Mon Sep 17 00:00:00 2001 From: Enzo HAMID Date: Thu, 30 Jan 2025 09:32:52 +0100 Subject: [PATCH 4/4] =?UTF-8?q?:sparkles:=20Finalisation=20de=20la=20prise?= =?UTF-8?q?=20en=20compte=20des=20guests=20dans=20le=20param=C3=A9trage=20?= =?UTF-8?q?de=20la=20partie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/assets/js/game.js | 31 ++++++++- src/App/Sockets/Game.php | 18 ++++- src/Controllers/controllerGame.class.php | 85 ++++++++---------------- 3 files changed, 73 insertions(+), 61 deletions(-) diff --git a/public/assets/js/game.js b/public/assets/js/game.js index 83a0402ab..c67852cc3 100644 --- a/public/assets/js/game.js +++ b/public/assets/js/game.js @@ -117,6 +117,14 @@ function closeModal() { background.classList.add("hidden"); } +function generateUUID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + // WebSocket const chatConnection = new WebSocket('ws://sockets.comus-party.com/chat/' + gameCode); chatConnection.onopen = function (e) { @@ -130,7 +138,28 @@ const gameConnection = new WebSocket('ws://localhost:8315/game/' + gameCode); gameConnection.onopen = function (e) { console.log("Connexion établie avec GAME_SOCKET !"); - gameConnection.send(JSON.stringify({uuid: playerUuid, command: 'joinGame', game: gameCode})); + // génère un uuid et un username + const guestUuid = generateUUID(); + const guestUsername = "Guest_" + Math.floor(Math.random() * 9000 + 1000); + + // stock les informations du guest en session + sessionStorage.setItem('uuid', guestUuid); + sessionStorage.setItem('username', guestUsername); + sessionStorage.setItem('elo', '0'); + sessionStorage.setItem('xp', '0'); + sessionStorage.setItem('pfpPath', 'default-pfp.jpg'); + sessionStorage.setItem('is_guest', 'true'); + + const sessionData = { + uuid: sessionStorage.getItem('uuid'), + username: sessionStorage.getItem('username'), + elo: sessionStorage.getItem('elo'), + xp: sessionStorage.getItem('xp'), + pfpPath: sessionStorage.getItem('pfpPath'), + is_guest: sessionStorage.getItem('is_guest') + }; + + gameConnection.send(JSON.stringify({uuid: playerUuid, command: 'joinGame', game: gameCode, sessionData: sessionData})); }; gameConnection.onmessage = function (e) { const data = JSON.parse(e.data); diff --git a/src/App/Sockets/Game.php b/src/App/Sockets/Game.php index ec4166b8b..7e603c103 100644 --- a/src/App/Sockets/Game.php +++ b/src/App/Sockets/Game.php @@ -46,6 +46,7 @@ function onMessage(ConnectionInterface $from, $msg): void $uuid = $data['uuid']; $game = $data['game']; $command = $data['command']; + $sessionData = $data['sessionData']; if (!isset($this->games[$game])) { $this->games[$game] = []; @@ -58,7 +59,7 @@ function onMessage(ConnectionInterface $from, $msg): void switch ($command) { case 'quitGame': case 'joinGame': - $this->updatePlayer($game); + $this->updatePlayer($game, $sessionData); break; case 'startGame': $this->redirectUserToGame($game, $uuid); @@ -68,7 +69,7 @@ function onMessage(ConnectionInterface $from, $msg): void } } - private function updatePlayer(string $game): void + private function updatePlayer(string $game, array | null $sessionData): void { $gameRecord = (new GameRecordDAO(Db::getInstance()->getConnection()))->findByCode($game); @@ -79,6 +80,17 @@ private function updatePlayer(string $game): void "pfp" => $player['player']->getActivePfp() ], $players); + if (isset($sessionData['is_guest']) && $sessionData['is_guest'] === 'true') { + $guestPlayer = [ + "uuid" => $sessionData['uuid'], + "username" => $sessionData['username'], + "elo" => $sessionData['elo'], + "xp" => $sessionData['xp'], + "pfp" => $sessionData['pfpPath'] + ]; + $jsonPlayer[] = $guestPlayer; + } + $this->sendToGame($game, "updatePlayer", json_encode($jsonPlayer)); } @@ -104,7 +116,7 @@ public function onClose(ConnectionInterface $conn): void foreach ($this->games as $game => $clients) { $key = array_search($conn, $clients); if ($key !== false) { - $this->updatePlayer($game); + $this->updatePlayer($game, null); unset($this->games[$game][$key]); } } diff --git a/src/Controllers/controllerGame.class.php b/src/Controllers/controllerGame.class.php index b462014a5..15e5b0b8c 100644 --- a/src/Controllers/controllerGame.class.php +++ b/src/Controllers/controllerGame.class.php @@ -262,23 +262,6 @@ public function showGame(string $code): void (new GameRecordDAO($this->getPdo()))->delete($gameRecord->getCode()); throw new NotFoundException("La partie n'existe pas"); } - - if (!isset($_SESSION['uuid']) || $_SESSION['uuid'] === null) { - // Crée un invité - // Genération des informations de l'invité - $guestUuid = Uuid::uuid4()->toString(); - $guestUsername = "Guest_" . rand(1000, 9999); - - // Stock les informations dans la session - $_SESSION['uuid'] = $guestUuid; - $_SESSION['username'] = $guestUsername; - $_SESSION['comusCoin'] = 0; - $_SESSION['elo'] = 0; - $_SESSION['xp'] = 0; - $_SESSION['basket'] = null; - $_SESSION['pfpPath'] = 'default-pfp.jpg'; - $_SESSION['is_guest'] = true; - } if ($gameRecord->getState() == GameRecordState::WAITING) { $this->showGameSettings($gameRecord); @@ -316,46 +299,37 @@ private function showGameSettings(GameRecord $gameRecord): void } $template = $this->getTwig()->load('player/game-settings.twig'); - // récupération des joueurs de la base de données - $players = array_map(fn($player) => $player['player'], $gameRecord->getPlayers()); - - // si le joueur est un guest, créer un player guest avec les informations en session et l'ajouter au tableau des joueurs - if (isset($_SESSION['is_guest']) && $_SESSION['is_guest'] === true) { - $guestPlayer = [ - 'uuid' => $_SESSION['uuid'], - 'username' => $_SESSION['username'], - 'comusCoin' => $_SESSION['comusCoin'], - 'elo' => $_SESSION['elo'], - 'xp' => $_SESSION['xp'], - 'pfpPath' => $_SESSION['pfpPath'] + // récupération des joueurs de la base de données + $players = array_map(fn($player) => $player['player'], $gameRecord->getPlayers()); + + // si le joueur est un guest, créer un player guest avec les informations en session et l'ajouter au tableau des joueurs + if (isset($_SESSION['is_guest']) && $_SESSION['is_guest'] === true) { + $guestPlayer = [ + 'uuid' => $_SESSION['uuid'], + 'username' => $_SESSION['username'], + 'comusCoin' => $_SESSION['comusCoin'], + 'elo' => $_SESSION['elo'], + 'xp' => $_SESSION['xp'], + 'pfpPath' => $_SESSION['pfpPath'] + ]; + $players[] = (object) $guestPlayer; + } + + // créer le gameState avec toutes les informations + $gameState = [ + "code" => $gameRecord->getCode(), + "isHost" => $gameRecord->getHostedBy()->getUuid() == $_SESSION['uuid'], + "players" => $players, + "game" => $gameRecord->getGame(), + "chat" => $gameSettings["settings"]["allowChat"], + "gameFileInfos" => $gameSettings["game"], + "settings" => $settings, + "isPrivate" => $gameRecord->isPrivate(), ]; - $players[] = (object) $guestPlayer; - } - - // créer le gameState avec toutes les informations - $gameState = [ - "code" => $gameRecord->getCode(), - "isHost" => $gameRecord->getHostedBy()->getUuid() == $_SESSION['uuid'], - "players" => $players, - "game" => $gameRecord->getGame(), - "chat" => $gameSettings["settings"]["allowChat"], - "gameFileInfos" => $gameSettings["game"], - "settings" => $settings, - "isPrivate" => $gameRecord->isPrivate(), - ]; - // broadcast le gameState à tous les joueurs avec un websocket - $this->broadcastGameState($gameState); - - echo $template->render($gameState); + echo $template->render($gameState); } - private function broadcastGameState(array $gameState): void - { - $wsClient = new WebSocket\Client("ws://localhost:8080/game"); // TODO change class or remove? - $wsClient->send(json_encode($gameState)); - $wsClient->close(); - } /** * @brief Rejoint une partie avec un code (Méthode GET ou POST autorisée) @@ -405,17 +379,14 @@ private function joinGame(string $code, ?string $playerUuid = null): string $player = (new PlayerDAO($this->getPdo()))->findByUuid($playerUuid); - // TODO: Fonctionnement pour un joueur non connecté a insérer ici - if ($gameRecord->getState() != GameRecordState::WAITING) { throw new GameUnavailableException("La partie a déjà commencé"); } if (!in_array($playerUuid, array_map(fn($player) => $player['player']->getUuid(), $gameRecord->getPlayers()))) { - // empêche la récupération d'informations en base de données si le joueur est un guest if(!isset($_SESSION['is_guest']) || $_SESSION['is_guest'] === false) { $gameRecordManager->addPlayer($gameRecord, $player);} - } + } return json_encode([ "success" => true,