This commit is contained in:
sepia 2025-07-19 18:34:53 -05:00
parent 1f19022d45
commit 742bd4f106
8 changed files with 49 additions and 62 deletions

View File

@ -27,9 +27,7 @@
<input type="text" id="game-link" size="50" readonly />
<button onclick="copyGameLink()">Copy</button>
</div>
<div id="ws-status" style="margin-top: 10px; color: grey">
Disconnected
</div>
<div id="ws-status" style="margin-top: 10px; color: grey">Disconnected</div>
<script src="scripts/display-ws-connection.js"></script>
<script src="scripts/send-ws-messages.js"></script>
<script src="scripts/copy-game-link.js"></script>

View File

@ -10,4 +10,4 @@ function copyGameLink() {
console.error('Failed to copy link: ', err);
});
}
}
}

View File

@ -22,12 +22,10 @@ htmx.trigger(gameContainer, ' and connect');
// Update the game link input
const gameLinkInput = document.getElementById('game-link');
if (!gameLinkInput) {
console.error('Missing game-link element.')
console.error('Missing game-link element.');
}
gameLinkInput.value =
window.location.origin +
window.location.pathname +
`?gameId=${gameId}`;
window.location.origin + window.location.pathname + `?gameId=${gameId}`;
// Update the WebSocket status indicator
const wsStatusDiv = document.getElementById('ws-status');

View File

@ -1,13 +1,13 @@
document.addEventListener('htmx:wsConfigSend', function(e) {
if (e.target.classList.contains('board-cell')) {
const row = parseInt(e.target.dataset.row);
const col = parseInt(e.target.dataset.col);
// Set the custom JSON data
e.detail.parameters = {
type: "make_move",
row: row,
col: col
};
}
});
document.addEventListener('htmx:wsConfigSend', function (e) {
if (e.target.classList.contains('board-cell')) {
const row = parseInt(e.target.dataset.row);
const col = parseInt(e.target.dataset.col);
// Set the custom JSON data
e.detail.parameters = {
type: 'make_move',
row: row,
col: col,
};
}
});

View File

@ -52,4 +52,4 @@ body {
#player-info {
margin-top: 10px;
font-weight: bold;
}
}

View File

@ -12,7 +12,7 @@ interface MakeMoveMessage {
col: number;
}
type WS = ElysiaWS<{query: {playerId: string, gameId: string}}>;
type WS = ElysiaWS<{ query: { playerId: string; gameId: string } }>;
export class WebSocketHandler {
private connections: Map<string, Array<WS>>;
private games: Map<string, GameInstance>;
@ -23,7 +23,7 @@ export class WebSocketHandler {
}
public handleConnection(ws: WS): void {
const {gameId, playerId} = ws.data.query;
const { gameId, playerId } = ws.data.query;
if (!this.connections.has(gameId)) {
this.connections.set(gameId, []);
@ -53,14 +53,13 @@ export class WebSocketHandler {
private handleMakeMove(ws: WS, message: MakeMoveMessage): void {
const { row, col } = message;
const {gameId, playerId} = ws.data.query;
console.log(`Handling make_move message in game ${gameId} from player ${playerId}: ${{message}}`);
const { gameId, playerId } = ws.data.query;
console.log(
`Handling make_move message in game ${gameId} from player ${playerId}: ${{ message }}`,
);
if (!gameId || !playerId || row === undefined || col === undefined) {
this.sendMessage(
ws,
'Error: missing gameId, playerId, row, or col',
);
this.sendMessage(ws, 'Error: missing gameId, playerId, row, or col');
return;
}
@ -74,10 +73,7 @@ export class WebSocketHandler {
([_, id]) => id === playerId,
)?.[0] as ('black' | 'white') | undefined;
if (!playerColor) {
this.sendMessage(
ws,
'Error: you are not a player in this game',
);
this.sendMessage(ws, 'Error: you are not a player in this game');
return;
}
@ -102,11 +98,13 @@ export class WebSocketHandler {
}
public handleDisconnect(ws: WS): void {
const {gameId, playerId} = ws.data.query;
const { gameId, playerId } = ws.data.query;
const connectionsInGame = this.connections.get(gameId);
if (!connectionsInGame) {
console.error(`Disconnecting WebSocket for player ${playerId} from game ${gameId}, but that game has no connections!`);
console.error(
`Disconnecting WebSocket for player ${playerId} from game ${gameId}, but that game has no connections!`,
);
return;
}
this.connections.set(
@ -127,28 +125,30 @@ export class WebSocketHandler {
public broadcastGameState(gameId: string): void {
const game = this.games.get(gameId);
if (!game) {
console.warn('Attempted to broadcast state of game ${gameId}, which is not loaded.');
console.warn(
'Attempted to broadcast state of game ${gameId}, which is not loaded.',
);
return;
}
const connectionsToUpdate = this.connections.get(gameId);
if (connectionsToUpdate) {
connectionsToUpdate.forEach((ws) => {
const {gameId, playerId} = ws.data.query;
const { gameId, playerId } = ws.data.query;
const updatedBoardHtml = renderGameBoardHtml(game, playerId);
ws.send(updatedBoardHtml);
const updatedPlayerInfoHtml = renderPlayerInfoHtml(
game.id,
playerId,
);
const updatedPlayerInfoHtml = renderPlayerInfoHtml(game.id, playerId);
ws.send(updatedPlayerInfoHtml);
if (game.status === 'finished') {
if (game.winner === 'draw') {
this.sendMessageToGame(gameId, 'Game ended in draw.');
} else if (game.winner) {
this.sendMessageToGame(gameId, `${game.winner.toUpperCase()} wins!`);
this.sendMessageToGame(
gameId,
`${game.winner.toUpperCase()} wins!`,
);
}
} else if (game.status === 'playing') {
const clientPlayerColor = Object.entries(game.players).find(
@ -168,27 +168,21 @@ export class WebSocketHandler {
}
}
public sendMessageToGame(
gameId: string,
message: string,
): void {
public sendMessageToGame(gameId: string, message: string): void {
const connections = this.connections.get(gameId);
if (connections) {
connections.forEach((ws) => {
ws.send('<div id="messages">' + message + '</div>')
ws.send('<div id="messages">' + message + '</div>');
});
}
}
public sendMessage(
targetWs: WS,
message: string,
): void {
targetWs.send('<div id="messages">' + message + '</div>')
public sendMessage(targetWs: WS, message: string): void {
targetWs.send('<div id="messages">' + message + '</div>');
}
public getGame(gameId: string): GameInstance | undefined {
return this.games.get(gameId)
return this.games.get(gameId);
}
createGame(gameId?: string): GameInstance {

View File

@ -16,8 +16,8 @@ const app = new Elysia()
.use(cookie())
.ws('/ws', {
query: t.Object({
gameId: t.String(),
playerId: t.String(),
gameId: t.String(),
playerId: t.String(),
}),
open(ws) {
const { gameId, playerId } = ws.data.query;

View File

@ -12,9 +12,8 @@ export function renderGameBoardHtml(
let boardHtml = '<div id="game-board" class="game-board-grid">';
const currentPlayerColor =
Object.entries(gameState.players).find(
([_, id]) => id === playerId,
)?.[0] || null;
Object.entries(gameState.players).find(([_, id]) => id === playerId)?.[0] ||
null;
const isPlayersTurn =
gameState.status === 'playing' &&
gameState.currentPlayer === currentPlayerColor;
@ -30,9 +29,7 @@ export function renderGameBoardHtml(
}
// HTMX attributes for making a move
const wsAttrs = isPlayersTurn && !stone
? `ws-send="click"`
: '';
const wsAttrs = isPlayersTurn && !stone ? `ws-send="click"` : '';
boardHtml += `
<div