Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir : __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
},
};
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}
6 changes: 0 additions & 6 deletions index.js

This file was deleted.

3,528 changes: 1,601 additions & 1,927 deletions package-lock.json

Large diffs are not rendered by default.

21 changes: 15 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@
"version": "1.0.0",
"description": "Template for the WebSocket remote control task",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node ./index.js",
"start:dev": "nodemon ./index.js",
"test": "echo \"Error: no test specified\" && exit 1"
"start": "ts-node ./src/index.ts",
"start:dev": "ts-node-dev --respawn ./src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint \"src/**/*.ts\" --fix",
"build": "tsc"
},
"author": "Andrei Auchynnikau",
"author": "Dzmitry Zakhar",
"keywords": [],
"license": "ISC",
"devDependencies": {
"nodemon": "^2.0.16"
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"nodemon": "^2.0.16",
"prettier": "^2.8.8",
"ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0"
},
"dependencies": {
"ws": "^8.8.0"
Expand Down
22 changes: 22 additions & 0 deletions src/actions/addShips.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AddShipsMessage } from '../types/game';
import { MessageSender } from '../websocket/MessageSender';
import { DB } from '../db/DB';
import { startGame } from './startGame';

const db = DB.getInstance();

export const addShips = (
message: AddShipsMessage,
indexRoom: number,
messageSender: MessageSender,
) => {
const isGameReadyToStart = db.addShipsToPlayer(
message.data.gameId,
message.data.indexPlayer,
message.data.ships,
);

if (isGameReadyToStart) {
startGame(indexRoom, message.data.gameId, messageSender);
}
};
23 changes: 23 additions & 0 deletions src/actions/assUserToRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { MessageSender } from '../websocket/MessageSender';
import { DB } from '../db/DB';
import { sendUpdateRoom } from './updateRoom';
import { createGame } from './createGame';
import { AddUserToRoomMessage } from '../types/game';

const db = DB.getInstance();

export const addUserToRoom = (
message: AddUserToRoomMessage,
messageSender: MessageSender,
) => {
const indexRoom = message.data.indexRoom;

db.addUserToRoom(messageSender.ws.userId, indexRoom);
messageSender.ws.roomId = indexRoom;

if (db.isRoomFull(indexRoom)) {
createGame(indexRoom, messageSender.wss);
}

sendUpdateRoom(messageSender);
};
171 changes: 171 additions & 0 deletions src/actions/attack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { Damage, UsedCells, AttackMessage, Game } from '../types/game';
import { Ship } from '../entities/Ship';
import { DB } from '../db/DB';
import {
isCellPressed,
getCellId,
getCellFromCoordinates,
} from '../utils/utils';
import { MessageSender } from '../websocket/MessageSender';
import { finish } from './finish';

const db = DB.getInstance();

const validateMessageDataForAttack = (message: AttackMessage): void => {
const { x, y, gameId, indexPlayer } = message.data;

if (
typeof gameId !== 'number' ||
typeof indexPlayer !== 'number' ||
typeof x !== 'number' ||
typeof y !== 'number'
) {
throw new Error(
'Not possible to attack, some required parameters are missing',
);
}
};

const addCellsToUsed = (killedShip: Ship, usedCells: UsedCells): void => {
killedShip.getSurroundingCells().forEach((cell) => {
usedCells[getCellId(cell)] = cell;
});
};

const markSurroundingCellsAsMissed = (
indexRoom: number,
indexPlayer: number,
killedShip: Ship,
messageSender: MessageSender,
) => {
killedShip.getSurroundingCells().forEach((cell) => {
if (cell.damageType === Damage.Miss) {
messageSender.broadcastToRoom(indexRoom, {
type: 'attack',
data: {
position: {
x: cell.position.x,
y: cell.position.y,
},
currentPlayer: indexPlayer,
status: Damage.Miss,
},
id: 0,
});
}
});
};

const updateCellsForKilledShip = (
indexRoom: number,
indexPlayer: number,
killedShip: Ship,
messageSender: MessageSender,
) => {
killedShip.shipCells.forEach((cell) => {
messageSender.broadcastToRoom(indexRoom, {
type: 'attack',
data: {
position: {
x: cell.position.x,
y: cell.position.y,
},
currentPlayer: indexPlayer,
status: Damage.Killed,
},
id: 0,
});
});
};

const getEnemyShipDamageStatus = (message: AttackMessage) => {
const { x, y, gameId, indexPlayer } = message.data;
const game = db.findGameBy(gameId);
const currentPlayerIndex = game?.players.findIndex(
(player) => player.indexPlayer === indexPlayer,
);
const indexEnemy = currentPlayerIndex === 0 ? 1 : 0;
const enemyShips = game.players[indexEnemy].ships;
let killedShip;
const damageType = enemyShips.reduce((damageType, ship) => {
const damage = ship.checkDamageFromAttack(x, y);

if (damage === Damage.Killed) {
killedShip = ship;
}

return damageType !== Damage.Miss ? damageType : damage;
}, Damage.Miss);

const isPlayerWin = enemyShips.every((ship) => ship.isShipKilled());
return { damageType, killedShip, isPlayerWin };
};

export const attack = (
message: AttackMessage,
indexRoom: number,
messageSender: MessageSender,
): void => {
validateMessageDataForAttack(message);
const { x, y, gameId, indexPlayer } = message.data;
const game = db.findGameBy(gameId);

// if it's not current player turn don't allow to make an attack
// if (game.currentPlayer !== messageSender.ws.userId) {
// return;
// }

if (typeof indexRoom !== 'number') {
throw new Error('The room is not found');
}

const usedCells = game.players[indexPlayer].usedCells;

// If the cell has already been pressed -> do nothing
if (isCellPressed(getCellFromCoordinates(x, y), usedCells)) {
return;
} else {
const cell = getCellFromCoordinates(x, y);
usedCells[getCellId(cell)] = cell;
}

const { damageType, killedShip, isPlayerWin } =
getEnemyShipDamageStatus(message);

messageSender.broadcastToRoom(indexRoom, {
type: 'attack',
data: {
position: {
x,
y,
},
currentPlayer: message.data.indexPlayer,
status: damageType,
},
id: 0,
});

if (damageType !== Damage.Shot) {
messageSender.sendTurn(indexRoom, game);
if (killedShip && typeof message.data.indexPlayer === 'number') {
updateCellsForKilledShip(
indexRoom,
message.data.indexPlayer,
killedShip,
messageSender,
);
if (isPlayerWin) {
finish(indexRoom, indexPlayer, messageSender);
}
markSurroundingCellsAsMissed(
indexRoom,
message.data.indexPlayer,
killedShip,
messageSender,
);
addCellsToUsed(killedShip, usedCells);
}
} else if (damageType === Damage.Shot) {
messageSender.sendSamePlayerTurn(indexRoom, game);
}
};
26 changes: 26 additions & 0 deletions src/actions/createGame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Message, WebSocketExtended } from '../types/game';
import { DB } from '../db/DB';
import { convertMessageToString } from '../utils/utils';
import { WebSocketServer } from 'ws';

const db = DB.getInstance();

export const createGame = (indexRoom: number, wss: WebSocketServer) => {
const gameId = db.createGame();

wss.clients.forEach((client) => {
const ws = Object.assign({}, client) as WebSocketExtended;
if (ws.roomId === indexRoom) {
ws.gameId = gameId;
const message = {
type: 'create_game',
data: {
idGame: gameId,
idPlayer: ws.userId,
},
id: 0,
} as Message;
client.send(convertMessageToString(message));
}
});
};
20 changes: 20 additions & 0 deletions src/actions/createRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MessageSender } from '../websocket/MessageSender';
import { DB } from '../db/DB';
import { addUserToRoom } from './assUserToRoom';
import { AddUserToRoomMessage } from '../types/game';

const db = DB.getInstance();

export const createRoom = (messageSender: MessageSender): number => {
const indexRoom = db.createRoom();
const addUserToRoomMessage = {
id: 0,
type: 'add_user_to_room',
data: {
indexRoom,
},
} as AddUserToRoomMessage;
addUserToRoom(addUserToRoomMessage, messageSender);

return indexRoom;
};
24 changes: 24 additions & 0 deletions src/actions/finish.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MessageSender } from '../websocket/MessageSender';
import { sendUpdateRoom } from './updateRoom';
import { updateWinners } from './updateWinners';
import { DB } from '../db/DB';

const db = DB.getInstance();

export const finish = (
indexRoom: number,
indexPlayer: number,
messageSender: MessageSender,
) => {
messageSender.broadcastToRoom(indexRoom, {
type: 'finish',
data: {
winPlayer: indexPlayer,
},
id: 0,
});

db.closeRoom(indexRoom);
sendUpdateRoom(messageSender);
updateWinners(indexRoom, indexPlayer, messageSender);
};
Loading