mirror of
https://github.com/MaciejkaG/statki.git
synced 2024-11-30 01:32:55 +01:00
Major changes
- Improved Smart AI - Minor design improvements
This commit is contained in:
parent
fd28365c98
commit
5c937cb9b8
11
index.js
11
index.js
@ -29,7 +29,6 @@ fs.readFile(path.join(__dirname, 'package.json'), function (err, data) {
|
|||||||
packageJSON = JSON.parse(data);
|
packageJSON = JSON.parse(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
const flags = process.env.flags ? process.env.flags.split(",") : null;
|
const flags = process.env.flags ? process.env.flags.split(",") : null;
|
||||||
@ -1122,7 +1121,7 @@ io.on('connection', async (socket) => {
|
|||||||
|
|
||||||
await GInfo.passTurn(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);
|
hit = await GInfo.shootShip(socket, 0, posX, posY);
|
||||||
|
|
||||||
@ -1236,6 +1235,12 @@ async function finishPrepPhase(socket, playerGame) {
|
|||||||
const socket = io.sockets.sockets.get(sid);
|
const socket = io.sockets.sockets.get(sid);
|
||||||
|
|
||||||
let placedShips = await GInfo.depleteShips(socket);
|
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 => {
|
placedShips.forEach(shipData => {
|
||||||
socket.emit("placed ship", shipData)
|
socket.emit("placed ship", shipData)
|
||||||
});
|
});
|
||||||
@ -1249,6 +1254,8 @@ async function finishPrepPhase(socket, playerGame) {
|
|||||||
GInfo.timer(playerGame.id, 30, () => {
|
GInfo.timer(playerGame.id, 30, () => {
|
||||||
AFKEnd(playerGame.id);
|
AFKEnd(playerGame.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function placeAIShips(socket, playerGame) {
|
async function placeAIShips(socket, playerGame) {
|
||||||
|
22
package-lock.json
generated
22
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "statki-backend",
|
"name": "statki",
|
||||||
"version": "1.0.0",
|
"version": "0.7.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "statki-backend",
|
"name": "statki",
|
||||||
"version": "1.0.0",
|
"version": "0.7.5",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"connect-redis": "^7.1.1",
|
"connect-redis": "^7.1.1",
|
||||||
@ -550,16 +550,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.18.3",
|
"version": "4.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
||||||
"integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==",
|
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"content-type": "~1.0.4",
|
"content-type": "~1.0.4",
|
||||||
"cookie": "0.5.0",
|
"cookie": "0.6.0",
|
||||||
"cookie-signature": "1.0.6",
|
"cookie-signature": "1.0.6",
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
@ -662,9 +662,9 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
"node_modules/express/node_modules/cookie": {
|
"node_modules/express/node_modules/cookie": {
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "statki-backend",
|
"name": "statki",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"description": "Backend do gry w statki",
|
"description": "Backend do gry w statki",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/MaciejkaG/statki-backend.git"
|
"url": "git+https://github.com/MaciejkaG/statki.git"
|
||||||
},
|
},
|
||||||
"author": "Maciejka",
|
"author": "Maciejka",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"background_color": "black",
|
"background_color": "black",
|
||||||
"theme_color": "black",
|
"theme_color": "black",
|
||||||
"orientation": "any",
|
"orientation": "any",
|
||||||
|
"display": "minimal-ui",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/assets/img/statki-logo-crop.png",
|
"src": "/assets/img/statki-logo-crop.png",
|
||||||
@ -72,6 +73,5 @@
|
|||||||
"form_factor": "narrow",
|
"form_factor": "narrow",
|
||||||
"label": "Create game screen"
|
"label": "Create game screen"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"display": "standalone"
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
const statki = "statki-by-maciejka-0.7.4";
|
const statki = "statki-by-maciejka-0.7.5";
|
||||||
const assets = [
|
const assets = [
|
||||||
"/favicon.ico",
|
"/favicon.ico",
|
||||||
"/assets/css/landing.css",
|
"/assets/css/landing.css",
|
||||||
|
@ -196,9 +196,10 @@ joinForm.addEventListener('submit', (e) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const pveForm = document.getElementById('pveCreateForm');
|
const pveForm = document.getElementById('pveCreateForm');
|
||||||
const pveDifficulty = document.getElementById('pveDifficulty').value;
|
const pveDifficultyElem = document.getElementById('pveDifficulty');
|
||||||
|
|
||||||
pveForm.addEventListener('submit', (e) => {
|
pveForm.addEventListener('submit', (e) => {
|
||||||
|
const pveDifficulty = pveDifficultyElem.value;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (pveDifficulty) {
|
if (pveDifficulty) {
|
||||||
lockUI(true);
|
lockUI(true);
|
||||||
|
@ -183,6 +183,10 @@ export class GameInfo {
|
|||||||
|
|
||||||
const rPos = search[Math.floor(Math.random() * search.length)];
|
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 });
|
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) });
|
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;
|
let multips;
|
||||||
@ -287,15 +291,87 @@ export class GameInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async makeAIMove(socket, difficulty) {
|
async makeAIMove(socket, difficulty) {
|
||||||
difficulty = 0;
|
if (difficulty === 2) {
|
||||||
|
difficulty = 1;
|
||||||
|
}
|
||||||
const gameId = socket.session.activeGame;
|
const gameId = socket.session.activeGame;
|
||||||
const key = `game:${gameId}`;
|
const key = `game:${gameId}`;
|
||||||
|
|
||||||
const boards = await this.redis.json.get(key, { path: `.boards` });
|
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
|
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
|
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
|
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
|
[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);
|
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 == null then the field with coordinates posX and posY was not shot at yet
|
||||||
|
|
||||||
if (!shot) {
|
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;
|
foundAppropriateTarget = true;
|
||||||
} else { // If difficulty mode is set to simple, just accept that field
|
} else { // If difficulty mode is set to simple, just accept that field
|
||||||
foundAppropriateTarget = true;
|
foundAppropriateTarget = true;
|
||||||
@ -566,6 +641,18 @@ function findEmptyFields(grid, len) { // Find all empty fields in the board
|
|||||||
return shipPlacements;
|
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) {
|
function clamp(n, min, max) {
|
||||||
return Math.min(Math.max(n, min), max);
|
return Math.min(Math.max(n, min), max);
|
||||||
}
|
}
|
@ -20,8 +20,14 @@
|
|||||||
<script src="https://unpkg.com/tippy.js@6"></script>
|
<script src="https://unpkg.com/tippy.js@6"></script>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6/animations/shift-toward-subtle.css" />
|
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6/animations/shift-toward-subtle.css" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6/themes/translucent.css" />
|
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6/themes/translucent.css" />
|
||||||
|
|
||||||
<link rel="manifest" href="/app/manifest.json" />
|
<link rel="manifest" href="/app/manifest.json" />
|
||||||
|
|
||||||
<meta name="theme-color" content="#000000"/>
|
<meta name="theme-color" content="#000000"/>
|
||||||
|
|
||||||
|
<meta name="description" content="The #1 online multiplayer battleships game">
|
||||||
|
<meta name="keywords" content="battleships, statki, online, multiplayer">
|
||||||
|
<meta name="author" content="Maciejka">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<span class="designerTag">Designed by Maciejka</span>
|
<span class="designerTag">Designed by Maciejka</span>
|
||||||
|
Loading…
Reference in New Issue
Block a user