caroxx / index.html
stroumphs's picture
Add 1 files
0ad087b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Caro Game - Online Multiplayer</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<style>
.cell {
width: 40px;
height: 40px;
transition: all 0.2s;
}
.cell:hover:not(.occupied) {
background-color: rgba(0, 0, 0, 0.05);
}
.x-symbol {
color: #3b82f6;
}
.o-symbol {
color: #ef4444;
}
.winning-cell {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.board {
display: grid;
grid-template-columns: repeat(15, 1fr);
gap: 1px;
background-color: #333;
}
#connectionStatus {
position: fixed;
bottom: 20px;
right: 20px;
padding: 8px 12px;
border-radius: 20px;
font-size: 14px;
display: flex;
align-items: center;
}
.connecting {
background-color: #fbbf24;
color: #92400e;
}
.connected {
background-color: #10b981;
color: #064e3b;
}
.disconnected {
background-color: #ef4444;
color: #7f1d1d;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">
<!-- Main Menu -->
<div id="mainMenu" class="max-w-md w-full bg-white rounded-xl shadow-xl overflow-hidden">
<div class="bg-gradient-to-r from-blue-500 to-purple-600 p-6 text-white">
<h1 class="text-3xl font-bold text-center">
<i class="fas fa-gamepad mr-2"></i> Caro Game Online
</h1>
<p class="text-center opacity-90 mt-2">Play against friends in real-time!</p>
</div>
<div class="p-6">
<div class="mb-6">
<label for="playerName" class="block text-sm font-medium text-gray-700 mb-1">Your Name</label>
<input type="text" id="playerName" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Enter your name">
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-1">Game Mode</label>
<div class="grid grid-cols-2 gap-4">
<button id="createRoomBtn" class="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 rounded-lg transition flex items-center justify-center">
<i class="fas fa-plus-circle mr-2"></i> Create Room
</button>
<button id="joinRoomBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition flex items-center justify-center">
<i class="fas fa-sign-in-alt mr-2"></i> Join Room
</button>
</div>
</div>
<div id="joinRoomSection" class="hidden mb-6">
<label for="roomId" class="block text-sm font-medium text-gray-700 mb-1">Room ID</label>
<div class="flex">
<input type="text" id="roomId" class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Enter room ID">
<button id="joinSpecificRoomBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-r-lg transition">
Join
</button>
</div>
</div>
<div id="roomInfo" class="hidden mb-6">
<div class="bg-gray-50 p-4 rounded-lg border border-gray-200">
<h3 class="font-semibold text-lg mb-2 flex items-center">
<i class="fas fa-door-open text-blue-500 mr-2"></i> Room Info
</h3>
<div class="flex justify-between mt-2">
<span class="text-gray-600">Room ID:</span>
<span id="displayRoomId" class="font-medium"></span>
</div>
<div class="flex justify-between mt-1">
<span class="text-gray-600">Created:</span>
<span id="roomCreatedAt" class="text-sm text-gray-500"></span>
</div>
<div id="playersInRoom" class="mt-3 space-y-2">
<!-- Players will be listed here -->
</div>
</div>
</div>
<div id="waitingForPlayers" class="hidden text-center py-4">
<div class="animate-pulse flex flex-col items-center">
<i class="fas fa-user-clock text-4xl text-blue-500 mb-2"></i>
<p class="text-gray-600">Waiting for another player to join...</p>
<div class="mt-4 bg-gray-100 rounded-lg p-3">
<p class="text-sm font-medium">Share this room ID:</p>
<p id="shareRoomId" class="font-mono text-lg font-bold mt-1"></p>
<button id="copyRoomIdBtn" class="mt-2 bg-gray-200 hover:bg-gray-300 text-gray-800 text-sm font-medium py-1 px-3 rounded transition">
<i class="fas fa-copy mr-1"></i> Copy to clipboard
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Game Screen -->
<div id="gameScreen" class="max-w-4xl w-full bg-white rounded-xl shadow-xl overflow-hidden hidden">
<div class="bg-gradient-to-r from-blue-500 to-purple-600 p-6 text-white">
<div class="flex justify-between items-center">
<h1 class="text-2xl font-bold">
<i class="fas fa-gamepad mr-2"></i> Caro Online
</h1>
<div id="roomStatus" class="text-sm bg-white bg-opacity-20 px-3 py-1 rounded-full">Room: <span id="currentRoomId"></span></div>
</div>
</div>
<div class="p-6 flex flex-col md:flex-row gap-8">
<div class="flex-1">
<div class="flex justify-between items-center mb-6">
<div class="player-card bg-blue-50 p-4 rounded-lg border-l-4 border-blue-500 flex-1 mr-2 transition-all duration-300" id="player1Card">
<h3 class="font-semibold text-blue-700" id="player1Name">Player X</h3>
<p class="text-sm text-gray-600">You</p>
</div>
<div class="player-card bg-gray-100 p-4 rounded-lg border-l-4 border-gray-300 flex-1 ml-2 transition-all duration-300" id="player2Card">
<h3 class="font-semibold text-gray-700" id="player2Name">Player O</h3>
<p class="text-sm text-gray-600">Waiting...</p>
</div>
</div>
<div class="board-container overflow-auto border rounded-lg shadow-inner bg-white p-2">
<div class="board" id="board"></div>
</div>
<div class="mt-6 flex justify-between items-center">
<button id="leaveRoomBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-lg transition">
<i class="fas fa-sign-out-alt mr-2"></i> Leave Room
</button>
<div id="gameStatus" class="text-lg font-medium text-gray-700">
Setting up game...
</div>
<button id="gameResetBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition">
<i class="fas fa-redo mr-2"></i> Rematch
</button>
</div>
</div>
<div class="md:w-64 bg-gray-50 p-4 rounded-lg border border-gray-200">
<h3 class="font-semibold text-lg mb-4 flex items-center">
<i class="fas fa-comments text-purple-500 mr-2"></i> Chat
</h3>
<div class="chat-messages h-48 overflow-y-auto mb-3" id="chatMessages">
<div class="text-center text-sm text-gray-500 py-4">No messages yet</div>
</div>
<div class="flex">
<input type="text" id="chatInput" class="flex-1 px-3 py-2 border border-gray-300 rounded-l-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Type a message...">
<button id="sendMessageBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-r-lg transition">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Win Modal -->
<div id="winModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl p-8 max-w-md w-full mx-4 text-center">
<div class="text-6xl mb-4" id="winSymbol"></div>
<h2 class="text-2xl font-bold mb-2" id="winMessage"></h2>
<p class="text-gray-600 mb-6" id="winDescription"></p>
<div class="flex gap-4 justify-center">
<button id="playAgainBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-lg transition">
Play Again
</button>
<button id="closeModalBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-6 rounded-lg transition">
Close
</button>
</div>
</div>
</div>
<!-- Connection Status Indicator -->
<div id="connectionStatus" class="connecting">
<i class="fas fa-circle-notch fa-spin mr-2"></i>
<span>Connecting...</span>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const BOARD_SIZE = 15;
// DOM Elements
const mainMenu = document.getElementById('mainMenu');
const gameScreen = document.getElementById('gameScreen');
const playerNameInput = document.getElementById('playerName');
const createRoomBtn = document.getElementById('createRoomBtn');
const joinRoomBtn = document.getElementById('joinRoomBtn');
const joinRoomSection = document.getElementById('joinRoomSection');
const roomIdInput = document.getElementById('roomId');
const joinSpecificRoomBtn = document.getElementById('joinSpecificRoomBtn');
const roomInfo = document.getElementById('roomInfo');
const displayRoomId = document.getElementById('displayRoomId');
const playersInRoom = document.getElementById('playersInRoom');
const waitingForPlayers = document.getElementById('waitingForPlayers');
const shareRoomId = document.getElementById('shareRoomId');
const copyRoomIdBtn = document.getElementById('copyRoomIdBtn');
const board = document.getElementById('board');
const gameStatus = document.getElementById('gameStatus');
const leaveRoomBtn = document.getElementById('leaveRoomBtn');
const gameResetBtn = document.getElementById('gameResetBtn');
const player1Card = document.getElementById('player1Card');
const player2Card = document.getElementById('player2Card');
const player1Name = document.getElementById('player1Name');
const player2Name = document.getElementById('player2Name');
const winModal = document.getElementById('winModal');
const winSymbol = document.getElementById('winSymbol');
const winMessage = document.getElementById('winMessage');
const winDescription = document.getElementById('winDescription');
const playAgainBtn = document.getElementById('playAgainBtn');
const closeModalBtn = document.getElementById('closeModalBtn');
const chatMessages = document.getElementById('chatMessages');
const chatInput = document.getElementById('chatInput');
const sendMessageBtn = document.getElementById('sendMessageBtn');
const connectionStatus = document.getElementById('connectionStatus');
const currentRoomIdDisplay = document.getElementById('currentRoomId');
// Game state
let socket;
let currentPlayer = '';
let gameBoard = [];
let gameActive = false;
let playerId = '';
let roomId = '';
let playerSymbol = '';
let opponentName = '';
let playerName = '';
let isRoomCreator = false;
// Initialize Socket.IO connection
function initializeSocket() {
// Use the current host for the Socket.IO connection
const socketUrl = window.location.hostname === 'localhost'
? 'http://localhost:3000'
: window.location.origin;
socket = io(socketUrl, {
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000
});
// Connection status handlers
socket.on('connect', () => {
console.log('Connected to server');
connectionStatus.className = 'connected';
connectionStatus.innerHTML = '<i class="fas fa-plug mr-2"></i><span>Connected</span>';
});
socket.on('disconnect', () => {
console.log('Disconnected from server');
connectionStatus.className = 'disconnected';
connectionStatus.innerHTML = '<i class="fas fa-plug mr-2"></i><span>Disconnected</span>';
if (roomId) {
showError('Disconnected from server. Trying to reconnect...');
}
});
socket.on('connect_error', (err) => {
console.log('Connection error:', err);
connectionStatus.className = 'disconnected';
connectionStatus.innerHTML = '<i class="fas fa-exclamation-triangle mr-2"></i><span>Connection Error</span>';
});
socket.on('reconnecting', (attempt) => {
console.log('Attempting to reconnect:', attempt);
connectionStatus.className = 'connecting';
connectionStatus.innerHTML = `<i class="fas fa-circle-notch fa-spin mr-2"></i><span>Reconnecting (${attempt})...</span>`;
});
socket.on('reconnect', () => {
console.log('Reconnected to server');
connectionStatus.className = 'connected';
connectionStatus.innerHTML = '<i class="fas fa-plug mr-2"></i><span>Reconnected</span>';
});
// Game event handlers
socket.on('playerId', (id) => {
playerId = id;
console.log('Your player ID:', playerId);
});
socket.on('roomCreated', (data) => {
console.log('Room created:', data.roomId);
roomId = data.roomId;
isRoomCreator = true;
displayRoomId.textContent = roomId;
shareRoomId.textContent = roomId;
currentRoomIdDisplay.textContent = roomId;
// Add creator to player list
updatePlayerList([{ id: playerId, name: playerName }]);
roomInfo.classList.remove('hidden');
waitingForPlayers.classList.remove('hidden');
joinRoomSection.classList.add('hidden');
});
socket.on('roomJoined', (data) => {
console.log('Joined room:', data);
roomId = data.roomId;
currentRoomIdDisplay.textContent = roomId;
// Update player list
updatePlayerList(data.players);
// If there's another player, wait for game start
if (data.players.length === 2) {
waitingForPlayers.classList.add('hidden');
roomInfo.classList.remove('hidden');
gameStatus.textContent = 'Game starting...';
} else {
waitingForPlayers.classList.remove('hidden');
roomInfo.classList.remove('hidden');
}
mainMenu.classList.add('hidden');
gameScreen.classList.remove('hidden');
});
socket.on('playerJoined', (data) => {
console.log('Player joined:', data.player.name);
updatePlayerList(data.players);
// If this is the creator and a second player joined, start the game
if (isRoomCreator && data.players.length === 2) {
socket.emit('startGame', { roomId });
waitingForPlayers.classList.add('hidden');
gameStatus.textContent = 'Game starting...';
}
});
socket.on('playerLeft', (data) => {
console.log('Player left:', data.playerId);
updatePlayerList(data.players);
if (gameActive) {
gameActive = false;
showError('Opponent disconnected. Waiting for reconnection...');
}
});
socket.on('gameStarted', (data) => {
console.log('Game started:', data);
playerSymbol = data.playerSymbol; // 'X' or 'O'
opponentName = data.players.find(p => p.id !== playerId)?.name || 'Opponent';
// Update UI
if (playerSymbol === 'X') {
player1Name.textContent = playerName;
player1Card.querySelector('p').textContent = 'You (X)';
player2Name.textContent = opponentName;
player2Card.querySelector('p').textContent = 'Opponent (O)';
currentPlayer = 'X';
} else {
player1Name.textContent = opponentName;
player1Card.querySelector('p').textContent = 'Opponent (X)';
player2Name.textContent = playerName;
player2Card.querySelector('p').textContent = 'You (O)';
currentPlayer = 'X'; // X always starts
}
initializeBoard();
gameActive = true;
updateTurnDisplay();
});
socket.on('moveMade', (data) => {
console.log('Move received:', data);
const { row, col, player } = data;
// Update board
gameBoard[row][col] = player;
const cell = document.querySelector(`[data-row="${row}"][data-col="${col}"]`);
cell.classList.add('occupied');
const symbol = document.createElement('div');
symbol.classList.add('text-2xl', 'font-bold');
symbol.textContent = player;
symbol.classList.add(player === 'X' ? 'x-symbol' : 'o-symbol');
cell.innerHTML = '';
cell.appendChild(symbol);
// Switch turns
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
updateTurnDisplay();
});
socket.on('gameOver', (data) => {
console.log('Game over:', data);
gameActive = false;
// Highlight winning cells if any
if (data.winningCells) {
data.winningCells.forEach(cell => {
const cellElement = document.querySelector(`[data-row="${cell.row}"][data-col="${cell.col}"]`);
cellElement.classList.add('winning-cell');
});
}
// Show win/draw modal
if (data.winner) {
const isWinner = (playerSymbol === data.winner);
winSymbol.innerHTML = data.winner === 'X' ?
'<i class="fas fa-times text-blue-500"></i>' :
'<i class="far fa-circle text-red-500"></i>';
winMessage.textContent = isWinner ? 'You Won!' : 'You Lost!';
winDescription.textContent = isWinner ?
`Congratulations! You defeated ${opponentName}.` :
`${opponentName} won this game. Better luck next time!`;
} else {
winSymbol.innerHTML = '<i class="fas fa-handshake text-yellow-500"></i>';
winMessage.textContent = "It's a Draw!";
winDescription.textContent = "The game has ended in a tie.";
}
winModal.classList.remove('hidden');
});
socket.on('rematchOffer', () => {
console.log('Received rematch offer');
showMessage(`${opponentName} wants a rematch!`, 'purple');
});
socket.on('rematchAccepted', () => {
console.log('Rematch accepted');
showMessage(`${opponentName} accepted the rematch!`, 'purple');
initializeBoard();
gameActive = true;
updateTurnDisplay();
});
socket.on('newGame', () => {
console.log('Starting new game');
initializeBoard();
gameActive = true;
updateTurnDisplay();
});
socket.on('error', (error) => {
console.error('Error:', error);
showError(error.message);
});
socket.on('chatMessage', (message) => {
addChatMessage(message.sender, message.text, message.isSystem);
});
}
// Initialize game board
function initializeBoard() {
board.innerHTML = '';
board.style.gridTemplateColumns = `repeat(${BOARD_SIZE}, 1fr)`;
gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(''));
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
const cell = document.createElement('div');
cell.classList.add('cell', 'border', 'border-gray-200', 'flex', 'items-center', 'justify-center', 'cursor-pointer');
cell.dataset.row = i;
cell.dataset.col = j;
// Add subtle grid coordinates for first row and column
if (i === 0) {
const colLabel = document.createElement('div');
colLabel.classList.add('text-xs', 'text-gray-400', 'absolute', '-mt-6');
colLabel.textContent = j + 1;
cell.appendChild(colLabel);
}
if (j === 0) {
const rowLabel = document.createElement('div');
rowLabel.classList.add('text-xs', 'text-gray-400', 'absolute', '-ml-6');
rowLabel.textContent = i + 1;
cell.appendChild(rowLabel);
}
cell.addEventListener('click', () => handleCellClick(i, j));
board.appendChild(cell);
}
}
}
// Handle cell click
function handleCellClick(row, col) {
if (!gameActive || currentPlayer !== playerSymbol || gameBoard[row][col] !== '') return;
// Make move
socket.emit('makeMove', { roomId, row, col, player: playerSymbol });
// Update local board immediately for better UX
gameBoard[row][col] = playerSymbol;
const cell = document.querySelector(`[data-row="${row}"][data-col="${col}"]`);
cell.classList.add('occupied');
const symbol = document.createElement('div');
symbol.classList.add('text-2xl', 'font-bold');
symbol.textContent = playerSymbol;
symbol.classList.add(playerSymbol === 'X' ? 'x-symbol' : 'o-symbol');
cell.innerHTML = '';
cell.appendChild(symbol);
// Switch turns locally (server will confirm)
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
updateTurnDisplay();
}
// Update the turn display
function updateTurnDisplay() {
if (currentPlayer === playerSymbol) {
gameStatus.innerHTML = `<span class="font-semibold">Your turn</span> (${playerSymbol})`;
player1Card.classList.toggle('bg-blue-50', playerSymbol === 'X');
player1Card.classList.toggle('border-blue-500', playerSymbol === 'X');
player2Card.classList.toggle('bg-blue-50', playerSymbol === 'O');
player2Card.classList.toggle('border-blue-500', playerSymbol === 'O');
} else {
gameStatus.innerHTML = `<span class="font-semibold">${opponentName}'s turn</span> (${currentPlayer})`;
player1Card.classList.toggle('bg-blue-50', playerSymbol !== 'X');
player1Card.classList.toggle('border-blue-500', playerSymbol !== 'X');
player2Card.classList.toggle('bg-blue-50', playerSymbol !== 'O');
player2Card.classList.toggle('border-blue-500', playerSymbol !== 'O');
}
}
// Update player list
function updatePlayerList(players) {
playersInRoom.innerHTML = '';
if (players.length === 0) {
playersInRoom.innerHTML = '<div class="text-center text-gray-500 py-2">No players</div>';
return;
}
players.forEach(player => {
const playerElement = document.createElement('div');
playerElement.classList.add('flex', 'items-center', 'py-1');
const icon = document.createElement('i');
icon.classList.add('fas', player.id === playerId ? 'fa-user' : 'fa-user-friends', 'mr-2');
icon.style.color = player.id === playerId ? '#3b82f6' : '#6b7280';
const name = document.createElement('span');
name.textContent = player.name;
if (player.id === playerId) {
name.classList.add('font-medium', 'text-blue-600');
}
playerElement.appendChild(icon);
playerElement.appendChild(name);
playersInRoom.appendChild(playerElement);
});
}
// Show error message
function showError(message) {
const existingError = document.getElementById('tempError');
if (existingError) existingError.remove();
const errorElement = document.createElement('div');
errorElement.id = 'tempError';
errorElement.className = 'bg-red-100 border-l-4 border-red-500 text-red-700 p-3 mb-4 rounded';
errorElement.role = 'alert';
const content = document.createElement('p');
content.className = 'font-medium';
content.innerHTML = `<i class="fas fa-exclamation-circle mr-2"></i> ${message}`;
errorElement.appendChild(content);
const firstChild = document.querySelector('.p-6 > div:first-child');
if (firstChild) {
firstChild.parentNode.insertBefore(errorElement, firstChild);
}
// Auto remove after 5 seconds
setTimeout(() => {
errorElement.remove();
}, 5000);
}
// Show success/info message
function showMessage(message, color = 'blue') {
const colorClasses = {
blue: 'bg-blue-100 border-blue-500 text-blue-700',
green: 'bg-green-100 border-green-500 text-green-700',
purple: 'bg-purple-100 border-purple-500 text-purple-700'
};
const existingMsg = document.getElementById('tempMessage');
if (existingMsg) existingMsg.remove();
const msgElement = document.createElement('div');
msgElement.id = 'tempMessage';
msgElement.className = `${colorClasses[color]} border-l-4 p-3 mb-4 rounded`;
msgElement.role = 'alert';
const content = document.createElement('p');
content.className = 'font-medium';
content.innerHTML = `<i class="fas fa-info-circle mr-2"></i> ${message}`;
msgElement.appendChild(content);
const firstChild = document.querySelector('.p-6 > div:first-child');
if (firstChild) {
firstChild.parentNode.insertBefore(msgElement, firstChild);
}
// Auto remove after 5 seconds
setTimeout(() => {
msgElement.remove();
}, 5000);
}
// Add chat message
function addChatMessage(sender, text, isSystem = false) {
if (chatMessages.firstChild?.classList?.contains('text-center')) {
chatMessages.innerHTML = '';
}
const messageElement = document.createElement('div');
messageElement.className = 'mb-2';
if (isSystem) {
messageElement.innerHTML = `
<div class="text-xs text-center text-gray-500 mb-1">${text}</div>
`;
} else {
const isCurrentUser = sender === playerName;
messageElement.innerHTML = `
<div class="flex ${isCurrentUser ? 'justify-end' : 'justify-start'}">
<div class="max-w-xs ${isCurrentUser ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-800'} rounded-lg p-2">
<div class="text-xs font-semibold ${isCurrentUser ? 'text-blue-100' : 'text-gray-600'}">${sender}</div>
<div class="text-sm">${text}</div>
</div>
</div>
`;
}
chatMessages.appendChild(messageElement);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// Event listeners
createRoomBtn.addEventListener('click', () => {
playerName = playerNameInput.value.trim();
if (!playerName) {
showError('Please enter your name');
return;
}
socket.emit('createRoom', { playerName });
});
joinRoomBtn.addEventListener('click', () => {
playerName = playerNameInput.value.trim();
if (!playerName) {
showError('Please enter your name');
return;
}
joinRoomSection.classList.remove('hidden');
});
joinSpecificRoomBtn.addEventListener('click', () => {
const roomId = roomIdInput.value.trim();
if (!roomId) {
showError('Please enter a room ID');
return;
}
socket.emit('joinRoom', { roomId, playerName });
});
copyRoomIdBtn.addEventListener('click', () => {
navigator.clipboard.writeText(roomId)
.then(() => {
const originalText = copyRoomIdBtn.innerHTML;
copyRoomIdBtn.innerHTML = '<i class="fas fa-check mr-1"></i> Copied!';
setTimeout(() => {
copyRoomIdBtn.innerHTML = originalText;
}, 2000);
})
.catch(err => {
console.error('Failed to copy:', err);
});
});
leaveRoomBtn.addEventListener('click', () => {
socket.emit('leaveRoom', { roomId });
mainMenu.classList.remove('hidden');
gameScreen.classList.add('hidden');
roomId = '';
playerSymbol = '';
opponentName = '';
isRoomCreator = false;
});
gameResetBtn.addEventListener('click', () => {
if (!opponentName) {
showError('Wait for opponent to join before starting a rematch');
return;
}
socket.emit('offerRematch', { roomId });
showMessage(`You offered a rematch to ${opponentName}`, 'purple');
});
playAgainBtn.addEventListener('click', () => {
socket.emit('acceptRematch', { roomId });
winModal.classList.add('hidden');
});
closeModalBtn.addEventListener('click', () => {
winModal.classList.add('hidden');
});
sendMessageBtn.addEventListener('click', sendChatMessage);
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendChatMessage();
}
});
function sendChatMessage() {
const message = chatInput.value.trim();
if (!message || !roomId) return;
socket.emit('sendChatMessage', { roomId, message, sender: playerName });
addChatMessage(playerName, message);
chatInput.value = '';
}
// Initialize
initializeSocket();
});
</script>
</body>
</html>