mirror of
https://github.com/MaciejkaG/statki.git
synced 2024-11-29 23:32:54 +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);
|
||||
});
|
||||
|
||||
|
||||
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) {
|
||||
|
22
package-lock.json
generated
22
package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
@ -20,8 +20,14 @@
|
||||
<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/themes/translucent.css" />
|
||||
|
||||
<link rel="manifest" href="/app/manifest.json" />
|
||||
|
||||
<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>
|
||||
<body>
|
||||
<span class="designerTag">Designed by Maciejka</span>
|
||||
|
Loading…
Reference in New Issue
Block a user