gomoku/dist/bundle.js

308 lines
8.9 KiB
JavaScript

// src/game-client/WebSocketClient.ts
class WebSocketClient {
ws = null;
url;
messageQueue = [];
isConnected = false;
reconnectCount = 0;
options;
manualClose = false;
onMessageHandler = () => {};
onOpenHandler = () => {};
onCloseHandler = () => {};
onErrorHandler = () => {};
constructor(url, options) {
this.url = url;
this.options = {
reconnectAttempts: 5,
reconnectInterval: 3000,
...options,
};
}
connect() {
this.manualClose = false;
this.ws = new WebSocket(this.url);
this.ws.onopen = this.handleOpen.bind(this);
this.ws.onmessage = this.handleMessage.bind(this);
this.ws.onclose = this.handleClose.bind(this);
this.ws.onerror = this.handleError.bind(this);
}
send(message) {
if (this.isConnected && this.ws) {
this.ws.send(message);
} else {
this.messageQueue.push(message);
}
}
close() {
this.manualClose = true;
if (this.ws) {
this.ws.close();
}
}
onMessage(handler) {
this.onMessageHandler = handler;
}
onOpen(handler) {
this.onOpenHandler = handler;
}
onClose(handler) {
this.onCloseHandler = handler;
}
onError(handler) {
this.onErrorHandler = handler;
}
handleOpen() {
this.isConnected = true;
this.reconnectCount = 0;
this.onOpenHandler();
this.flushMessageQueue();
}
handleMessage(event) {
this.onMessageHandler(event.data);
}
handleClose(event) {
this.isConnected = false;
this.onCloseHandler(event.code, event.reason);
if (
!this.manualClose &&
this.reconnectCount < this.options.reconnectAttempts
) {
this.reconnectCount++;
setTimeout(() => this.connect(), this.options.reconnectInterval);
}
}
handleError(event) {
this.onErrorHandler(event);
}
flushMessageQueue() {
while (this.messageQueue.length > 0 && this.isConnected && this.ws) {
const message = this.messageQueue.shift();
if (message) {
this.ws.send(message);
}
}
}
}
// src/game-client/GameStateManager.ts
class GameStateManager {
gameState;
stateHistory;
constructor() {
this.gameState = this.getDefaultGameState();
this.stateHistory = [];
}
getDefaultGameState() {
const emptyBoard = Array(15)
.fill(null)
.map(() => Array(15).fill(null));
return {
id: '',
board: emptyBoard,
currentPlayer: 'black',
status: 'waiting',
winner: null,
players: {},
};
}
getGameState() {
return this.gameState;
}
updateGameState(newState) {
this.stateHistory.push(JSON.parse(JSON.stringify(this.gameState)));
this.gameState = newState;
}
rollbackGameState() {
if (this.stateHistory.length > 0) {
this.gameState = this.stateHistory.pop();
} else {
console.warn('No previous state to rollback to.');
}
}
}
// src/game-client/GameBoardUI.ts
class GameBoardUI {
boardElement;
cells = [];
onCellClickCallback = null;
isInteractionEnabled = true;
constructor(boardElement) {
this.boardElement = boardElement;
this.initializeBoard();
}
initializeBoard() {
this.boardElement.innerHTML = '';
this.boardElement.style.display = 'grid';
this.boardElement.style.gridTemplateColumns = 'repeat(15, 1fr)';
this.boardElement.style.width = '450px';
this.boardElement.style.height = '450px';
this.boardElement.style.border = '1px solid black';
for (let row = 0; row < 15; row++) {
this.cells[row] = [];
for (let col = 0; col < 15; col++) {
const cell = document.createElement('div');
cell.classList.add('board-cell');
cell.style.width = '30px';
cell.style.height = '30px';
cell.style.border = '1px solid #ccc';
cell.style.boxSizing = 'border-box';
cell.style.display = 'flex';
cell.style.justifyContent = 'center';
cell.style.alignItems = 'center';
cell.dataset.row = row.toString();
cell.dataset.col = col.toString();
cell.addEventListener('click', () => this.handleCellClick(row, col));
this.boardElement.appendChild(cell);
this.cells[row][col] = cell;
}
}
}
updateBoard(gameState) {
const board = gameState.board;
const lastMove = { row: -1, col: -1 };
for (let row = 0; row < 15; row++) {
for (let col = 0; col < 15; col++) {
const cell = this.cells[row][col];
cell.innerHTML = '';
const stone = board[row][col];
if (stone) {
const stoneElement = document.createElement('div');
stoneElement.style.width = '24px';
stoneElement.style.height = '24px';
stoneElement.style.borderRadius = '50%';
stoneElement.style.backgroundColor =
stone === 'black' ? 'black' : 'white';
stoneElement.style.border = '1px solid #333';
cell.appendChild(stoneElement);
}
cell.classList.remove('last-move');
}
}
this.isInteractionEnabled =
gameState.status === 'playing' && gameState.currentPlayer === 'black';
this.boardElement.style.pointerEvents = this.isInteractionEnabled
? 'auto'
: 'none';
this.boardElement.style.opacity = this.isInteractionEnabled ? '1' : '0.7';
console.log(
`Current Player: ${gameState.currentPlayer}, Status: ${gameState.status}`,
);
}
setOnCellClick(callback) {
this.onCellClickCallback = callback;
}
handleCellClick(row, col) {
if (this.isInteractionEnabled && this.onCellClickCallback) {
this.onCellClickCallback(row, col);
}
}
}
// src/client-entry.ts
console.log('Gomoku client entry point loaded.');
var WS_URL = 'ws://localhost:3000/ws';
var gameStateManager = new GameStateManager();
var wsClient = new WebSocketClient(WS_URL);
var gameBoardElement = document.getElementById('game-board');
console.log('gameBoardElement: ', gameBoardElement);
var messagesElement = document.getElementById('messages');
var playerInfoElement = document.getElementById('player-info');
if (!gameBoardElement || !messagesElement || !playerInfoElement) {
console.error(
'Missing essential DOM elements (game-board, messages, or player-info)',
);
throw new Error(
'Missing essential DOM elements (game-board, messages, or player-info)',
);
}
var gameBoardUI = new GameBoardUI(gameBoardElement);
console.log('GameBoardUI initialized.', gameBoardUI);
wsClient.onMessage((message) => {
try {
const msg = JSON.parse(message);
console.log('Parsed message:', msg);
switch (msg.type) {
case 'game_state':
gameStateManager.updateGameState(msg.state);
gameBoardUI.updateBoard(gameStateManager.getGameState());
console.log('Game state updated: ', gameStateManager.getGameState());
break;
case 'move_result':
if (msg.success) {
console.log('Move successful!');
} else {
console.error(`Move failed: ${msg.error}`);
gameStateManager.rollbackGameState();
gameBoardUI.updateBoard(gameStateManager.getGameState());
}
break;
case 'player_joined':
console.log(`${msg.playerId} joined the game.`);
break;
case 'player_disconnected':
console.log(`${msg.playerId} disconnected.`);
break;
case 'ping':
break;
default:
console.log(`Unknown message type: ${msg.type}`);
}
} catch (e) {
console.error(
'Error parsing WebSocket message:',
e,
'Message was:',
message,
);
}
});
gameBoardUI.setOnCellClick((row, col) => {
const moveMessage = {
type: 'make_move',
row,
col,
};
console.log('Sending move:', moveMessage);
wsClient.send(JSON.stringify(moveMessage));
const currentGameState = gameStateManager.getGameState();
const nextPlayer =
currentGameState.currentPlayer === 'black' ? 'white' : 'black';
const newBoard = currentGameState.board.map((rowArr) => [...rowArr]);
newBoard[row][col] = currentGameState.currentPlayer;
const optimisticState = {
...currentGameState,
board: newBoard,
currentPlayer: nextPlayer,
};
gameStateManager.updateGameState(optimisticState);
gameBoardUI.updateBoard(gameStateManager.getGameState());
});
wsClient.onOpen(() => {
console.log('Connected to game server.');
const playerId = `player-${Math.random().toString(36).substring(2, 9)}`;
const joinMessage = {
type: 'join_game',
gameId: 'some-game-id',
playerId,
};
wsClient.send(JSON.stringify(joinMessage));
if (playerInfoElement) {
playerInfoElement.textContent = `You are: ${playerId} (Waiting for game state...)`;
}
});
wsClient.onClose(() => {
console.log('Disconnected from game server. Attempting to reconnect...');
});
wsClient.onError((error) => {
console.error(
`WebSocket error: ${error instanceof ErrorEvent ? error.message : String(error)}`,
);
});
wsClient.connect();
gameBoardUI.updateBoard(gameStateManager.getGameState());
if (playerInfoElement) {
playerInfoElement.textContent = `You are: (Connecting...)`;
}