Transforma cualquier juego HTML5 de un jugador en multijugador online con Cerewro: un servidor Node.js con Socket.io sincroniza el estado del juego en tiempo real entre todos los clientes conectados.
| Componente | Tecnología | Rol |
|---|---|---|
| Cliente | HTML5 Canvas + Socket.io-client | Renderiza el juego, envía inputs |
| Servidor | Node.js + Socket.io | Autoridad del estado, sincroniza |
| Protocolo | WebSocket (a través de Socket.io) | Comunicación bidireccional en tiempo real |
Añade modo multijugador online al juego Snake que ya tenemos. Necesito:
- Servidor Node.js + Socket.io en puerto 3001
- Sala de espera con código para invitar a amigos
- Hasta 4 jugadores simultáneos, cada uno con color diferente
- Sincronización de estado a 20 ticks por segundo
- Cuando una serpiente choca, se elimina (el resto sigue)
- Ganador: el último en sobrevivir
- Chat en tiempo real con Bootstrap
- Reconexión automática si se pierde la conexión
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, { cors: { origin: '*' } });
const rooms = {}; // { roomCode: { players, gameState, interval } }
const TICK = 1000 / 20; // 20 ticks/segundo
io.on('connection', socket => {
console.log(`Conectado: ${socket.id}`);
socket.on('createRoom', () => {
const code = Math.random().toString(36).substr(2, 6).toUpperCase();
rooms[code] = { players: {}, gameState: null, interval: null };
socket.join(code);
rooms[code].players[socket.id] = { id: socket.id, color: getColor(0), score: 0 };
socket.emit('roomCreated', { code, playerId: socket.id });
});
socket.on('joinRoom', ({ code }) => {
if (!rooms[code]) return socket.emit('error', 'Sala no encontrada');
const nPlayers = Object.keys(rooms[code].players).length;
if (nPlayers >= 4) return socket.emit('error', 'Sala llena');
socket.join(code);
rooms[code].players[socket.id] = { id: socket.id, color: getColor(nPlayers), score: 0 };
io.to(code).emit('playerJoined', rooms[code].players);
});
socket.on('startGame', ({ code }) => {
initGameState(code);
rooms[code].interval = setInterval(() => {
updateGameState(code);
io.to(code).emit('gameState', rooms[code].gameState);
}, TICK);
});
socket.on('direction', ({ code, dir }) => {
if (rooms[code]?.gameState?.snakes?.[socket.id]) {
rooms[code].gameState.snakes[socket.id].nextDir = dir;
}
});
socket.on('disconnect', () => {
// Limpiar sala si se vacía
for (const [code, room] of Object.entries(rooms)) {
delete room.players[socket.id];
if (Object.keys(room.players).length === 0) {
clearInterval(room.interval);
delete rooms[code];
}
}
});
});
server.listen(3001, () => console.log('Servidor en http://localhost:3001'));
const COLORS = ['#00ff64','#ff6b6b','#4ecdc4','#ffd93d'];
function getColor(i) { return COLORS[i % COLORS.length]; }