diff --git a/index.js b/index.js index ffe824b..4d9940b 100644 --- a/index.js +++ b/index.js @@ -29,7 +29,6 @@ fs.readFile(path.join(__dirname, 'package.json'), function (err, data) { packageJSON = JSON.parse(data); }); - const app = express(); const flags = process.env.flags ? process.env.flags.split(",") : null; @@ -1122,7 +1121,7 @@ io.on('connection', async (socket) => { await GInfo.passTurn(socket); - [posX, posY] = await GInfo.makeAIMove(socket, playerGame.difficulty); + [posX, posY] = await GInfo.makeAIMove(socket, playerGame.data.difficulty); hit = await GInfo.shootShip(socket, 0, posX, posY); @@ -1236,6 +1235,12 @@ async function finishPrepPhase(socket, playerGame) { const socket = io.sockets.sockets.get(sid); let placedShips = await GInfo.depleteShips(socket); + if (!placedShips) { + io.to(playerGame.id).emit('toast', "An error occured while autoplacing player's ships"); + endGame(playerGame.id); + return; + } + placedShips.forEach(shipData => { socket.emit("placed ship", shipData) }); @@ -1249,6 +1254,8 @@ async function finishPrepPhase(socket, playerGame) { GInfo.timer(playerGame.id, 30, () => { AFKEnd(playerGame.id); }); + + return true; } async function placeAIShips(socket, playerGame) { diff --git a/package-lock.json b/package-lock.json index 11628d5..671d4f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "statki-backend", - "version": "1.0.0", + "name": "statki", + "version": "0.7.5", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "statki-backend", - "version": "1.0.0", + "name": "statki", + "version": "0.7.5", "license": "ISC", "dependencies": { "connect-redis": "^7.1.1", @@ -550,16 +550,16 @@ } }, "node_modules/express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -662,9 +662,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } diff --git a/package.json b/package.json index 3a83ff9..6872ce9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "statki-backend", - "version": "0.7.4", + "name": "statki", + "version": "0.7.5", "description": "Backend do gry w statki", "main": "index.js", "type": "module", @@ -10,7 +10,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/MaciejkaG/statki-backend.git" + "url": "git+https://github.com/MaciejkaG/statki.git" }, "author": "Maciejka", "license": "ISC", diff --git a/public/app/manifest.json b/public/app/manifest.json index ce20e63..18f133f 100644 --- a/public/app/manifest.json +++ b/public/app/manifest.json @@ -5,6 +5,7 @@ "background_color": "black", "theme_color": "black", "orientation": "any", + "display": "minimal-ui", "icons": [ { "src": "/assets/img/statki-logo-crop.png", @@ -72,6 +73,5 @@ "form_factor": "narrow", "label": "Create game screen" } - ], - "display": "standalone" + ] } \ No newline at end of file diff --git a/public/assets/js/service-worker.js b/public/assets/js/service-worker.js index 8d787e7..001cb91 100644 --- a/public/assets/js/service-worker.js +++ b/public/assets/js/service-worker.js @@ -1,4 +1,4 @@ -const statki = "statki-by-maciejka-0.7.4"; +const statki = "statki-by-maciejka-0.7.5"; const assets = [ "/favicon.ico", "/assets/css/landing.css", diff --git a/public/assets/js/socket.js b/public/assets/js/socket.js index 91b4fbd..6ba42e7 100644 --- a/public/assets/js/socket.js +++ b/public/assets/js/socket.js @@ -196,9 +196,10 @@ joinForm.addEventListener('submit', (e) => { }); const pveForm = document.getElementById('pveCreateForm'); -const pveDifficulty = document.getElementById('pveDifficulty').value; +const pveDifficultyElem = document.getElementById('pveDifficulty'); pveForm.addEventListener('submit', (e) => { + const pveDifficulty = pveDifficultyElem.value; e.preventDefault(); if (pveDifficulty) { lockUI(true); diff --git a/utils/battleships.js b/utils/battleships.js index 1db694f..f7d38d6 100644 --- a/utils/battleships.js +++ b/utils/battleships.js @@ -183,6 +183,10 @@ export class GameInfo { const rPos = search[Math.floor(Math.random() * search.length)]; + if (rPos == null) { + return false; + } + placedShips.push({ type: i, posX: rPos.posX, posY: rPos.posY, rot: rPos.rot }); await this.redis.json.arrAppend(key, `.boards[${playerIdx}].ships`, { type: i, posX: rPos.posX, posY: rPos.posY, rot: rPos.rot, hits: Array.from(new Array(i + 1), () => false) }); let multips; @@ -287,15 +291,87 @@ export class GameInfo { } async makeAIMove(socket, difficulty) { - difficulty = 0; - + if (difficulty === 2) { + difficulty = 1; + } const gameId = socket.session.activeGame; const key = `game:${gameId}`; const boards = await this.redis.json.get(key, { path: `.boards` }); if (difficulty == 1) { // If difficulty mode is set to smart, check if there are any shot but not sunk ships + // Iterate through player's ships + for (let i = 0; i < boards[0].ships.length; i++) { + const ship = boards[0].ships[i]; + // If the ship has at least one hit field and at least one not hit field + if (ship.hits.includes(false) && ship.hits.includes(true)) { + // Iterate through ships + for (let fieldIdx = 0; fieldIdx < ship.hits.length; fieldIdx++) { + // If the ship we're currently iterating has been hit... + if (ship.hits[fieldIdx]) { + let multips; + switch (ship.rot) { // Set up proper multipliers for each possible rotation + case 0: + multips = [1, 0]; + break; + + case 1: + multips = [0, 1]; + break; + + case 2: + multips = [-1, 0]; + break; + + case 3: + multips = [0, -1]; + break; + } + + // hitFieldX and hitFieldY simply contain the exact coordinates of the hit field of the ship on the board + let hitFieldX = clamp(ship.posX + multips[0] * fieldIdx, 0, 9); + let hitFieldY = clamp(ship.posY + multips[1] * fieldIdx, 0, 9); + + // subtrahents array contains sets of difference factors from the hit field. + // We will use them to target fields around the field that was already hit + // They are similarto the ones used in validateShipPosition(), but shorter + // This is because we do not want to target fields that touch corners with our hit field, but the ones that touch with sides + let subtrahents = [[0, 1], [1, 0], [0, -1], [-1, 0]]; + + // Shuffle them, so they are later iterated in random order + shuffle(subtrahents); + + // Iterate through all subtrahents + for (let j = 0; j < subtrahents.length; j++) { + const subs = subtrahents[j]; + + // Calculate the target field based on the current set of subtrahents, then clamp it so it doesn't exceed board's boundaries + let targetX = clamp(hitFieldX - subs[0], 0, 9); + let targetY = clamp(hitFieldY - subs[1], 0, 9); + + // If the bot has hit two fields of the ship already, lock axises depending on the rotation of the ship + // This makes it so if the bot has hit two out of four fields of a ship that's placed horizontally, it won't shoot above the ship as the ships are always a straight line + if (ship.hits.filter(value => value === true).length >= 2) { + if (!ship.rot % 2) { + targetY = hitFieldY; + } else { + targetX = hitFieldX; + } + } + + let shot = boards[0].shots.find((shot) => shot.posX === targetX && shot.posY === targetY); + // If shot == null then the field with coordinates posX and posY was not shot at yet + + if (!shot) { + // If the field has not been shot yet and it seems possible, try it! + return [targetX, targetY]; + } + } + } + } + } + } } if (difficulty != 2) { // If difficulty mode is not set to Overkill @@ -306,12 +382,11 @@ export class GameInfo { while (!foundAppropriateTarget) { // As long as no appropriate target was found [posX, posY] = [Math.floor(Math.random() * 10), Math.floor(Math.random() * 10)]; // Randomise another set of coordinates - // let check = checkHit(boards[0].ships, posX, posY); let shot = boards[0].shots.find((shot) => shot.posX === posX && shot.posY === posY); // If shot == null then the field with coordinates posX and posY was not shot at yet if (!shot) { - if (difficulty == 1) { // If difficulty mode is set to smart, check if the shot wasn't near any sunk ship + if (difficulty == 1) { // If difficulty mode is set to smart, check if the shot wasn't near any sunk ship (not done yet) foundAppropriateTarget = true; } else { // If difficulty mode is set to simple, just accept that field foundAppropriateTarget = true; @@ -566,6 +641,18 @@ function findEmptyFields(grid, len) { // Find all empty fields in the board return shipPlacements; } +function shuffle(array) { + let currentIndex = array.length; + + while (currentIndex != 0) { + let randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + + [array[currentIndex], array[randomIndex]] = [ + array[randomIndex], array[currentIndex]]; + } +} + function clamp(n, min, max) { return Math.min(Math.max(n, min), max); } \ No newline at end of file diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars index 9edfbc7..b539097 100644 --- a/views/layouts/main.handlebars +++ b/views/layouts/main.handlebars @@ -20,8 +20,14 @@ + + + + + + Designed by Maciejka