From 6021305f4a7aaa9d8fcf0c1cc6a6841aace124e4 Mon Sep 17 00:00:00 2001 From: MaciejkaG Date: Thu, 7 Mar 2024 21:56:44 +0100 Subject: [PATCH] Major changes - Hit registration works perfectly now - Multiple color tweaks - Enhanced timer animation To-do: - Detecting game end - Multiple fixes - Revamp round timers (they are global now, so resetting a timer resets it for all games) --- index.js | 33 +++++++++++++++---- public/assets/css/board.css | 25 +++++++++------ public/assets/js/battleships-lib.js | 38 +++++++++++++++++++++- public/assets/js/main.js | 28 ++++++++-------- public/assets/js/socket-game.js | 25 ++++++++++++++- utils/battleships.js | 50 +++++++++++++++++++++++------ 6 files changed, 158 insertions(+), 41 deletions(-) diff --git a/index.js b/index.js index 082382a..c2380b4 100644 --- a/index.js +++ b/index.js @@ -29,7 +29,7 @@ const io = new Server(server); const redis = createClient(); redis.on('error', err => console.log('Redis Client Error', err)); await redis.connect(); -redis.flushDb(); +// redis.flushDb(); const GInfo = new bships.GameInfo(redis, io); @@ -224,7 +224,16 @@ io.on('connection', async (socket) => { let UTCTs = Math.floor((new Date()).getTime() / 1000 + 90); io.to(playerGame.id).emit('turn update', { turn: 0, phase: "preparation", timerToUTC: UTCTs }); - bships.timer(90, () => { + bships.timer(20, async () => { + const playerGame = await GInfo.getPlayerGameData(socket); + for (let i = 0; i < playerGame.data.boards.length; i++) { + const ships = playerGame.data.boards[i].ships; + if (!ships.length) { + AFKEnd(playerGame.id); + return; + } + } + GInfo.endPrepPhase(socket); bships.timer(30, () => { AFKEnd(playerGame.id); @@ -250,7 +259,7 @@ io.on('connection', async (socket) => { } else if (!shipAvailable) { socket.emit("toast", "Nie masz już statków tego typu"); } else { - await GInfo.placeShip(socket, { type: type, posX: posX, posY: posY, rot: rot }); + await GInfo.placeShip(socket, { type: type, posX: posX, posY: posY, rot: rot, hits: Array.from(new Array(type+1), () => false) }); socket.emit("placed ship", { type: type, posX: posX, posY: posY, rot: rot }); } } @@ -265,12 +274,24 @@ io.on('connection', async (socket) => { } }); - socket.on('shoot', async () => { + socket.on('shoot', async (posX, posY) => { const playerGame = await GInfo.getPlayerGameData(socket); if (playerGame.data.state === 'action') { - if (bships.checkTurn(playerGame, socket.request.session.id)) { - + if (bships.checkTurn(playerGame.data, socket.request.session.id)) { + const enemyIdx = socket.request.session.id === playerGame.data.hostId ? 1 : 0; + + if (await GInfo.shootShip(socket, posX, posY)) { + io.to(playerGame.id).emit("shot hit", enemyIdx, posX, posY); + } else { + io.to(playerGame.id).emit("shot missed", enemyIdx, posX, posY); + } + + await GInfo.passTurn(socket); + bships.resetTimers(); + bships.timer(30, () => { + AFKEnd(playerGame.id); + }); } } }); diff --git a/public/assets/css/board.css b/public/assets/css/board.css index ca1ef32..535027a 100644 --- a/public/assets/css/board.css +++ b/public/assets/css/board.css @@ -1,18 +1,23 @@ :root { font-size: 20px; - --field: rgb(36, 36, 36); --mark-line: rgb(59, 59, 59); --mark-spot: rgb(90, 90, 90); + --mark-ship-valid: hsl(120, 100%, 80%); --mark-ship-invalid: hsl(0, 100%, 80%); + + --mark-hit: #ffffff; + --ship-valid: hsl(120, 70%, 55%); --ship-invalid: hsl(0, 70%, 55%); + --ship-miss: hsl(0, 0%, 18%); --dynamic: rgb(83, 83, 245); --danger: rgb(243, 56, 56); --important: rgb(203, 50, 241); + color: rgb(136, 136, 136) } @@ -21,7 +26,7 @@ } body { - background: black; + background-color: black; color: white; font-family: 'Lato', sans-serif; transition: opacity 0.3s ease; @@ -67,14 +72,14 @@ h1,h2,h3,h4,h5,h6 { aspect-ratio: 1; border-radius: 20%; cursor: pointer; - transition: background 0.1s; + transition: background-color 0.1s; } .field .shipField { width: 100%; aspect-ratio: 1; border-radius: 20%; - background: var(--ship-valid); + background-color: var(--ship-valid); pointer-events: none; opacity: 0; transform: scale(0); @@ -82,7 +87,7 @@ h1,h2,h3,h4,h5,h6 { } #secondaryBoard .field .shipField { - background: var(--ship-invalid); + background-color: var(--ship-invalid); } .field.active .shipField { @@ -208,9 +213,9 @@ h1,h2,h3,h4,h5,h6 { justify-content: center; align-items: center; text-align: center; - background: rgb(0, 0, 0, 0.6); + background-color: rgb(0, 0, 0, 0.6); backdrop-filter: blur(20px); - transition: opacity 0.5s; + transition: opacity 0.5s, transform 0.5s; z-index: 999; } @@ -219,9 +224,9 @@ h1,h2,h3,h4,h5,h6 { } @keyframes timerDanger { - 0% { color: var(--important) } - 50% { color: var(--danger) } - 100% { color: var(--important) } + 0% { color: var(--important); transform: scale(1); } + 50% { color: var(--danger); transform: scale(1.1); } + 100% { color: var(--important); transform: scale(1); } } #timer.active { diff --git a/public/assets/js/battleships-lib.js b/public/assets/js/battleships-lib.js index 84e5b00..3c6ead3 100644 --- a/public/assets/js/battleships-lib.js +++ b/public/assets/js/battleships-lib.js @@ -12,7 +12,7 @@ class Battleships { for (var i = 0; i < size; i++) { let row = "
"; for (var n = 0; n < size; n++) { - row += `
`; + row += `
`; } row += "
"; board += row; @@ -31,6 +31,16 @@ class Battleships { } } + getFieldSecondary(x, y) { + if (0 <= x && x < this.boardSize && 0 <= y && y < this.boardSize) { + x++; + y++; + return $(`#secondaryBoard .row:nth-child(${y}) .field:nth-child(${x})`); + } else { + throw new RangeError("getField position out of range."); + } + } + getRow(row) { row++; if (row<=this.boardSize) { @@ -49,6 +59,32 @@ class Battleships { } } + setField(x, y, state, primary = false) { + if (state==="hit") { + this.getField(x, y).children().children("svg").html(""); + this.getField(x, y).addClass("hit"); + } else if (state==="miss") { + this.getField(x, y).children(".shipField").css("background-color", "var(--ship-miss)"); + this.getField(x, y).addClass("active hit"); + this.getField(x, y).children().children("svg").html(""); + } + + this.getFieldSecondary(x, y).addClass("hit"); + } + + setFieldEnemy(x, y, state, primary = false) { + if (state === "hit") { + this.getFieldSecondary(x, y).children().children("svg").html(""); + this.getFieldSecondary(x, y).addClass("active hit"); + } else if (state === "miss") { + this.getFieldSecondary(x, y).children(".shipField").css("background-color", "var(--ship-miss)"); + this.getFieldSecondary(x, y).addClass("active hit"); + this.getFieldSecondary(x, y).children().children("svg").html(""); + } + + this.getFieldSecondary(x, y).addClass("hit"); + } + placeShip(data) { let fields = []; switch (data.rot) { diff --git a/public/assets/js/main.js b/public/assets/js/main.js index ee883cc..5733f1d 100644 --- a/public/assets/js/main.js +++ b/public/assets/js/main.js @@ -30,20 +30,20 @@ $(".board .field").hover(function () { changedFields.push(row, column, $(this)); - row.css("background", "var(--mark-line)"); - column.css("background", "var(--mark-line)"); + row.css("background-color", "var(--mark-line)"); + column.css("background-color", "var(--mark-line)"); previousRow = row; previousColumn = column; if (postPrep) { if (myTurn) { - $(this).css("background", "var(--mark-ship-invalid)"); + $(this).css("background-color", "var(--mark-ship-invalid)"); } else { - (this).css("background", "var(--mark-spot)"); + $(this).css("background-color", "var(--mark-spot)"); } } else { - $(this).css("background", "var(--mark-spot)"); + $(this).css("background-color", "var(--mark-spot)"); // Pokaż podgląd statku @@ -87,9 +87,9 @@ $(".board .field").hover(function () { } if (failed) { - fieldElem.css("background", "var(--mark-ship-invalid)"); + fieldElem.css("background-color", "var(--mark-ship-invalid)"); } else { - fieldElem.css("background", "var(--mark-ship-valid)"); + fieldElem.css("background-color", "var(--mark-ship-valid)"); } changedFields.push(fieldElem); } @@ -98,7 +98,7 @@ $(".board .field").hover(function () { hoveredField = null; // Wyłącz "miarki" po wyjściu kursora z pola (aby się nie duplikowały w przyszłości) changedFields.forEach(field => { - field.css("background", "var(--field)"); + field.css("background-color", "var(--field)"); }); changedFields.length = 0; }); @@ -185,7 +185,7 @@ function refreshBoardView() { if (hoveredField) { changedFields.forEach(field => { - field.css("background", "var(--field)"); + field.css("background-color", "var(--field)"); }); changedFields.length = 0; @@ -196,10 +196,10 @@ function refreshBoardView() { changedFields.push(row, column, $(hoveredField)); - row.css("background", "var(--mark-line)"); - column.css("background", "var(--mark-line)"); + row.css("background-color", "var(--mark-line)"); + column.css("background-color", "var(--mark-line)"); - $(hoveredField).css("background", "var(--mark-field)"); + $(hoveredField).css("background-color", "var(--mark-field)"); previousRow = row; previousColumn = column; @@ -243,9 +243,9 @@ function refreshBoardView() { } if (failed) { - fieldElem.css("background", "var(--mark-ship-invalid)"); + fieldElem.css("background-color", "var(--mark-ship-invalid)"); } else { - fieldElem.css("background", "var(--mark-ship-valid)"); + fieldElem.css("background-color", "var(--mark-ship-valid)"); } changedFields.push(fieldElem); } diff --git a/public/assets/js/socket-game.js b/public/assets/js/socket-game.js index 68c98b0..3b56b6f 100644 --- a/public/assets/js/socket-game.js +++ b/public/assets/js/socket-game.js @@ -5,10 +5,15 @@ var timerDestination = null; var gamePhase = 'pregame'; var occupiedFields = []; -$('.field').on('click', function () { +$('#board .field').on('click', function () { socket.emit("place ship", selectedShip, $(this).data('pos-x'), $(this).data('pos-y'), shipRotation); }); +$('#secondaryBoard .field').on('click', function () { + socket.emit("shoot", $(this).data('pos-x'), $(this).data('pos-y')); +}); + + $('.field').on('contextmenu', function () { if ($(this).hasClass('active')) { let originPos = occupiedFields.find((elem) => elem.pos[0] == $(this).data('pos-x') && elem.pos[1] == $(this).data('pos-y')).origin; @@ -54,6 +59,24 @@ socket.on("removed ship", (data) => { refreshBoardView(); }); +socket.on("shot hit", (victimIdx, posX, posY) => { + console.log("hit"); + if (victimIdx === playerIdx) { + bsc.setField(posX, posY, "hit"); + } else { + bsc.setFieldEnemy(posX, posY, "hit"); + } +}); + +socket.on("shot missed", (victimIdx, posX, posY) => { + console.log("missed"); + if (victimIdx === playerIdx) { + bsc.setField(posX, posY, "miss"); + } else { + bsc.setFieldEnemy(posX, posY, "miss"); + } +}); + socket.on('connect', () => { $(".cover h1").html("Oczekiwanie na serwer..."); }); diff --git a/utils/battleships.js b/utils/battleships.js index cb8203b..a295b77 100644 --- a/utils/battleships.js +++ b/utils/battleships.js @@ -36,12 +36,12 @@ export class GameInfo { const key = `game:${gameId}`; await this.redis.json.set(key, '.state', 'action'); - let nextPlayer = await this.redis.json.get(key, '.nextPlayer'); + let nextPlayer = await this.redis.json.get(key, { path:'.nextPlayer' }); nextPlayer = nextPlayer === 0 ? 1 : 0; await this.redis.json.set(key, '.nextPlayer', nextPlayer); const UTCTs = Math.floor((new Date()).getTime() / 1000 + 30); - this.io.to(gameId).emit('turn update', { turn: 0, phase: "action", timerToUTC: UTCTs }); + this.io.to(gameId).emit('turn update', { turn: nextPlayer, phase: "action", timerToUTC: UTCTs }); } async placeShip(socket, shipData) { @@ -73,6 +73,37 @@ export class GameInfo { await this.redis.json.set(key, `.boards[${playerIdx}].ships`, playerShips); return deletedShip; } + + async shootShip(socket, posX, posY) { + const gameId = socket.session.activeGame; + const key = `game:${gameId}`; + const hostId = (await this.redis.json.get(key, { path: '.hostId' })); + + const enemyIdx = socket.request.session.id === hostId ? 1 : 0; + const playerIdx = enemyIdx ? 0 : 1; + + let playerShips = await this.redis.json.get(key, { path: `.boards[${enemyIdx}].ships` }); + + var check = checkHit(playerShips, posX, posY); + + if (!check) { + return false; + } + + var shotShip; + for (let i = 0; i < playerShips.length; i++) { + const ship = playerShips[i]; + + if (ship.posX === check.originPosX & ship.posY === check.originPosY) { + shotShip = ship; + playerShips[i].hits[check.fieldIdx] = true; + } + } + + await this.redis.json.set(key, `.boards[${enemyIdx}].ships`, playerShips); + + return true; + } } export function isPlayerInRoom(socket) { @@ -135,11 +166,7 @@ export function getShipsAvailable(ships) { return shipsLeft; } -export function checkHit(data, playerIdx, posX, posY) { - playerIdx = playerIdx === 0 ? 1 : 0; - - let enemyBoard = data.boards[playerIdx]; - +export function checkHit(ships, posX, posY) { let boardRender = []; for (let i = 0; i < 10; i++) { @@ -150,7 +177,7 @@ export function checkHit(data, playerIdx, posX, posY) { boardRender.push(array); } - enemyBoard.ships.forEach(ship => { + ships.forEach(ship => { let multips; switch (ship.rot) { @@ -172,7 +199,8 @@ export function checkHit(data, playerIdx, posX, posY) { } for (let i = 0; i < ship.type + 2; i++) { - boardRender[ship.posX + multips[1] * i][ship.posY + multips[0] * i] = true; + console.log(`boardRender[${ship.posX + multips[1] * i}][${ship.posY + multips[0] * i}]`) + boardRender[ship.posX + multips[1] * i][ship.posY + multips[0] * i] = {fieldIdx: i, originPosX: ship.posX, originPosY: ship.posY}; } }); @@ -180,6 +208,10 @@ export function checkHit(data, playerIdx, posX, posY) { } export function validateShipPosition(ships, type, posX, posY, rot) { + if (type < 0 || type > 3 || rot < 0 || rot > 3) { + return false; + } + let boardRender = []; for (let i = 0; i < 10; i++) {