357 lines
8.3 KiB
Markdown
357 lines
8.3 KiB
Markdown
# 🎯 Snatch or Share - Servidor
|
||
|
||
Servidor de juego multijugador basado en Colyseus.io que implementa el "Snatch Game" de Elinor Ostrom para estudiar la evolución de instituciones y cooperación.
|
||
|
||
## 🛠️ Stack Tecnológico
|
||
|
||
- **Colyseus.io** (framework de servidor multijugador)
|
||
- **Node.js** (runtime)
|
||
- **TypeScript** (tipado estricto)
|
||
- **Express** (servidor HTTP)
|
||
- **@colyseus/schema** (sincronización de estado)
|
||
|
||
## 🚀 Inicio Rápido
|
||
|
||
### Prerrequisitos
|
||
- Node.js 18+
|
||
- npm 9+
|
||
|
||
### Instalación y Desarrollo
|
||
|
||
```bash
|
||
# Instalar dependencias
|
||
npm install
|
||
|
||
# Iniciar servidor de desarrollo (puerto 2567)
|
||
npm run dev
|
||
|
||
# Verificar que el servidor esté ejecutándose
|
||
curl http://localhost:2567
|
||
```
|
||
|
||
### Comandos Disponibles
|
||
|
||
```bash
|
||
# Desarrollo
|
||
npm run dev # Servidor con hot reload (ts-node-dev)
|
||
|
||
# Producción
|
||
npm run build # Compilar TypeScript a JavaScript
|
||
npm run start # Ejecutar servidor compilado
|
||
|
||
# Utilidades
|
||
npm test # Ejecutar tests (placeholder)
|
||
```
|
||
|
||
## 🏗️ Arquitectura del Servidor
|
||
|
||
### Estructura de Directorios
|
||
|
||
```
|
||
server/
|
||
├── src/
|
||
│ ├── rooms/ # Salas de juego Colyseus
|
||
│ │ └── GameRoom.ts # Sala principal del juego
|
||
│ ├── app.config.ts # Configuración de Colyseus
|
||
│ └── index.ts # Punto de entrada del servidor
|
||
├── lib/ # JavaScript compilado (build)
|
||
└── tsconfig.json # Configuración TypeScript
|
||
```
|
||
|
||
### GameRoom.ts - Sala Principal
|
||
|
||
```typescript
|
||
export class GameRoom extends Room<GameState> {
|
||
maxClients = 3;
|
||
private producerRoles = ["turkey", "coffee", "corn"];
|
||
|
||
onCreate(options: GameRoomOptions) {
|
||
// Inicialización de la sala
|
||
}
|
||
|
||
onJoin(client: Client, options: any) {
|
||
// Manejo de jugadores que se unen
|
||
}
|
||
|
||
onMessage(type: string, callback: Function) {
|
||
// Handlers de mensajes del cliente
|
||
}
|
||
}
|
||
```
|
||
|
||
## 📊 Esquemas de Estado (Colyseus Schema)
|
||
|
||
### GameState
|
||
Estado principal del juego sincronizado con todos los clientes:
|
||
|
||
```typescript
|
||
export class GameState extends Schema {
|
||
@type({ map: Player }) players = new MapSchema<Player>();
|
||
@type({ array: TradeOffer }) activeTradeOffers = new ArraySchema<TradeOffer>();
|
||
@type("number") round: number = 1;
|
||
@type("string") gamePhase: string = "waiting";
|
||
@type("boolean") gameStarted: boolean = false;
|
||
@type("number") minPlayers: number = 3;
|
||
@type("number") maxPlayers: number = 3;
|
||
}
|
||
```
|
||
|
||
### Player
|
||
Información de cada jugador:
|
||
|
||
```typescript
|
||
export class Player extends Schema {
|
||
@type("string") id: string;
|
||
@type("string") name: string;
|
||
@type("string") producerRole: string; // "turkey" | "coffee" | "corn"
|
||
@type(TokenInventory) tokens = new TokenInventory();
|
||
@type("number") points: number = 0;
|
||
@type("number") shameTokens: number = 0;
|
||
@type("boolean") isSuspended: boolean = false;
|
||
@type("string") role: string = "trader"; // "trader" | "judge"
|
||
}
|
||
```
|
||
|
||
### TradeOffer
|
||
Ofertas comerciales entre jugadores:
|
||
|
||
```typescript
|
||
export class TradeOffer extends Schema {
|
||
@type("string") id: string;
|
||
@type("string") offererId: string;
|
||
@type("string") targetId: string;
|
||
@type(TokenInventory) offering = new TokenInventory();
|
||
@type(TokenInventory) requesting = new TokenInventory();
|
||
@type("string") status: string = "pending";
|
||
}
|
||
```
|
||
|
||
### TokenInventory
|
||
Inventario de tokens por jugador:
|
||
|
||
```typescript
|
||
export class TokenInventory extends Schema {
|
||
@type("number") turkey: number = 0;
|
||
@type("number") coffee: number = 0;
|
||
@type("number") corn: number = 0;
|
||
}
|
||
```
|
||
|
||
## 🎮 Lógica del Juego
|
||
|
||
### Inicialización
|
||
1. **Sala creada**: Espera exactamente 3 jugadores
|
||
2. **Asignación de roles**: Roles únicos asignados aleatoriamente
|
||
3. **Distribución inicial**: 5 tokens del tipo correspondiente
|
||
4. **Fase trading**: Comienza la Ronda 1
|
||
|
||
### Sistema de Tokens
|
||
- **Valor propio**: 1 punto por token del mismo tipo
|
||
- **Valor ajeno**: 2 puntos por token de otro tipo
|
||
- **Ejemplo**: 5 propios + 3 ajenos = 5×1 + 3×2 = 11 puntos
|
||
|
||
### Ofertas Comerciales
|
||
- **Límite**: Máximo 2 ofertas por jugador por objetivo
|
||
- **Simultaneidad**: Múltiples ofertas activas
|
||
- **Visibilidad**: Todas las ofertas son públicas
|
||
- **Respuestas**: Accept, Reject, Snatch
|
||
|
||
### Cumplimiento Parcial
|
||
```typescript
|
||
// Ejemplo: Ofrecer 6 tokens pero solo tener 5
|
||
const actualOffering = {
|
||
turkey: Math.min(offer.offering.turkey, offerer.tokens.turkey),
|
||
coffee: Math.min(offer.offering.coffee, offerer.tokens.coffee),
|
||
corn: Math.min(offer.offering.corn, offerer.tokens.corn)
|
||
};
|
||
```
|
||
|
||
## 📡 API de Mensajes
|
||
|
||
### Mensajes del Cliente → Servidor
|
||
|
||
#### makeOffer
|
||
```typescript
|
||
{
|
||
targetId: string,
|
||
offering: { turkey: number, coffee: number, corn: number },
|
||
requesting: { turkey: number, coffee: number, corn: number }
|
||
}
|
||
```
|
||
|
||
#### respondToOffer
|
||
```typescript
|
||
{
|
||
offerId: string,
|
||
response: "accept" | "reject" | "snatch"
|
||
}
|
||
```
|
||
|
||
#### cancelOffer
|
||
```typescript
|
||
{
|
||
offerId: string
|
||
}
|
||
```
|
||
|
||
### Eventos del Servidor → Cliente
|
||
|
||
#### onStateChange
|
||
- Sincronización automática del `GameState`
|
||
- Reactividad en tiempo real
|
||
- Cambios en jugadores, ofertas, puntuaciones
|
||
|
||
## 🔧 Configuración
|
||
|
||
### Configuración de Colyseus
|
||
|
||
```typescript
|
||
// app.config.ts
|
||
import config from "@colyseus/tools";
|
||
import { GameRoom } from "./rooms/GameRoom";
|
||
|
||
export default config({
|
||
initializeGameServer: (gameServer) => {
|
||
gameServer.define('game', GameRoom);
|
||
},
|
||
|
||
initializeExpress: (app) => {
|
||
app.get("/", (req, res) => {
|
||
res.send("Snatch or Share Server");
|
||
});
|
||
},
|
||
|
||
beforeListen: () => {
|
||
console.log("🎮 Snatch or Share Server starting...");
|
||
}
|
||
});
|
||
```
|
||
|
||
### Variables de Entorno
|
||
|
||
```env
|
||
# Puerto del servidor
|
||
PORT=2567
|
||
|
||
# Modo de desarrollo
|
||
NODE_ENV=development
|
||
|
||
# URL de producción (si aplica)
|
||
PRODUCTION_URL=wss://tu-servidor.com
|
||
```
|
||
|
||
## 🎯 Funcionalidades Implementadas
|
||
|
||
### Ronda 1: Estado de Naturaleza
|
||
- ✅ Sin reglas especiales
|
||
- ✅ Todos los jugadores son "Traders"
|
||
- ✅ Libre mercado sin enforcement
|
||
|
||
### Sistema de Ofertas
|
||
- ✅ Ofertas simultáneas múltiples
|
||
- ✅ Límite de 2 ofertas por target
|
||
- ✅ Cumplimiento parcial automático
|
||
- ✅ Respuestas: Accept/Reject/Snatch
|
||
|
||
### Gestión de Estado
|
||
- ✅ Sincronización en tiempo real
|
||
- ✅ Asignación automática de roles
|
||
- ✅ Cálculo automático de puntos
|
||
- ✅ Rotación de ofertas (más recientes arriba)
|
||
|
||
## 🔍 Debugging y Monitoreo
|
||
|
||
### Logs del Servidor
|
||
```typescript
|
||
// Logs automáticos incluidos
|
||
console.log(`🎭 Player ${player.name} assigned role: ${player.producerRole}`);
|
||
console.log(`📝 Trade offer created: ${offer.id}`);
|
||
console.log(`✅ Trade offer ${offer.id} ${response}ed`);
|
||
console.log(`🔄 Trade executed: ${isSnatch ? 'SNATCH' : 'FAIR'}`);
|
||
```
|
||
|
||
### Monitor de Colyseus
|
||
```bash
|
||
# Acceder al monitor (desarrollo)
|
||
http://localhost:2567/colyseus
|
||
```
|
||
|
||
### Verificación de Estado
|
||
```bash
|
||
# Verificar salas activas
|
||
curl http://localhost:2567/matchmake/game
|
||
|
||
# Estado del servidor
|
||
curl http://localhost:2567
|
||
```
|
||
|
||
## 🚀 Despliegue
|
||
|
||
### Desarrollo
|
||
```bash
|
||
npm run dev
|
||
# Servidor disponible en ws://localhost:2567
|
||
```
|
||
|
||
### Producción
|
||
```bash
|
||
npm run build
|
||
npm run start
|
||
# Puerto configurado via PORT env var
|
||
```
|
||
|
||
### Docker (desde raíz del proyecto)
|
||
```bash
|
||
docker-compose up server
|
||
```
|
||
|
||
## 🧪 Testing
|
||
|
||
### Probar Conexión
|
||
```bash
|
||
# Verificar que el servidor responde
|
||
curl -i http://localhost:2567
|
||
|
||
# Verificar WebSocket (usando wscat)
|
||
wscat -c ws://localhost:2567
|
||
```
|
||
|
||
### Generar Tipos para Cliente
|
||
```bash
|
||
# Desde directorio server
|
||
npx schema-codegen src/rooms/GameRoom.ts --ts --output ../client/src/types/
|
||
```
|
||
|
||
## ⚡ Rendimiento
|
||
|
||
### Optimizaciones Implementadas
|
||
- **Schema eficiente**: Solo sincroniza cambios
|
||
- **Límites por sala**: Máximo 3 clientes
|
||
- **Cleanup automático**: Gestión de memoria
|
||
- **Validación**: Prevención de estados inválidos
|
||
|
||
### Métricas Clave
|
||
- **Latencia**: <50ms para red local
|
||
- **Throughput**: 100+ mensajes/segundo por sala
|
||
- **Memoria**: ~10MB por sala activa
|
||
|
||
## 🔐 Seguridad
|
||
|
||
### Validaciones Implementadas
|
||
- ✅ Límite de ofertas por jugador
|
||
- ✅ Validación de tokens disponibles
|
||
- ✅ Verificación de permisos por acción
|
||
- ✅ Prevención de auto-ofertas
|
||
|
||
### Consideraciones
|
||
- Sin autenticación (red local)
|
||
- Validación en servidor (nunca confiar en cliente)
|
||
- Límites estrictos en recursos
|
||
|
||
## 🤝 Contribución
|
||
|
||
Ver [CLAUDE.md](../CLAUDE.md) para:
|
||
- Convenciones de código
|
||
- Guías de desarrollo
|
||
- Arquitectura del proyecto
|
||
- Comandos útiles |