diff --git a/index.js b/index.js index d24dc99..356e32e 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url'; import { v4 as uuidv4 } from 'uuid'; import session from "express-session"; import { engine } from 'express-handlebars'; +import { createClient } from 'redis'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -21,6 +22,11 @@ app.set('views', './views'); const server = createServer(app); const io = new Server(server); +const redis = createClient(); +redis.on('error', err => console.log('Redis Client Error', err)); +await redis.connect(); + +var gameData = []; app.set('trust proxy', 1); const sessionMiddleware = session({ @@ -34,7 +40,7 @@ app.use(express.static(path.join(__dirname, 'public'))); io.engine.use(sessionMiddleware); -app.get("/", (req, res) => { +app.get("/", async (req, res) => { if (req.session.nickname == null) { res.redirect("/setup"); } else { @@ -50,14 +56,26 @@ app.get("/setup", (req, res) => { } }); -app.post('/api/setup-profile', function (req, res) { +app.post('/api/setup-profile', (req, res) => { if (req.session.nickname == null && 4 < req.body.nickname.length && req.body.nickname.length < 16) { req.session.nickname = req.body.nickname; + req.session.playerID = uuidv4(); } res.redirect("/") }); +app.get("/game", async (req, res) => { + const game = await redis.json.get(`game:${req.query.id}`); + if (req.session.nickname == null) { + res.redirect("/setup"); + } else if (req.query.id == null || game == null || game.state == "expired") { + res.status(400).send('badGameId'); + } else { + res.render('board'); + } +}); + app.get("/*", (req, res) => { res.redirect("/?path=" + req.originalUrl); }); @@ -69,59 +87,113 @@ io.on('connection', (socket) => { return; } - socket.on('create lobby', (callback) => { - if (socket.rooms.size === 1) { - let id = genID(); - callback({ - status: "ok", - gameCode: id - }); + console.log(isPlayerInGame(socket)); + if (!isPlayerInGame(socket)) { + socket.on('whats my nick', (callback) => { + callback(session.nickname); + }); - socket.join(id); - } else { - callback({ - status: "alreadyInLobby", - gameCode: socket.rooms[1] - }); - } - }); - - socket.on('join lobby', (msg, callback) => { - if (io.sockets.adapter.rooms.get(msg) == null) { - callback({ - status: "bad_id" - }); - } else { + socket.on('create lobby', (callback) => { if (socket.rooms.size === 1) { - io.to(msg).emit("joined", session.nickname); - let opp = io.sockets.sockets.get(io.sockets.adapter.rooms.get(msg).values().next().value); - let oppNickname = opp.request.session.nickname; - socket.join(msg); + let id = genID(); callback({ status: "ok", - oppNickname: oppNickname, + gameCode: id }); + + socket.join(id); } else { callback({ status: "alreadyInLobby", - gameCode: id, + gameCode: socket.rooms[1] }); } - } - }); + }); - socket.on('leave lobby', (callback) => { - if (socket.rooms.size === 2) { - socket.leave(socket.rooms[1]); - callback({ - status: "ok" - }); - } else { - callback({ - status: "youreNotInLobby" - }); - } - }); + socket.on('join lobby', (msg, callback) => { + if (io.sockets.adapter.rooms.get(msg) == null || io.sockets.adapter.rooms.get(msg).size > 1) { + callback({ + status: "bad_id" + }); + } else { + if (socket.rooms.size === 1) { + io.to(msg).emit("joined", session.nickname); // Wyślij hostowi powiadomienie o dołączającym graczu + // Zmienna opp zawiera socket hosta + let opp = io.sockets.sockets.get(io.sockets.adapter.rooms.get(msg).values().next().value); + let oppNickname = opp.request.session.nickname; + + socket.join(msg); // Dołącz gracza do grupy + callback({ + status: "ok", + oppNickname: oppNickname, + }); // Wyślij dołączonemu graczowi odpowiedź + + // Teraz utwórz objekt partii w trakcie w bazie Redis + const gameId = uuidv4(); + redis.json.set(`game:${gameId}`, '$', { + state: "pregame", + boards: { + host: { // typ 2 to trójmasztowiec pozycja i obrót na planszy które pola zostały trafione + ships: [], // zawiera np. {type: 2, posX: 3, posY: 4, rot: 2, hits: [false, false, true]} + // pozycja na planszy czy strzał miał udział w zatopieniu statku? + shots: [], // zawiera np. {posX: 3, posY: 5, sunk: true} + }, + guest: { + ships: [], + shots: [], + } + }, + nextPlayer: 0, + }); + + io.to(msg).emit("gameReady", gameId); + + io.sockets.clients(msg).forEach((s) => { + s.leave(msg); + }); + } else { + callback({ + status: "alreadyInLobby", + gameCode: id, + }); + } + } + }); + + socket.on('leave lobby', (callback) => { + if (socket.rooms.size === 2) { + socket.leave(socket.rooms[1]); + io.to(socket.rooms[1]).emit("player left"); + callback({ + status: "ok" + }); + } else { + callback({ + status: "youreNotInLobby" + }); + } + }); + + socket.on('disconnecting', () => { + if (isPlayerInRoom(socket)) { + io.to(socket.rooms[1]).emit("player left"); + } + }); + } else { + socket.on('shoot', () => { + const playerGame = getPlayerGameData(socket); + + if (playerGame.state === "action") { + + } + }); + + socket.on('disconnecting', () => { + if (isPlayerInRoom(socket)) { + io.to(socket.rooms).emit("player left"); + } + }); + } }); server.listen(7777, () => { @@ -130,4 +202,37 @@ server.listen(7777, () => { function genID() { return Math.floor(100000 + Math.random() * 900000).toString(); +} + +// async function emitToParty(partyuuid) { +// const party = gameData.find((element) => element.partyId===partyuuid); + +// if (party!==null) { +// party.members.forEach(socketId => { + +// io.to(socketId).emit(); +// }); +// } +// } + +function isPlayerInRoom(socket) { + return !socket.rooms.size === 1; +} + +async function isPlayerInGame(socket) { + const room = getPlayerRoom(socket); + + const game = await redis.json.get(`game:${room}`); + return game != null; +} + +function getPlayerGameData(socket) { + const room = getPlayerRoom(socket); + + const game = redis.json.get(`game:${room}`); + return game; +} + +function getPlayerRoom(socket) { + return socket.rooms.values().next().value; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2c35f6c..946fd4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "express": "^4.18.2", "express-handlebars": "^7.1.2", "express-session": "^1.17.3", + "redis": "^4.6.12", "socket.io": "^4.7.2", "uuid": "^9.0.1", "uuid4": "^2.0.3" @@ -42,6 +43,59 @@ "node": ">=14" } }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.13.tgz", + "integrity": "sha512-epkUM9D0Sdmt93/8Ozk43PNjLi36RZzG+d/T1Gdu5AI8jvghonTeLYV69WVWdilvFo+PYxbP0TZ0saMvr6nscQ==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.6.tgz", + "integrity": "sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", + "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", + "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -185,6 +239,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -543,6 +605,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, "node_modules/get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -961,6 +1031,19 @@ "node": ">= 0.8" } }, + "node_modules/redis": { + "version": "4.6.12", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.12.tgz", + "integrity": "sha512-41Xuuko6P4uH4VPe5nE3BqXHB7a9lkFL0J29AlxKaIfD6eWO8VO/5PDF9ad2oS+mswMsfFxaM5DlE3tnXT+P8Q==", + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.5.13", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.6", + "@redis/search": "1.1.6", + "@redis/time-series": "1.0.5" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1454,6 +1537,11 @@ "optional": true } } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index 5fa506b..b3a426f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "express": "^4.18.2", "express-handlebars": "^7.1.2", "express-session": "^1.17.3", + "redis": "^4.6.12", "socket.io": "^4.7.2", "uuid": "^9.0.1", "uuid4": "^2.0.3" diff --git a/public/assets/css/board.css b/public/assets/css/board.css index 95f1eb6..ca5f01d 100644 --- a/public/assets/css/board.css +++ b/public/assets/css/board.css @@ -16,6 +16,10 @@ color: rgb(136, 136, 136) } +#gameView { + display: flex; +} + body { background: black; color: white; @@ -163,4 +167,20 @@ h1,h2,h3,h4,h5,h6 { #selectedShip { animation: changingIn 1 0.2s ease; +} + +.cover { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + background: rgb(0, 0, 0, 0.4); + backdrop-filter: blur(20px); + transition: opacity 0.5s; + z-index: 999; } \ No newline at end of file diff --git a/public/assets/js/socket-game.js b/public/assets/js/socket-game.js new file mode 100644 index 0000000..6938876 --- /dev/null +++ b/public/assets/js/socket-game.js @@ -0,0 +1,6 @@ +const socket = io(); + +socket.on("players ready", () => { + $(".cover").css({opacity: 0, pointerEvents: "none"}); + socket.emit("shoot"); +}); \ No newline at end of file diff --git a/public/assets/js/socket.js b/public/assets/js/socket.js index e09639c..0baa791 100644 --- a/public/assets/js/socket.js +++ b/public/assets/js/socket.js @@ -7,6 +7,35 @@ socket.on("joined", (nick) => { lockUI(false); }); +socket.on("player left", () => { + lockUI(true); + switchView("mainMenuView"); + lockUI(false); +}); + +socket.on("gameReady", (gameId) => { + setTimeout(() => { + window.location.replace("/game?id=" + gameId); + }, 2000); +}); + +var nickname; + +socket.emit("whats my nick", (myNickname) => { + nickname = myNickname; +}); + +socket.on("game start", (gameInfo) => { + let opp; + if (gameInfo.players[0]!==nickname) { + opp = gameInfo.players[0]; + } else { + opp = gameInfo.players[1]; + } + + alert(`Grasz przeciwko: ${opp}`); +}); + $("#createGameButton").on("click", function () { lockUI(true); socket.emit("create lobby", (response) => { diff --git a/views/board.handlebars b/views/board.handlebars new file mode 100644 index 0000000..ae239b2 --- /dev/null +++ b/views/board.handlebars @@ -0,0 +1,34 @@ +