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)
This commit is contained in:
MaciejkaG 2024-03-07 21:56:44 +01:00
parent 8f3538417a
commit 6021305f4a
6 changed files with 158 additions and 41 deletions

View File

@ -29,7 +29,7 @@ const io = new Server(server);
const redis = createClient(); const redis = createClient();
redis.on('error', err => console.log('Redis Client Error', err)); redis.on('error', err => console.log('Redis Client Error', err));
await redis.connect(); await redis.connect();
redis.flushDb(); // redis.flushDb();
const GInfo = new bships.GameInfo(redis, io); 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); let UTCTs = Math.floor((new Date()).getTime() / 1000 + 90);
io.to(playerGame.id).emit('turn update', { turn: 0, phase: "preparation", timerToUTC: UTCTs }); 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); GInfo.endPrepPhase(socket);
bships.timer(30, () => { bships.timer(30, () => {
AFKEnd(playerGame.id); AFKEnd(playerGame.id);
@ -250,7 +259,7 @@ io.on('connection', async (socket) => {
} else if (!shipAvailable) { } else if (!shipAvailable) {
socket.emit("toast", "Nie masz już statków tego typu"); socket.emit("toast", "Nie masz już statków tego typu");
} else { } 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 }); 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); const playerGame = await GInfo.getPlayerGameData(socket);
if (playerGame.data.state === 'action') { 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);
});
} }
} }
}); });

View File

@ -1,18 +1,23 @@
:root { :root {
font-size: 20px; font-size: 20px;
--field: rgb(36, 36, 36); --field: rgb(36, 36, 36);
--mark-line: rgb(59, 59, 59); --mark-line: rgb(59, 59, 59);
--mark-spot: rgb(90, 90, 90); --mark-spot: rgb(90, 90, 90);
--mark-ship-valid: hsl(120, 100%, 80%); --mark-ship-valid: hsl(120, 100%, 80%);
--mark-ship-invalid: hsl(0, 100%, 80%); --mark-ship-invalid: hsl(0, 100%, 80%);
--mark-hit: #ffffff;
--ship-valid: hsl(120, 70%, 55%); --ship-valid: hsl(120, 70%, 55%);
--ship-invalid: hsl(0, 70%, 55%); --ship-invalid: hsl(0, 70%, 55%);
--ship-miss: hsl(0, 0%, 18%);
--dynamic: rgb(83, 83, 245); --dynamic: rgb(83, 83, 245);
--danger: rgb(243, 56, 56); --danger: rgb(243, 56, 56);
--important: rgb(203, 50, 241); --important: rgb(203, 50, 241);
color: rgb(136, 136, 136) color: rgb(136, 136, 136)
} }
@ -21,7 +26,7 @@
} }
body { body {
background: black; background-color: black;
color: white; color: white;
font-family: 'Lato', sans-serif; font-family: 'Lato', sans-serif;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
@ -67,14 +72,14 @@ h1,h2,h3,h4,h5,h6 {
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 20%; border-radius: 20%;
cursor: pointer; cursor: pointer;
transition: background 0.1s; transition: background-color 0.1s;
} }
.field .shipField { .field .shipField {
width: 100%; width: 100%;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 20%; border-radius: 20%;
background: var(--ship-valid); background-color: var(--ship-valid);
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;
transform: scale(0); transform: scale(0);
@ -82,7 +87,7 @@ h1,h2,h3,h4,h5,h6 {
} }
#secondaryBoard .field .shipField { #secondaryBoard .field .shipField {
background: var(--ship-invalid); background-color: var(--ship-invalid);
} }
.field.active .shipField { .field.active .shipField {
@ -208,9 +213,9 @@ h1,h2,h3,h4,h5,h6 {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
text-align: center; text-align: center;
background: rgb(0, 0, 0, 0.6); background-color: rgb(0, 0, 0, 0.6);
backdrop-filter: blur(20px); backdrop-filter: blur(20px);
transition: opacity 0.5s; transition: opacity 0.5s, transform 0.5s;
z-index: 999; z-index: 999;
} }
@ -219,9 +224,9 @@ h1,h2,h3,h4,h5,h6 {
} }
@keyframes timerDanger { @keyframes timerDanger {
0% { color: var(--important) } 0% { color: var(--important); transform: scale(1); }
50% { color: var(--danger) } 50% { color: var(--danger); transform: scale(1.1); }
100% { color: var(--important) } 100% { color: var(--important); transform: scale(1); }
} }
#timer.active { #timer.active {

View File

@ -12,7 +12,7 @@ class Battleships {
for (var i = 0; i < size; i++) { for (var i = 0; i < size; i++) {
let row = "<div class=\"row\">"; let row = "<div class=\"row\">";
for (var n = 0; n < size; n++) { for (var n = 0; n < size; n++) {
row += `<div class="field" data-pos-x="${n}" data-pos-y="${i}"><div class="shipField"><svg xmlns='http://www.w3.org/2000/svg' version='1.1' preserveAspectRatio='none' viewBox='0 0 100 100'><path d='M100 0 L0 100 ' stroke='#f33838' stroke-width='10'/><path d='M0 0 L100 100 ' stroke='#f33838' stroke-width='10'/></svg></div></div>`; row += `<div class="field" data-pos-x="${n}" data-pos-y="${i}"><div class="shipField"><svg xmlns='http://www.w3.org/2000/svg' version='1.1' preserveAspectRatio='none' viewBox='0 0 100 100'><path d='M100 0 L0 100 ' stroke='#ffffff' stroke-width='10'/><path d='M0 0 L100 100 ' stroke='#ffffff' stroke-width='10'/></svg></div></div>`;
} }
row += "</div>"; row += "</div>";
board += 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) { getRow(row) {
row++; row++;
if (row<=this.boardSize) { 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("<path d='M100 0 L0 100 ' stroke='#ffffff' stroke-width='10'/><path d='M0 0 L100 100 ' stroke='#ffffff' stroke-width='10'/>");
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("<circle fill='#ffffff' cx='50' cy='50' r='20' />");
}
this.getFieldSecondary(x, y).addClass("hit");
}
setFieldEnemy(x, y, state, primary = false) {
if (state === "hit") {
this.getFieldSecondary(x, y).children().children("svg").html("<path d='M100 0 L0 100 ' stroke='#ffffff' stroke-width='10'/><path d='M0 0 L100 100 ' stroke='#ffffff' stroke-width='10'/>");
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("<circle fill='#ffffff' cx='50' cy='50' r='20' />");
}
this.getFieldSecondary(x, y).addClass("hit");
}
placeShip(data) { placeShip(data) {
let fields = []; let fields = [];
switch (data.rot) { switch (data.rot) {

View File

@ -30,20 +30,20 @@ $(".board .field").hover(function () {
changedFields.push(row, column, $(this)); changedFields.push(row, column, $(this));
row.css("background", "var(--mark-line)"); row.css("background-color", "var(--mark-line)");
column.css("background", "var(--mark-line)"); column.css("background-color", "var(--mark-line)");
previousRow = row; previousRow = row;
previousColumn = column; previousColumn = column;
if (postPrep) { if (postPrep) {
if (myTurn) { if (myTurn) {
$(this).css("background", "var(--mark-ship-invalid)"); $(this).css("background-color", "var(--mark-ship-invalid)");
} else { } else {
(this).css("background", "var(--mark-spot)"); $(this).css("background-color", "var(--mark-spot)");
} }
} else { } else {
$(this).css("background", "var(--mark-spot)"); $(this).css("background-color", "var(--mark-spot)");
// Pokaż podgląd statku // Pokaż podgląd statku
@ -87,9 +87,9 @@ $(".board .field").hover(function () {
} }
if (failed) { if (failed) {
fieldElem.css("background", "var(--mark-ship-invalid)"); fieldElem.css("background-color", "var(--mark-ship-invalid)");
} else { } else {
fieldElem.css("background", "var(--mark-ship-valid)"); fieldElem.css("background-color", "var(--mark-ship-valid)");
} }
changedFields.push(fieldElem); changedFields.push(fieldElem);
} }
@ -98,7 +98,7 @@ $(".board .field").hover(function () {
hoveredField = null; hoveredField = null;
// Wyłącz "miarki" po wyjściu kursora z pola (aby się nie duplikowały w przyszłości) // Wyłącz "miarki" po wyjściu kursora z pola (aby się nie duplikowały w przyszłości)
changedFields.forEach(field => { changedFields.forEach(field => {
field.css("background", "var(--field)"); field.css("background-color", "var(--field)");
}); });
changedFields.length = 0; changedFields.length = 0;
}); });
@ -185,7 +185,7 @@ function refreshBoardView() {
if (hoveredField) { if (hoveredField) {
changedFields.forEach(field => { changedFields.forEach(field => {
field.css("background", "var(--field)"); field.css("background-color", "var(--field)");
}); });
changedFields.length = 0; changedFields.length = 0;
@ -196,10 +196,10 @@ function refreshBoardView() {
changedFields.push(row, column, $(hoveredField)); changedFields.push(row, column, $(hoveredField));
row.css("background", "var(--mark-line)"); row.css("background-color", "var(--mark-line)");
column.css("background", "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; previousRow = row;
previousColumn = column; previousColumn = column;
@ -243,9 +243,9 @@ function refreshBoardView() {
} }
if (failed) { if (failed) {
fieldElem.css("background", "var(--mark-ship-invalid)"); fieldElem.css("background-color", "var(--mark-ship-invalid)");
} else { } else {
fieldElem.css("background", "var(--mark-ship-valid)"); fieldElem.css("background-color", "var(--mark-ship-valid)");
} }
changedFields.push(fieldElem); changedFields.push(fieldElem);
} }

View File

@ -5,10 +5,15 @@ var timerDestination = null;
var gamePhase = 'pregame'; var gamePhase = 'pregame';
var occupiedFields = []; 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); 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 () { $('.field').on('contextmenu', function () {
if ($(this).hasClass('active')) { if ($(this).hasClass('active')) {
let originPos = occupiedFields.find((elem) => elem.pos[0] == $(this).data('pos-x') && elem.pos[1] == $(this).data('pos-y')).origin; 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(); 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', () => { socket.on('connect', () => {
$(".cover h1").html("Oczekiwanie na serwer..."); $(".cover h1").html("Oczekiwanie na serwer...");
}); });

View File

@ -36,12 +36,12 @@ export class GameInfo {
const key = `game:${gameId}`; const key = `game:${gameId}`;
await this.redis.json.set(key, '.state', 'action'); 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; nextPlayer = nextPlayer === 0 ? 1 : 0;
await this.redis.json.set(key, '.nextPlayer', nextPlayer); await this.redis.json.set(key, '.nextPlayer', nextPlayer);
const UTCTs = Math.floor((new Date()).getTime() / 1000 + 30); 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) { async placeShip(socket, shipData) {
@ -73,6 +73,37 @@ export class GameInfo {
await this.redis.json.set(key, `.boards[${playerIdx}].ships`, playerShips); await this.redis.json.set(key, `.boards[${playerIdx}].ships`, playerShips);
return deletedShip; 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) { export function isPlayerInRoom(socket) {
@ -135,11 +166,7 @@ export function getShipsAvailable(ships) {
return shipsLeft; return shipsLeft;
} }
export function checkHit(data, playerIdx, posX, posY) { export function checkHit(ships, posX, posY) {
playerIdx = playerIdx === 0 ? 1 : 0;
let enemyBoard = data.boards[playerIdx];
let boardRender = []; let boardRender = [];
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
@ -150,7 +177,7 @@ export function checkHit(data, playerIdx, posX, posY) {
boardRender.push(array); boardRender.push(array);
} }
enemyBoard.ships.forEach(ship => { ships.forEach(ship => {
let multips; let multips;
switch (ship.rot) { switch (ship.rot) {
@ -172,7 +199,8 @@ export function checkHit(data, playerIdx, posX, posY) {
} }
for (let i = 0; i < ship.type + 2; i++) { 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) { export function validateShipPosition(ships, type, posX, posY, rot) {
if (type < 0 || type > 3 || rot < 0 || rot > 3) {
return false;
}
let boardRender = []; let boardRender = [];
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {