494
server.mjs
Archivo normal
494
server.mjs
Archivo normal
@@ -0,0 +1,494 @@
|
||||
import { createServer } from 'http';
|
||||
import { parse } from 'url';
|
||||
import next from 'next';
|
||||
import { Server } from 'socket.io';
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production';
|
||||
const hostname = 'localhost';
|
||||
const port = 3000;
|
||||
|
||||
const app = next({ dev, hostname, port });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
// Store active game rooms
|
||||
const gameRooms = new Map();
|
||||
const playerRooms = new Map();
|
||||
|
||||
// Generate unique room ID
|
||||
function generateRoomId() {
|
||||
return Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
}
|
||||
|
||||
// Create a new game state
|
||||
function createGameState(roomId) {
|
||||
return {
|
||||
id: roomId,
|
||||
players: [],
|
||||
currentPlayerIndex: 0,
|
||||
board: [],
|
||||
boneyard: [],
|
||||
boardEnds: [],
|
||||
winner: null,
|
||||
isGameOver: false,
|
||||
turnsPassed: 0,
|
||||
gameMode: 'waiting',
|
||||
};
|
||||
}
|
||||
|
||||
// Generate domino set
|
||||
function generateDominoSet() {
|
||||
const tiles = [];
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
for (let j = i; j <= 6; j++) {
|
||||
tiles.push({
|
||||
id: `${i}-${j}`,
|
||||
left: i,
|
||||
right: j,
|
||||
isDouble: i === j,
|
||||
});
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
// Shuffle array
|
||||
function shuffleArray(array) {
|
||||
const shuffled = [...array];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
// Deal tiles
|
||||
function dealTiles(numPlayers) {
|
||||
const allTiles = shuffleArray(generateDominoSet());
|
||||
const tilesPerPlayer = 7;
|
||||
const playerTiles = [];
|
||||
|
||||
for (let i = 0; i < numPlayers; i++) {
|
||||
playerTiles.push(allTiles.splice(0, tilesPerPlayer));
|
||||
}
|
||||
|
||||
return {
|
||||
playerTiles,
|
||||
boneyard: allTiles,
|
||||
};
|
||||
}
|
||||
|
||||
// Find starting player
|
||||
function findStartingPlayer(players) {
|
||||
let highestDouble = -1;
|
||||
let startingPlayerIndex = 0;
|
||||
|
||||
players.forEach((player, index) => {
|
||||
player.tiles.forEach(tile => {
|
||||
if (tile.isDouble && tile.left > highestDouble) {
|
||||
highestDouble = tile.left;
|
||||
startingPlayerIndex = index;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return startingPlayerIndex;
|
||||
}
|
||||
|
||||
// Start game
|
||||
function startGame(roomId) {
|
||||
const gameState = gameRooms.get(roomId);
|
||||
if (!gameState) throw new Error('Game not found');
|
||||
|
||||
const { playerTiles, boneyard } = dealTiles(gameState.players.length);
|
||||
|
||||
const updatedPlayers = gameState.players.map((player, index) => ({
|
||||
...player,
|
||||
tiles: playerTiles[index],
|
||||
score: 0,
|
||||
}));
|
||||
|
||||
const startingPlayerIndex = findStartingPlayer(updatedPlayers);
|
||||
|
||||
const newGameState = {
|
||||
...gameState,
|
||||
players: updatedPlayers,
|
||||
currentPlayerIndex: startingPlayerIndex,
|
||||
boneyard,
|
||||
gameMode: 'playing',
|
||||
};
|
||||
|
||||
gameRooms.set(roomId, newGameState);
|
||||
return newGameState;
|
||||
}
|
||||
|
||||
app.prepare().then(() => {
|
||||
const server = createServer(async (req, res) => {
|
||||
try {
|
||||
const parsedUrl = parse(req.url, true);
|
||||
await handle(req, res, parsedUrl);
|
||||
} catch (err) {
|
||||
console.error('Error occurred handling', req.url, err);
|
||||
res.statusCode = 500;
|
||||
res.end('internal server error');
|
||||
}
|
||||
});
|
||||
|
||||
const io = new Server(server, {
|
||||
cors: {
|
||||
origin: '*',
|
||||
methods: ['GET', 'POST'],
|
||||
},
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
console.log('Client connected:', socket.id);
|
||||
|
||||
socket.on('create-room', () => {
|
||||
const roomId = generateRoomId();
|
||||
const gameState = createGameState(roomId);
|
||||
gameRooms.set(roomId, gameState);
|
||||
|
||||
socket.join(roomId);
|
||||
socket.emit('room-created', roomId);
|
||||
console.log('Room created:', roomId);
|
||||
});
|
||||
|
||||
socket.on('join-room', (roomId, playerName) => {
|
||||
const gameState = gameRooms.get(roomId);
|
||||
|
||||
if (!gameState) {
|
||||
socket.emit('error', 'Room not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameState.players.length >= 4) {
|
||||
socket.emit('error', 'Room is full');
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameState.gameMode !== 'waiting') {
|
||||
socket.emit('error', 'Game already started');
|
||||
return;
|
||||
}
|
||||
|
||||
const player = {
|
||||
id: socket.id,
|
||||
name: playerName,
|
||||
tiles: [],
|
||||
score: 0,
|
||||
isAI: false,
|
||||
isReady: false,
|
||||
};
|
||||
|
||||
gameState.players.push(player);
|
||||
playerRooms.set(socket.id, roomId);
|
||||
|
||||
socket.join(roomId);
|
||||
socket.emit('room-joined', gameState, socket.id);
|
||||
socket.to(roomId).emit('player-joined', player);
|
||||
|
||||
console.log(`Player ${playerName} joined room ${roomId}`);
|
||||
});
|
||||
|
||||
socket.on('player-ready', (roomId) => {
|
||||
const gameState = gameRooms.get(roomId);
|
||||
if (!gameState) return;
|
||||
|
||||
const player = gameState.players.find(p => p.id === socket.id);
|
||||
if (!player) return;
|
||||
|
||||
player.isReady = true;
|
||||
gameRooms.set(roomId, gameState);
|
||||
|
||||
const allReady = gameState.players.length >= 2 && gameState.players.every(p => p.isReady);
|
||||
|
||||
if (allReady) {
|
||||
const startedGame = startGame(roomId);
|
||||
io.to(roomId).emit('game-started', startedGame);
|
||||
console.log('Game started in room:', roomId);
|
||||
} else {
|
||||
io.to(roomId).emit('game-state-updated', gameState);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('make-move', (roomId, move) => {
|
||||
console.log('Received make-move:', { roomId, move, socketId: socket.id });
|
||||
|
||||
const gameState = gameRooms.get(roomId);
|
||||
if (!gameState) {
|
||||
console.log('Game state not found for room:', roomId);
|
||||
socket.emit('error', 'Game not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentPlayer = gameState.players[gameState.currentPlayerIndex];
|
||||
|
||||
if (currentPlayer.id !== socket.id) {
|
||||
console.log('Not player turn:', { currentPlayerId: currentPlayer.id, socketId: socket.id });
|
||||
socket.emit('invalid-move', 'Not your turn');
|
||||
return;
|
||||
}
|
||||
|
||||
const player = gameState.players.find(p => p.id === move.playerId);
|
||||
if (!player) {
|
||||
console.log('Player not found:', move.playerId);
|
||||
socket.emit('error', 'Player not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (move.pass) {
|
||||
console.log('Player passing turn');
|
||||
gameState.currentPlayerIndex = (gameState.currentPlayerIndex + 1) % gameState.players.length;
|
||||
gameState.turnsPassed++;
|
||||
} else {
|
||||
const tileIndex = player.tiles.findIndex(t => t.id === move.tile.id);
|
||||
if (tileIndex === -1) {
|
||||
console.log('Tile not found in player hand:', move.tile.id);
|
||||
socket.emit('invalid-move', 'Tile not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validar el movimiento
|
||||
if (gameState.boardEnds.length > 0) {
|
||||
const targetEnd = gameState.boardEnds.find(end =>
|
||||
(move.side === 'left' && end.side === 'left') ||
|
||||
(move.side === 'right' && end.side === 'right')
|
||||
);
|
||||
|
||||
if (!targetEnd) {
|
||||
console.log('Invalid side:', move.side);
|
||||
socket.emit('invalid-move', 'Invalid side');
|
||||
return;
|
||||
}
|
||||
|
||||
const canPlace = move.tile.left === targetEnd.value || move.tile.right === targetEnd.value;
|
||||
if (!canPlace) {
|
||||
console.log('Tile does not match:', { tile: move.tile, targetEnd: targetEnd.value });
|
||||
socket.emit('invalid-move', 'Tile does not match board end');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Placing tile:', { tile: move.tile, side: move.side });
|
||||
|
||||
// Remover la ficha de la mano del jugador
|
||||
player.tiles.splice(tileIndex, 1);
|
||||
|
||||
// Calcular posición según el lado del tablero
|
||||
let position;
|
||||
let tileToPlace = { ...move.tile };
|
||||
|
||||
if (gameState.board.length === 0) {
|
||||
position = { x: 400, y: 300 };
|
||||
} else {
|
||||
// Get the current board end value
|
||||
const targetEnd = gameState.boardEnds.find(end => end.side === move.side);
|
||||
const matchValue = targetEnd?.value || 0;
|
||||
|
||||
// Determine if we need to flip the tile
|
||||
// When placing on the RIGHT side:
|
||||
// - The LEFT value of the new tile should match the board end
|
||||
// - If tile.right matches, we need to flip it
|
||||
// When placing on the LEFT side:
|
||||
// - The RIGHT value of the new tile should match the board end
|
||||
// - If tile.left matches, we need to flip it
|
||||
|
||||
let needsFlip = false;
|
||||
if (move.side === 'right') {
|
||||
// On right side, left value of tile should match
|
||||
if (move.tile.right === matchValue && move.tile.left !== matchValue) {
|
||||
needsFlip = true;
|
||||
}
|
||||
} else {
|
||||
// On left side, right value of tile should match
|
||||
if (move.tile.left === matchValue && move.tile.right !== matchValue) {
|
||||
needsFlip = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Flip the tile if needed
|
||||
if (needsFlip) {
|
||||
tileToPlace = {
|
||||
...move.tile,
|
||||
left: move.tile.right,
|
||||
right: move.tile.left,
|
||||
};
|
||||
console.log('Flipping tile from', move.tile, 'to', tileToPlace);
|
||||
}
|
||||
|
||||
if (move.side === 'right') {
|
||||
const lastTile = gameState.board[gameState.board.length - 1];
|
||||
position = { x: lastTile.position.x + 65, y: lastTile.position.y };
|
||||
} else {
|
||||
const firstTile = gameState.board[0];
|
||||
position = { x: firstTile.position.x - 65, y: firstTile.position.y };
|
||||
}
|
||||
}
|
||||
|
||||
const placedTile = {
|
||||
tile: tileToPlace,
|
||||
position,
|
||||
orientation: 'horizontal',
|
||||
rotation: 0,
|
||||
};
|
||||
|
||||
// Agregar al tablero
|
||||
if (move.side === 'right') {
|
||||
gameState.board.push(placedTile);
|
||||
} else {
|
||||
gameState.board.unshift(placedTile);
|
||||
}
|
||||
|
||||
// Actualizar los extremos del tablero
|
||||
if (gameState.board.length === 1) {
|
||||
gameState.boardEnds = [
|
||||
{ value: tileToPlace.left, position: placedTile.position, side: 'left' },
|
||||
{ value: tileToPlace.right, position: placedTile.position, side: 'right' },
|
||||
];
|
||||
} else {
|
||||
// After placing, the new ends are straightforward:
|
||||
// Left end is the left value of the leftmost tile
|
||||
// Right end is the right value of the rightmost tile
|
||||
const leftTile = gameState.board[0];
|
||||
const rightTile = gameState.board[gameState.board.length - 1];
|
||||
|
||||
gameState.boardEnds = [
|
||||
{ value: leftTile.tile.left, position: leftTile.position, side: 'left' },
|
||||
{ value: rightTile.tile.right, position: rightTile.position, side: 'right' },
|
||||
];
|
||||
}
|
||||
|
||||
gameState.currentPlayerIndex = (gameState.currentPlayerIndex + 1) % gameState.players.length;
|
||||
gameState.turnsPassed = 0;
|
||||
|
||||
if (player.tiles.length === 0) {
|
||||
gameState.winner = player.id;
|
||||
gameState.isGameOver = true;
|
||||
gameState.gameMode = 'finished';
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Updating game state and emitting to room:', roomId);
|
||||
gameRooms.set(roomId, gameState);
|
||||
io.to(roomId).emit('game-state-updated', gameState);
|
||||
console.log('Game state updated successfully');
|
||||
});
|
||||
|
||||
socket.on('draw-tile', (roomId) => {
|
||||
const gameState = gameRooms.get(roomId);
|
||||
if (!gameState) return;
|
||||
|
||||
const currentPlayer = gameState.players[gameState.currentPlayerIndex];
|
||||
|
||||
if (currentPlayer.id !== socket.id) {
|
||||
socket.emit('invalid-move', 'Not your turn');
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameState.boneyard.length === 0) {
|
||||
socket.emit('invalid-move', 'No tiles left to draw');
|
||||
return;
|
||||
}
|
||||
|
||||
const drawnTile = gameState.boneyard.pop();
|
||||
currentPlayer.tiles.push(drawnTile);
|
||||
|
||||
gameRooms.set(roomId, gameState);
|
||||
io.to(roomId).emit('game-state-updated', gameState);
|
||||
});
|
||||
|
||||
socket.on('leave-room', (roomId) => {
|
||||
console.log('Player leaving room:', { socketId: socket.id, roomId });
|
||||
|
||||
const gameState = gameRooms.get(roomId);
|
||||
if (!gameState) return;
|
||||
|
||||
// Filter out AI players to count only human players
|
||||
const humanPlayers = gameState.players.filter(p => !p.isAI);
|
||||
const leavingPlayer = gameState.players.find(p => p.id === socket.id);
|
||||
|
||||
if (!leavingPlayer) return;
|
||||
|
||||
// Remove player from game
|
||||
gameState.players = gameState.players.filter(p => p.id !== socket.id);
|
||||
playerRooms.delete(socket.id);
|
||||
socket.leave(roomId);
|
||||
|
||||
if (gameState.players.length === 0) {
|
||||
// No players left, delete room
|
||||
gameRooms.delete(roomId);
|
||||
console.log('Room deleted - no players remaining:', roomId);
|
||||
} else {
|
||||
// Check if only one human player remains
|
||||
const remainingHumanPlayers = gameState.players.filter(p => !p.isAI);
|
||||
|
||||
if (gameState.gameMode === 'playing' && remainingHumanPlayers.length === 1 && humanPlayers.length > 1) {
|
||||
// Only one human player left, they win by default
|
||||
const winner = remainingHumanPlayers[0];
|
||||
gameState.isGameOver = true;
|
||||
gameState.winner = winner.id;
|
||||
gameState.gameMode = 'finished';
|
||||
console.log(`Player ${winner.name} wins - only player remaining in room ${roomId}`);
|
||||
}
|
||||
|
||||
// Adjust currentPlayerIndex if needed
|
||||
if (gameState.currentPlayerIndex >= gameState.players.length) {
|
||||
gameState.currentPlayerIndex = 0;
|
||||
}
|
||||
|
||||
gameRooms.set(roomId, gameState);
|
||||
io.to(roomId).emit('player-left', socket.id);
|
||||
io.to(roomId).emit('game-state-updated', gameState);
|
||||
console.log(`Player ${leavingPlayer.name} left room ${roomId}. ${gameState.players.length} players remaining.`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
const roomId = playerRooms.get(socket.id);
|
||||
if (roomId) {
|
||||
const gameState = gameRooms.get(roomId);
|
||||
if (gameState) {
|
||||
// Filter out AI players to count only human players
|
||||
const humanPlayers = gameState.players.filter(p => !p.isAI);
|
||||
const disconnectedPlayer = gameState.players.find(p => p.id === socket.id);
|
||||
|
||||
gameState.players = gameState.players.filter(p => p.id !== socket.id);
|
||||
playerRooms.delete(socket.id);
|
||||
|
||||
if (gameState.players.length === 0) {
|
||||
gameRooms.delete(roomId);
|
||||
console.log('Room deleted:', roomId);
|
||||
} else {
|
||||
// Check if only one human player remains after disconnect
|
||||
const remainingHumanPlayers = gameState.players.filter(p => !p.isAI);
|
||||
|
||||
if (gameState.gameMode === 'playing' && remainingHumanPlayers.length === 1 && humanPlayers.length > 1) {
|
||||
// Only one human player left, they win by default
|
||||
const winner = remainingHumanPlayers[0];
|
||||
gameState.isGameOver = true;
|
||||
gameState.winner = winner.id;
|
||||
gameState.gameMode = 'finished';
|
||||
console.log(`Player ${winner.name} wins - only player remaining in room ${roomId}`);
|
||||
}
|
||||
|
||||
// Adjust currentPlayerIndex if needed
|
||||
if (gameState.currentPlayerIndex >= gameState.players.length) {
|
||||
gameState.currentPlayerIndex = 0;
|
||||
}
|
||||
|
||||
gameRooms.set(roomId, gameState);
|
||||
io.to(roomId).emit('player-left', socket.id);
|
||||
io.to(roomId).emit('game-state-updated', gameState);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('Client disconnected:', socket.id);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(port, (err) => {
|
||||
if (err) throw err;
|
||||
console.log(`> Ready on http://${hostname}:${port}`);
|
||||
console.log('> Socket.IO server running');
|
||||
});
|
||||
});
|
||||
Referencia en una nueva incidencia
Block a user