Get the client to a point where it at least renders the board
This commit is contained in:
parent
3be0c40b64
commit
e8e982c3d6
15 changed files with 1364 additions and 366 deletions
|
|
@ -179,203 +179,229 @@ describe('WebSocketHandler', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
it('should notify other players and remove a disconnected player', () => {
|
||||
const gameManager = new GameManager();
|
||||
const webSocketHandler = new WebSocketHandler(gameManager);
|
||||
it('should notify other players and remove a disconnected player', () => {
|
||||
const gameManager = new GameManager();
|
||||
const webSocketHandler = new WebSocketHandler(gameManager);
|
||||
|
||||
// Player 1
|
||||
let mockWsData1: { request: {}; gameId?: string; playerId?: string } = { request: {} };
|
||||
const mockWs1: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs1._messageCallback = callback;
|
||||
if (event === 'close') mockWs1._closeCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
_closeCallback: null,
|
||||
data: mockWsData1,
|
||||
};
|
||||
// Player 1
|
||||
let mockWsData1: { request: {}; gameId?: string; playerId?: string } = {
|
||||
request: {},
|
||||
};
|
||||
const mockWs1: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs1._messageCallback = callback;
|
||||
if (event === 'close') mockWs1._closeCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
_closeCallback: null,
|
||||
data: mockWsData1,
|
||||
};
|
||||
|
||||
// Player 2
|
||||
let mockWsData2: { request: {}; gameId?: string; playerId?: string } = { request: {} };
|
||||
const mockWs2: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs2._messageCallback = callback;
|
||||
if (event === 'close') mockWs2._closeCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
_closeCallback: null,
|
||||
data: mockWsData2,
|
||||
};
|
||||
// Player 2
|
||||
let mockWsData2: { request: {}; gameId?: string; playerId?: string } = {
|
||||
request: {},
|
||||
};
|
||||
const mockWs2: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs2._messageCallback = callback;
|
||||
if (event === 'close') mockWs2._closeCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
_closeCallback: null,
|
||||
data: mockWsData2,
|
||||
};
|
||||
|
||||
const triggerMessageForWs = (ws: any, message: string) => {
|
||||
if (ws._messageCallback) {
|
||||
ws._messageCallback(message);
|
||||
}
|
||||
};
|
||||
const triggerMessageForWs = (ws: any, message: string) => {
|
||||
if (ws._messageCallback) {
|
||||
ws._messageCallback(message);
|
||||
}
|
||||
};
|
||||
|
||||
const triggerCloseForWs = (ws: any) => {
|
||||
if (ws._closeCallback) {
|
||||
ws._closeCallback();
|
||||
}
|
||||
};
|
||||
const triggerCloseForWs = (ws: any) => {
|
||||
if (ws._closeCallback) {
|
||||
ws._closeCallback();
|
||||
}
|
||||
};
|
||||
|
||||
// Player 1 joins, creates game
|
||||
webSocketHandler.handleConnection(mockWs1, mockWs1.data.request);
|
||||
triggerMessageForWs(mockWs1, JSON.stringify({ type: 'join_game', playerId: 'player1' }));
|
||||
mockWs1.data.gameId = mockWsData1.gameId;
|
||||
mockWs1.data.playerId = 'player1';
|
||||
// Player 1 joins, creates game
|
||||
webSocketHandler.handleConnection(mockWs1, mockWs1.data.request);
|
||||
triggerMessageForWs(
|
||||
mockWs1,
|
||||
JSON.stringify({ type: 'join_game', playerId: 'player1' }),
|
||||
);
|
||||
mockWs1.data.gameId = mockWsData1.gameId;
|
||||
mockWs1.data.playerId = 'player1';
|
||||
|
||||
// Player 2 joins same game
|
||||
webSocketHandler.handleConnection(mockWs2, mockWs2.data.request);
|
||||
triggerMessageForWs(mockWs2, JSON.stringify({ type: 'join_game', gameId: mockWsData1.gameId, playerId: 'player2' }));
|
||||
mockWs2.data.gameId = mockWsData1.gameId;
|
||||
mockWs2.data.playerId = 'player2';
|
||||
|
||||
// Player 2 disconnects
|
||||
mockWs1.send.mockClear(); // Clear P1's send history before P2 disconnects
|
||||
triggerCloseForWs(mockWs2);
|
||||
|
||||
// Expect Player 1 to receive player_disconnected message
|
||||
expect(mockWs1.send).toHaveBeenCalledTimes(1);
|
||||
const receivedMessage = JSON.parse(mockWs1.send.mock.calls[0][0]);
|
||||
expect(receivedMessage.type).toBe('player_disconnected');
|
||||
expect(receivedMessage.playerId).toBe('player2');
|
||||
expect(receivedMessage.gameId).toBe(mockWsData1.gameId);
|
||||
|
||||
// Verify connections map is updated (Player 2 removed)
|
||||
// @ts-ignore
|
||||
expect(webSocketHandler.connections.get(mockWsData1.gameId)).toContain(mockWs1);
|
||||
// @ts-ignore
|
||||
expect(webSocketHandler.connections.get(mockWsData1.gameId)).not.toContain(mockWs2);
|
||||
});
|
||||
it('should broadcast game state to other players when a new player joins', () => {
|
||||
const gameManager = new GameManager();
|
||||
const webSocketHandler = new WebSocketHandler(gameManager);
|
||||
|
||||
let mockWsData1: { request: {}; gameId?: string; playerId?: string } = { request: {} };
|
||||
const mockWs1: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs1._messageCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
data: mockWsData1,
|
||||
};
|
||||
|
||||
let mockWsData2: { request: {}; gameId?: string; playerId?: string } = { request: {} };
|
||||
const mockWs2: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs2._messageCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
data: mockWsData2,
|
||||
};
|
||||
|
||||
const triggerMessageForWs = (ws: any, message: string) => {
|
||||
if (ws._messageCallback) {
|
||||
ws._messageCallback(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Player 1 joins and creates a new game
|
||||
webSocketHandler.handleConnection(mockWs1, mockWs1.data.request);
|
||||
const joinGameMessage1 = JSON.stringify({
|
||||
// Player 2 joins same game
|
||||
webSocketHandler.handleConnection(mockWs2, mockWs2.data.request);
|
||||
triggerMessageForWs(
|
||||
mockWs2,
|
||||
JSON.stringify({
|
||||
type: 'join_game',
|
||||
playerId: 'player1',
|
||||
});
|
||||
triggerMessageForWs(mockWs1, joinGameMessage1);
|
||||
const player1GameId = mockWsData1.gameId;
|
||||
|
||||
// Player 2 joins the same game
|
||||
webSocketHandler.handleConnection(mockWs2, mockWs2.data.request);
|
||||
const joinGameMessage2 = JSON.stringify({
|
||||
type: 'join_game',
|
||||
gameId: player1GameId,
|
||||
gameId: mockWsData1.gameId,
|
||||
playerId: 'player2',
|
||||
});
|
||||
triggerMessageForWs(mockWs2, joinGameMessage2);
|
||||
}),
|
||||
);
|
||||
mockWs2.data.gameId = mockWsData1.gameId;
|
||||
mockWs2.data.playerId = 'player2';
|
||||
|
||||
// Check that Player 1 received the game_state update after Player 2 joined
|
||||
// Player 1 should have received two messages: initial join and then game_state after P2 joins
|
||||
expect(mockWs1.send).toHaveBeenCalledTimes(2);
|
||||
const secondCallArgs = mockWs1.send.mock.calls[1][0];
|
||||
const receivedMessage = JSON.parse(secondCallArgs);
|
||||
// Player 2 disconnects
|
||||
mockWs1.send.mockClear(); // Clear P1's send history before P2 disconnects
|
||||
triggerCloseForWs(mockWs2);
|
||||
|
||||
expect(receivedMessage.type).toBe('game_state');
|
||||
expect(receivedMessage.state.players.black).toBe('player1');
|
||||
expect(receivedMessage.state.players.white).toBe('player2');
|
||||
// Expect Player 1 to receive player_disconnected message
|
||||
expect(mockWs1.send).toHaveBeenCalledTimes(1);
|
||||
const receivedMessage = JSON.parse(mockWs1.send.mock.calls[0][0]);
|
||||
expect(receivedMessage.type).toBe('player_disconnected');
|
||||
expect(receivedMessage.playerId).toBe('player2');
|
||||
expect(receivedMessage.gameId).toBe(mockWsData1.gameId);
|
||||
|
||||
// Verify connections map is updated (Player 2 removed)
|
||||
// @ts-ignore
|
||||
expect(webSocketHandler.connections.get(mockWsData1.gameId)).toContain(
|
||||
mockWs1,
|
||||
);
|
||||
// @ts-ignore
|
||||
expect(webSocketHandler.connections.get(mockWsData1.gameId)).not.toContain(
|
||||
mockWs2,
|
||||
);
|
||||
});
|
||||
it('should broadcast game state to other players when a new player joins', () => {
|
||||
const gameManager = new GameManager();
|
||||
const webSocketHandler = new WebSocketHandler(gameManager);
|
||||
|
||||
let mockWsData1: { request: {}; gameId?: string; playerId?: string } = {
|
||||
request: {},
|
||||
};
|
||||
const mockWs1: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs1._messageCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
data: mockWsData1,
|
||||
};
|
||||
|
||||
let mockWsData2: { request: {}; gameId?: string; playerId?: string } = {
|
||||
request: {},
|
||||
};
|
||||
const mockWs2: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs2._messageCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
data: mockWsData2,
|
||||
};
|
||||
|
||||
const triggerMessageForWs = (ws: any, message: string) => {
|
||||
if (ws._messageCallback) {
|
||||
ws._messageCallback(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Player 1 joins and creates a new game
|
||||
webSocketHandler.handleConnection(mockWs1, mockWs1.data.request);
|
||||
const joinGameMessage1 = JSON.stringify({
|
||||
type: 'join_game',
|
||||
playerId: 'player1',
|
||||
});
|
||||
triggerMessageForWs(mockWs1, joinGameMessage1);
|
||||
const player1GameId = mockWsData1.gameId;
|
||||
|
||||
it('should broadcast game state after a successful move', () => {
|
||||
const gameManager = new GameManager();
|
||||
const webSocketHandler = new WebSocketHandler(gameManager);
|
||||
|
||||
let mockWsData1: { request: {}; gameId?: string; playerId?: string } = { request: {} };
|
||||
const mockWs1: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs1._messageCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
data: mockWsData1,
|
||||
};
|
||||
|
||||
let mockWsData2: { request: {}; gameId?: string; playerId?: string } = { request: {} };
|
||||
const mockWs2: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs2._messageCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
data: mockWsData2,
|
||||
};
|
||||
|
||||
const triggerMessageForWs = (ws: any, message: string) => {
|
||||
if (ws._messageCallback) {
|
||||
ws._messageCallback(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Player 1 joins and creates a new game
|
||||
webSocketHandler.handleConnection(mockWs1, mockWs1.data.request);
|
||||
const joinGameMessage1 = JSON.stringify({
|
||||
type: 'join_game',
|
||||
playerId: 'player1',
|
||||
});
|
||||
triggerMessageForWs(mockWs1, joinGameMessage1);
|
||||
const player1GameId = mockWsData1.gameId;
|
||||
mockWs1.data.gameId = player1GameId; // Manually set gameId for mockWs1
|
||||
mockWs1.data.playerId = 'player1'; // Manually set playerId for mockWs1
|
||||
|
||||
// Player 2 joins the same game
|
||||
webSocketHandler.handleConnection(mockWs2, mockWs2.data.request);
|
||||
const joinGameMessage2 = JSON.stringify({
|
||||
type: 'join_game',
|
||||
gameId: player1GameId,
|
||||
playerId: 'player2',
|
||||
});
|
||||
triggerMessageForWs(mockWs2, joinGameMessage2);
|
||||
mockWs2.data.gameId = player1GameId; // Manually set gameId for mockWs2
|
||||
mockWs2.data.playerId = 'player2'; // Manually set playerId for mockWs2
|
||||
|
||||
// Clear previous calls for clean assertion
|
||||
mockWs1.send.mockClear();
|
||||
mockWs2.send.mockClear();
|
||||
|
||||
// Player 1 makes a move
|
||||
const makeMoveMessage = JSON.stringify({
|
||||
type: 'make_move',
|
||||
row: 7,
|
||||
col: 7,
|
||||
});
|
||||
triggerMessageForWs(mockWs1, makeMoveMessage);
|
||||
|
||||
// Expect Player 2 to receive the game state update
|
||||
expect(mockWs2.send).toHaveBeenCalledTimes(1);
|
||||
const receivedMessage = JSON.parse(mockWs2.send.mock.calls[0][0]);
|
||||
expect(receivedMessage.type).toBe('game_state');
|
||||
expect(receivedMessage.state.board[7][7]).toBe('black');
|
||||
// Player 2 joins the same game
|
||||
webSocketHandler.handleConnection(mockWs2, mockWs2.data.request);
|
||||
const joinGameMessage2 = JSON.stringify({
|
||||
type: 'join_game',
|
||||
gameId: player1GameId,
|
||||
playerId: 'player2',
|
||||
});
|
||||
triggerMessageForWs(mockWs2, joinGameMessage2);
|
||||
|
||||
// Check that Player 1 received the game_state update after Player 2 joined
|
||||
// Player 1 should have received two messages: initial join and then game_state after P2 joins
|
||||
expect(mockWs1.send).toHaveBeenCalledTimes(2);
|
||||
const secondCallArgs = mockWs1.send.mock.calls[1][0];
|
||||
const receivedMessage = JSON.parse(secondCallArgs);
|
||||
|
||||
expect(receivedMessage.type).toBe('game_state');
|
||||
expect(receivedMessage.state.players.black).toBe('player1');
|
||||
expect(receivedMessage.state.players.white).toBe('player2');
|
||||
});
|
||||
|
||||
it('should broadcast game state after a successful move', () => {
|
||||
const gameManager = new GameManager();
|
||||
const webSocketHandler = new WebSocketHandler(gameManager);
|
||||
|
||||
let mockWsData1: { request: {}; gameId?: string; playerId?: string } = {
|
||||
request: {},
|
||||
};
|
||||
const mockWs1: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs1._messageCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
data: mockWsData1,
|
||||
};
|
||||
|
||||
let mockWsData2: { request: {}; gameId?: string; playerId?: string } = {
|
||||
request: {},
|
||||
};
|
||||
const mockWs2: any = {
|
||||
send: mock(() => {}),
|
||||
on: mock((event: string, callback: Function) => {
|
||||
if (event === 'message') mockWs2._messageCallback = callback;
|
||||
}),
|
||||
_messageCallback: null,
|
||||
data: mockWsData2,
|
||||
};
|
||||
|
||||
const triggerMessageForWs = (ws: any, message: string) => {
|
||||
if (ws._messageCallback) {
|
||||
ws._messageCallback(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Player 1 joins and creates a new game
|
||||
webSocketHandler.handleConnection(mockWs1, mockWs1.data.request);
|
||||
const joinGameMessage1 = JSON.stringify({
|
||||
type: 'join_game',
|
||||
playerId: 'player1',
|
||||
});
|
||||
triggerMessageForWs(mockWs1, joinGameMessage1);
|
||||
const player1GameId = mockWsData1.gameId;
|
||||
mockWs1.data.gameId = player1GameId; // Manually set gameId for mockWs1
|
||||
mockWs1.data.playerId = 'player1'; // Manually set playerId for mockWs1
|
||||
|
||||
// Player 2 joins the same game
|
||||
webSocketHandler.handleConnection(mockWs2, mockWs2.data.request);
|
||||
const joinGameMessage2 = JSON.stringify({
|
||||
type: 'join_game',
|
||||
gameId: player1GameId,
|
||||
playerId: 'player2',
|
||||
});
|
||||
triggerMessageForWs(mockWs2, joinGameMessage2);
|
||||
mockWs2.data.gameId = player1GameId; // Manually set gameId for mockWs2
|
||||
mockWs2.data.playerId = 'player2'; // Manually set playerId for mockWs2
|
||||
|
||||
// Clear previous calls for clean assertion
|
||||
mockWs1.send.mockClear();
|
||||
mockWs2.send.mockClear();
|
||||
|
||||
// Player 1 makes a move
|
||||
const makeMoveMessage = JSON.stringify({
|
||||
type: 'make_move',
|
||||
row: 7,
|
||||
col: 7,
|
||||
});
|
||||
triggerMessageForWs(mockWs1, makeMoveMessage);
|
||||
|
||||
// Expect Player 2 to receive the game state update
|
||||
expect(mockWs2.send).toHaveBeenCalledTimes(1);
|
||||
const receivedMessage = JSON.parse(mockWs2.send.mock.calls[0][0]);
|
||||
expect(receivedMessage.type).toBe('game_state');
|
||||
expect(receivedMessage.state.board[7][7]).toBe('black');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,22 +23,19 @@ export class WebSocketHandler {
|
|||
|
||||
public handleConnection(ws: any, req: any): void {
|
||||
console.log('WebSocket connected');
|
||||
|
||||
ws.on('message', (message: string) => {
|
||||
this.handleMessage(ws, message);
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
console.log('WebSocket disconnected');
|
||||
this.handleDisconnect(ws);
|
||||
});
|
||||
|
||||
ws.on('error', (error: Error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
private handleMessage(ws: any, message: string): void {
|
||||
public handleError(ws: any, error: Error): void {
|
||||
console.error('WebSocket error:', error);
|
||||
// Optionally send an error message to the client
|
||||
if (ws) {
|
||||
ws.send(
|
||||
JSON.stringify({ type: 'error', error: 'Server-side WebSocket error' }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public handleMessage(ws: any, message: string): void {
|
||||
try {
|
||||
const parsedMessage: WebSocketMessage = JSON.parse(message);
|
||||
console.log('Received message:', parsedMessage);
|
||||
|
|
@ -111,7 +108,8 @@ export class WebSocketHandler {
|
|||
ws.send(gameStateMessage);
|
||||
// Notify other players if any
|
||||
this.connections.get(game.id)?.forEach((playerWs: any) => {
|
||||
if (playerWs !== ws) { // Don't send back to the player who just joined
|
||||
if (playerWs !== ws) {
|
||||
// Don't send back to the player who just joined
|
||||
playerWs.send(gameStateMessage);
|
||||
}
|
||||
});
|
||||
|
|
@ -187,7 +185,7 @@ export class WebSocketHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private handleDisconnect(ws: any): void {
|
||||
public handleDisconnect(ws: any): void {
|
||||
const gameId = ws.data.gameId;
|
||||
const playerId = ws.data.playerId;
|
||||
|
||||
|
|
@ -195,7 +193,10 @@ export class WebSocketHandler {
|
|||
// Remove disconnected player's websocket from connections
|
||||
const connectionsInGame = this.connections.get(gameId);
|
||||
if (connectionsInGame) {
|
||||
this.connections.set(gameId, connectionsInGame.filter((conn: any) => conn !== ws));
|
||||
this.connections.set(
|
||||
gameId,
|
||||
connectionsInGame.filter((conn: any) => conn !== ws),
|
||||
);
|
||||
if (this.connections.get(gameId)?.length === 0) {
|
||||
this.connections.delete(gameId); // Clean up if no players left
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue