From 80ae25663831908f184be02b58974f2941731fe4 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 28 Apr 2025 20:56:06 +0200 Subject: [PATCH] Bestenliste und Scores gespeichert und beim Ende eines Spiel Rang anzeige --- backend/src/Database/DataBaseManager.js | 2 + backend/src/Database/ScoreManager/Score.js | 12 +++++ .../src/Database/ScoreManager/ScoreManager.js | 44 +++++++++++++++++++ .../src/SocketIO/GameManager/ClientHandler.js | 6 +-- .../GameManager/Game/Classes/Snake/Snake.js | 24 +++++----- backend/src/SocketIO/GameManager/Game/Game.js | 28 +++++++++--- .../src/SocketIO/GameManager/Game/GameLoop.js | 12 ++--- .../src/SocketIO/GameManager/GameManager.js | 10 +++-- backend/src/SocketIO/SocketIOManager.js | 2 +- doublesnake.sql | 41 +++++++++++++++++ frontend/game/scripts/Game/UI/UIManager.js | 4 ++ frontend/game/style/mainStyle.css | 2 +- 12 files changed, 155 insertions(+), 32 deletions(-) create mode 100644 backend/src/Database/ScoreManager/Score.js create mode 100644 backend/src/Database/ScoreManager/ScoreManager.js diff --git a/backend/src/Database/DataBaseManager.js b/backend/src/Database/DataBaseManager.js index 817b7d0..91afeec 100644 --- a/backend/src/Database/DataBaseManager.js +++ b/backend/src/Database/DataBaseManager.js @@ -1,5 +1,6 @@ const mySql = require("mysql2"); const UserManager = require("./UserManager/UserManager"); +const ScoreManager = require("./ScoreManager/ScoreManager"); require("dotenv").config(); class DataBaseManager { @@ -20,6 +21,7 @@ class DataBaseManager { }); this.usermanager = new UserManager(this.connection); + this.scoremanager = new ScoreManager(this.connection); } } diff --git a/backend/src/Database/ScoreManager/Score.js b/backend/src/Database/ScoreManager/Score.js new file mode 100644 index 0000000..f77ca37 --- /dev/null +++ b/backend/src/Database/ScoreManager/Score.js @@ -0,0 +1,12 @@ +class Score{ + /** @param {UserManager} usermanager */ + constructor(scoreJson){ + this.id = scoreJson.id; + this.user1ID = scoreJson.user1; + this.user2ID = scoreJson.user2; + this.score = scoreJson.score; + this.time = new Date(scoreJson.time); + } +} + +module.exports = Score; \ No newline at end of file diff --git a/backend/src/Database/ScoreManager/ScoreManager.js b/backend/src/Database/ScoreManager/ScoreManager.js new file mode 100644 index 0000000..996db88 --- /dev/null +++ b/backend/src/Database/ScoreManager/ScoreManager.js @@ -0,0 +1,44 @@ +const mySql = require("mysql2"); +const Score = require("./Score"); + +class ScoreManager{ + /**@param {mySql.Connection} connection*/ + constructor(connection) { + this.connection = connection; + } + + async getAllScores(){ + const response = await this.connection.promise().query("SELECT * FROM scores ORDER BY score DESC"); + const sortedScores = response[0].map(scoreData => new Score(scoreData)); + + return sortedScores; + } + + async createScore(newScoreJson){ + const sql = `INSERT INTO scores (user1, user2, score) VALUES ( + ${newScoreJson.user1}, ${newScoreJson.user2}, ${newScoreJson.score})`; + + const insertResult = await this.connection.promise().query(sql); + const insertId = insertResult[0].insertId; + + const selectSql = `SELECT * FROM scores WHERE id = ${insertId}`; + const selectResult = await this.connection.promise().query(selectSql); + + return new Score(selectResult[0][0]); + } + + async getScoreById(id){ + const response = await this.connection.promise().query(`SELECT * FROM scores WHERE id = ${id}`); + return new Score(response.rows[0][0]); + } + + async getScoreRang(id){ + const allSortetScores = await this.getAllScores(); + + const score = allSortetScores.find(score => score.id === id); + + return allSortetScores.indexOf(score) + 1; + } +} + +module.exports = ScoreManager; \ No newline at end of file diff --git a/backend/src/SocketIO/GameManager/ClientHandler.js b/backend/src/SocketIO/GameManager/ClientHandler.js index adb2699..fcad989 100644 --- a/backend/src/SocketIO/GameManager/ClientHandler.js +++ b/backend/src/SocketIO/GameManager/ClientHandler.js @@ -17,7 +17,7 @@ class ClientHandler { this.socket ); - this.socket.on("disconnect", () => { this.defaultDisconnect() }); + this.socket.on("disconnect", async () => { await this.defaultDisconnect() }); this.joinGame(); } @@ -31,8 +31,8 @@ class ClientHandler { this.socket.join(`game-${this.currentGameCode}`); } - defaultDisconnect(){ - this.gameManager.leaveGame(this.user, this.currentGameCode); + async defaultDisconnect(){ + await this.gameManager.leaveGame(this.user, this.currentGameCode); } } diff --git a/backend/src/SocketIO/GameManager/Game/Classes/Snake/Snake.js b/backend/src/SocketIO/GameManager/Game/Classes/Snake/Snake.js index 8676fe6..4cb953c 100644 --- a/backend/src/SocketIO/GameManager/Game/Classes/Snake/Snake.js +++ b/backend/src/SocketIO/GameManager/Game/Classes/Snake/Snake.js @@ -28,7 +28,7 @@ class Snake{ this.setup(startTiles); } - setup(startTiles){ + async setup(startTiles){ this.player.socket.emit("color", this.color); const headX = startTiles.x; @@ -66,7 +66,7 @@ class Snake{ }); } - this.checkAndDrawTiles(); + await this.checkAndDrawTiles(); } movementToAxes(movement){ @@ -82,7 +82,7 @@ class Snake{ return {num, axes}; } - move(){ + async move(){ // Aktuelles Movement Klonen nicht das es zwischendurch geändert wird if( this.movementToAxes(this.nextMovement).axes @@ -156,7 +156,7 @@ class Snake{ else if (dy === 1) end.deg = 270; else if (dy === -1) end.deg = 90; - this.checkAndDrawTiles(); + await this.checkAndDrawTiles(); } getBigger(){ @@ -181,22 +181,24 @@ class Snake{ this.tiles.push(newEnd); } - checkAndDrawTiles(){ - this.tiles.forEach(tile => { + async checkAndDrawTiles(){ + for (const tile of this.tiles) { const exsitingtile = this.playground.getTile(tile.x, tile.y); - + // TODO: Klammert man das ein Kann man alleine Spielen, da es keine Kolisionserkennung gibt if(exsitingtile === undefined){ // End Game weil außerhalb des Spielfeldes - this.game.endGame(`${this.player.username} hat die Wand berührt!`) + await this.game.endGame(`${this.player.username} hat die Wand berührt!`); + return; } if(exsitingtile?.class === "Snake"){ // Eng Game weil schon belegt mit anderer oder eigender Schlange - this.game.endGame(`Es gab eine Kollision!`) + await this.game.endGame(`Es gab eine Kollision!`); + return; } - + this.playground.setTile(tile.x, tile.y, tile); - }) + } } updateNextMovement(data){ diff --git a/backend/src/SocketIO/GameManager/Game/Game.js b/backend/src/SocketIO/GameManager/Game/Game.js index 9bfd8b9..259166b 100644 --- a/backend/src/SocketIO/GameManager/Game/Game.js +++ b/backend/src/SocketIO/GameManager/Game/Game.js @@ -21,6 +21,9 @@ class Game{ /**@type {Array} */ this.players = []; + /**@type {Array} */ + this.playerIds = []; + this.gameLoop = new GameLoop(io, this); setTimeout(() => { this.waitingForPlayers(true) }, 100); @@ -35,6 +38,9 @@ class Game{ // 2 Schlangen für die Spieler Instazieren this.players.forEach((player, i) => { + // id pushen um nachher das Endgame zu beenden + this.playerIds.push(player.id); + const start = (10 * i + 5) - 1; const snake = new Snake( player, @@ -54,20 +60,29 @@ class Game{ this.gameLoop.loop(); } - endGame(msg){ - // TODO: Spielende in die Datenbank eintragen + async endGame(msg){ + // TODO: Spielende in die Datenbank eintragen + this.gameStarted = false; + + const scoreDb = await this.gameManager.db.scoremanager.createScore({ + user1: this.playerIds[0], + user2: this.playerIds[1], + score: this.score + }); + const rang = await this.gameManager.db.scoremanager.getScoreRang(scoreDb.id); this.io.to(`game-${this.code}`).emit("gameEnd", { msg: msg, score: this.score, + rang: rang }); } - waitingForPlayers(changeTime = false){ + async waitingForPlayers(changeTime = false){ if(this.waitingSeconds <= 0){ if(this.players.length < 2) { - this.endGame("Das Spiel ist zu Ende, da nicht genug Spieler beigetreten sind!"); + await this.endGame("Das Spiel ist zu Ende, da nicht genug Spieler beigetreten sind!"); this.gameManager.games.delete(this.code); return; } @@ -108,16 +123,15 @@ class Game{ } /** @param {SocketUser} user */ - leaveUser(user){ + async leaveUser(user){ this.players.forEach((player, index) => { if(player.id === user.id){ this.players.splice(index, 1); - return; } }); if(this.players.length != 2){ - this.endGame("Das Spiel ist zu Ende, da ein Spieler verlassen hat!"); + if(this.gameStarted) await this.endGame("Das Spiel ist zu Ende, da ein Spieler verlassen hat!"); return 1; } } diff --git a/backend/src/SocketIO/GameManager/Game/GameLoop.js b/backend/src/SocketIO/GameManager/Game/GameLoop.js index 774fa37..fe154c5 100644 --- a/backend/src/SocketIO/GameManager/Game/GameLoop.js +++ b/backend/src/SocketIO/GameManager/Game/GameLoop.js @@ -18,17 +18,19 @@ class GameLoop{ this.fruitManager = new FruitManager(this.io, this.game, this.playground); } - loop(){ + async loop(){ this.playground.resetPlayground(); - this.snakes.forEach(snake => { snake.move()}); + await Promise.all(this.snakes.map(snake => snake.move())); this.fruitManager.updateFruits(); this.sendUpdate(); - setTimeout(() => { - this.loop(); - }, 125); + if(this.game.gameStarted){ + setTimeout(() => { + this.loop(); + }, 125); + } } sendUpdate(){ diff --git a/backend/src/SocketIO/GameManager/GameManager.js b/backend/src/SocketIO/GameManager/GameManager.js index fde735b..629a8e2 100644 --- a/backend/src/SocketIO/GameManager/GameManager.js +++ b/backend/src/SocketIO/GameManager/GameManager.js @@ -2,12 +2,14 @@ const socketIO = require("socket.io"); const SocketUser = require("../Classes/SocketUser"); const TemporaryLobby = require("../LobbyManager/Classes/TemporaryLobby"); const LobbyManager = require("../LobbyManager/LobbyManager"); +const DataBaseManager = require("../../Database/DataBaseManager"); const Game = require("./Game/Game"); class GameManager { - /** @param {socketIO.Server} io @param {LobbyManager} lobbyManager */ - constructor(io, lobbyManager) { + /** @param {socketIO.Server} io @param {DataBaseManager} db @param {LobbyManager} lobbyManager */ + constructor(io, db, lobbyManager) { this.io = io; + this.db = db; this.lobbyManager = lobbyManager; /** @type {Map}*/ @@ -52,11 +54,11 @@ class GameManager { return oldLobbySave.gameCode; } - leaveGame(user, code){ + async leaveGame(user, code){ const game = this.games.get(code); if(!game) return 1; - const response = game.leaveUser(user); + const response = await game.leaveUser(user); if(response === 1) this.games.delete(code); } diff --git a/backend/src/SocketIO/SocketIOManager.js b/backend/src/SocketIO/SocketIOManager.js index 03f8268..301d9a7 100644 --- a/backend/src/SocketIO/SocketIOManager.js +++ b/backend/src/SocketIO/SocketIOManager.js @@ -31,7 +31,7 @@ class SocketIOManager { Der LobbyManager kümmert sich um alle Lobbys */ this.lobbyManager = new LobbyManager(this.io); - this.gameManager = new GameManager(this.io, this.lobbyManager); + this.gameManager = new GameManager(this.io, this.db, this.lobbyManager); this.io.on("connection", (socket) => { this.sendToRightManager(socket) }); diff --git a/doublesnake.sql b/doublesnake.sql index 6b7f280..5358120 100644 --- a/doublesnake.sql +++ b/doublesnake.sql @@ -47,6 +47,26 @@ INSERT INTO `users` (`id`, `username`, `email`, `password`, `fullName`, `created (2, 'test2', 'test2@gmail.com', '$2b$10$fRrqNkFVA4TIyy61ovgPae83drW6oZOCND94BErwthKmt1MxzgTci', 'Test Person 2', '2025-03-22 19:48:45'), (3, 'test3', NULL, '$2b$10$5QEkQuI/HxnaNovp8XPbnOnkuPpfSLdjwyqXMXKCB5oHZQR3ILTkq', NULL, '2025-03-22 19:48:51'); +-- -------------------------------------------------------- +-- +-- Tabellenstruktur für Tabelle `scores` +-- + +CREATE TABLE `scores` ( + `id` int(11) NOT NULL, + `user1` int(11) NOT NULL, + `user2` int(11) NOT NULL, + `score` int(11) NOT NULL, + `time` timestamp NOT NULL DEFAULT current_timestamp() +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +-- +-- Daten für Tabelle `scores` +-- + +INSERT INTO `scores` (`id`, `user1`, `user2`, `score`, `time`) VALUES +(1, 1, 2, 100, '2025-03-22 20:00:00'), +(2, 2, 3, 150, '2025-03-22 20:15:00'), +(3, 1, 3, 200, '2025-03-22 20:30:00'); -- -- Indizes der exportierten Tabellen -- @@ -58,6 +78,14 @@ ALTER TABLE `users` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `username` (`username`); +-- +-- Indizes für die Tabelle `scores` +-- +ALTER TABLE `scores` + ADD PRIMARY KEY (`id`), + ADD KEY `user1` (`user1`), + ADD KEY `user2` (`user2`); + -- -- AUTO_INCREMENT für exportierte Tabellen -- @@ -67,6 +95,19 @@ ALTER TABLE `users` -- ALTER TABLE `users` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; + +-- +-- AUTO_INCREMENT für Tabelle `scores` +-- +ALTER TABLE `scores` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; + +-- +-- Constraints der Tabelle `scores` +-- +ALTER TABLE `scores` + ADD CONSTRAINT `scores_ibfk_1` FOREIGN KEY (`user1`) REFERENCES `users` (`id`), + ADD CONSTRAINT `scores_ibfk_2` FOREIGN KEY (`user2`) REFERENCES `users` (`id`); COMMIT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/frontend/game/scripts/Game/UI/UIManager.js b/frontend/game/scripts/Game/UI/UIManager.js index be6317e..515065b 100644 --- a/frontend/game/scripts/Game/UI/UIManager.js +++ b/frontend/game/scripts/Game/UI/UIManager.js @@ -107,6 +107,10 @@ class UIManager{ const score = document.createElement("h2"); score.innerText = data.score; scoreDiv.appendChild(score); + const rang = document.createElement("h2"); + rang.innerText = `Bestenliste Platz: ${data.rang}`; + rang.style.color = "rgb(255, 223, 0)"; + scoreDiv.appendChild(rang); endDiv.appendChild(scoreDiv); const dashButton = document.createElement("button"); diff --git a/frontend/game/style/mainStyle.css b/frontend/game/style/mainStyle.css index b370d72..4b6e3cf 100644 --- a/frontend/game/style/mainStyle.css +++ b/frontend/game/style/mainStyle.css @@ -86,7 +86,7 @@ img{ flex-direction: column; align-items: center; justify-content: space-evenly; - height: 150px; + height: 200px; } #scoreEndDiv h3{