Skip to content

Commit 257ad52

Browse files
committed
chore: cleanup generic wsdata schemas
fix: do not allow attack on already attacked square
1 parent 8049593 commit 257ad52

9 files changed

Lines changed: 55 additions & 27 deletions

File tree

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules
2+
build

src/models/player.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { AttackDataPayload } from '@app/payloads/incoming';
2-
import { getCellCoverageForOriginOrientationAndArea } from '@app/utils';
2+
import {
3+
getCellCoverageForOriginOrientationAndArea,
4+
isSameOrigin
5+
} from '@app/utils';
36
import {
47
CellPosition,
58
Orientation,
@@ -136,6 +139,10 @@ export default class Player extends Model<PlayerData> {
136139
return this.attacks.length > 0;
137140
}
138141

142+
hasAttackedLocation(origin: CellPosition): boolean {
143+
return !!this.attacks.find((a) => isSameOrigin(a.attack.origin, origin));
144+
}
145+
139146
getShipPositionData() {
140147
return this.board?.positions;
141148
}

src/payloads/incoming.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ export enum IncomingMsgType {
66
Attack = 'attack'
77
}
88

9+
export type WsPayload = {
10+
type: IncomingMsgType;
11+
data: unknown;
12+
};
13+
914
export type ConnectionRequestPayload = {
1015
username?: string;
1116
gameId?: string;

src/payloads/outgoing.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export enum OutgoingMsgType {
22
AttackResult = 'attack-result',
3+
BadAttack = 'bad-attack',
34
ServerError = 'server-error',
45
BadMessageType = 'bad-message-type',
56
BadPayload = 'invalid-payload',

src/payloads/schemas.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import Joi from 'joi';
22
import { GAME_GRID_SIZE } from '@app/config';
33
import { ShipType, Orientation, CellArea } from '@app/game/types';
44

5+
export const WsPayloadSchema = Joi.object({
6+
type: Joi.string().required(),
7+
data: Joi.object()
8+
});
9+
510
export const ShipSchema = Joi.object({
611
origin: Joi.array()
712
.min(2)
@@ -17,6 +22,13 @@ export const ShipSchema = Joi.object({
1722
.required()
1823
});
1924

25+
export const ConnectionRequestPayloadSchema = Joi.object({
26+
username: Joi.string(),
27+
gameId: Joi.string(),
28+
playerId: Joi.string(),
29+
useAiOpponent: Joi.boolean()
30+
});
31+
2032
export const ShipsLockedSchema = Joi.object({
2133
[ShipType.Battleship]: ShipSchema.required(),
2234
[ShipType.Destroyer]: ShipSchema.required(),

src/sockets/handler.attack.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import PlayerConfiguration, {
1818
import { getPlayerSpecificData, send } from './common';
1919
import * as ml from '@app/ml';
2020
import { AttackPayloadSchema } from '@app/payloads/schemas';
21+
import Player from '@app/models/player';
2122

2223
export type AttackResult = {
2324
origin: CellPosition;
@@ -48,6 +49,7 @@ const attackHandler: MessageHandler<
4849
}
4950
};
5051
} else {
52+
const attack = validatedData.value as AttackDataPayload;
5153
const wsPlayer = players.getPlayerAssociatedWithSocket(ws);
5254

5355
if (!wsPlayer) {
@@ -93,14 +95,22 @@ const attackHandler: MessageHandler<
9395
);
9496
}
9597

98+
if (player.hasAttackedLocation(attack.origin)) {
99+
return {
100+
type: OutgoingMsgType.BadAttack,
101+
data: {
102+
info: `location ${attack.origin.join(',')} has already been attacked`
103+
}
104+
};
105+
}
106+
96107
const opponentShipData = opponent.getShipPositionData();
97108
if (!opponentShipData) {
98109
throw new Error(
99110
`player ${player.getUUID()} opponent (${opponent.getUUID()}) was missing ship position data`
100111
);
101112
}
102113

103-
const attack = validatedData.value as AttackDataPayload;
104114
let attackResult: AttackResult = {
105115
origin: attack.origin,
106116
hit: false,

src/sockets/handler.connection.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Joi from 'joi';
21
import WebSocket from 'ws';
32
import log from '@app/log';
43
import PlayerConfiguration, {
@@ -10,16 +9,10 @@ import {
109
ValidationErrorPayload
1110
} from '@app/payloads/outgoing';
1211
import { ConnectionRequestPayload } from '@app/payloads/incoming';
12+
import { ConnectionRequestPayloadSchema } from '@app/payloads/schemas';
1313
import { MessageHandler } from './common';
1414
import { getPlayerSpecificData, send } from './common';
1515

16-
const ConnectionRequestPayloadSchema = Joi.object({
17-
username: Joi.string(),
18-
gameId: Joi.string(),
19-
playerId: Joi.string(),
20-
useAiOpponent: Joi.boolean()
21-
});
22-
2316
const connectionHandler: MessageHandler<
2417
PlayerConfigurationData | ValidationErrorPayload
2518
> = async (ws: WebSocket, data: unknown) => {

src/sockets/index.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
import { FastifyInstance } from 'fastify';
2-
import Joi from 'joi';
32
import WebSocket from 'ws';
43
import log from '@app/log';
54
import attackHandler from './handler.attack';
65
import connectionHandler from './handler.connection';
76
import shipPositionHandler from './handler.ship-positions';
87
import { OutgoingMsgType } from '@app/payloads/outgoing';
9-
import { IncomingMsgType } from '@app/payloads/incoming';
8+
import { WsPayload, IncomingMsgType } from '@app/payloads/incoming';
9+
import { WsPayloadSchema } from '@app/payloads/schemas';
1010
import { MessageHandler, MessageHandlerResponse } from './common';
1111
import { heartbeat, send } from './common';
1212

13-
type ParsedWsData = {
14-
type: IncomingMsgType;
15-
data: unknown;
16-
};
17-
18-
const WsDataSchema = Joi.object({
19-
type: Joi.string().required(),
20-
data: Joi.object()
21-
});
22-
2313
/**
2414
* Configures a heartbeat for the WSS attached to the given fastify instance.
2515
* @param app {FastifyInstance}
@@ -38,9 +28,9 @@ const MessageHandlers: { [key in IncomingMsgType]: MessageHandler<unknown> } = {
3828
* Find an appropriate handler for the incoming message, and execute it.
3929
* Send a response generated by the handler to the client.
4030
* @param ws {WebSocket}
41-
* @param data {ParsedWsData}
31+
* @param data {WsPayload}
4232
*/
43-
async function _processSocketMessage(ws: WebSocket, data: ParsedWsData) {
33+
async function _processSocketMessage(ws: WebSocket, data: WsPayload) {
4434
const handler = MessageHandlers[data.type];
4535
if (handler) {
4636
log.trace('processing incoming message: %j', data);
@@ -78,7 +68,7 @@ export default async function processSocketMessage(
7868
ws: WebSocket,
7969
data: WebSocket.Data
8070
) {
81-
let parsed: ParsedWsData;
71+
let parsed: WsPayload;
8272

8373
try {
8474
parsed = JSON.parse(data.toString());
@@ -87,7 +77,7 @@ export default async function processSocketMessage(
8777
return;
8878
}
8979

90-
const valid = WsDataSchema.validate(parsed);
80+
const valid = WsPayloadSchema.validate(parsed);
9181

9282
if (valid.error) {
9383
log.warn('client sent an invalid message payload: %j', parsed);
@@ -99,6 +89,6 @@ export default async function processSocketMessage(
9989
}
10090
});
10191
} else {
102-
_processSocketMessage(ws, valid.value as ParsedWsData);
92+
_processSocketMessage(ws, valid.value as WsPayload);
10393
}
10494
}

src/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ export function getCellAreaWidthAndHeight(area: CellArea) {
5656
};
5757
}
5858

59+
/**
60+
* Determine if two vectors/positions are equal
61+
* @param a
62+
* @param b
63+
*/
64+
export function isSameOrigin(a: CellPosition, b: CellPosition) {
65+
return a[0] === b[0] && a[1] === b[1];
66+
}
67+
5968
/**
6069
* This function will return an array containing occupied cell coordinates for
6170
* an input (typically and attack) given area, origin, and orientation.

0 commit comments

Comments
 (0)